如何让Azure Service Bus消息仅被缩放池中单个WebApi实例处理
多实例App Service下单条消息仅单实例消费的成熟实现方案
问题本质
当前多实例同时收到同一条消息、重复推送通知的核心原因,是消费端没有用到Azure Service Bus原生的竞争消费者投递模型,错误地让每个WebApi实例成为了独立的消息订阅方,消息自然会被全量推送给所有实例。
原生现成机制(完全无需自研逻辑)
这套场景是企业级消息队列设计的经典场景,Azure Service Bus本身就有开箱即用的实现,和Azure App Service自动扩缩容能力天然适配:
- 方案1:直接使用Service Bus队列承载消息流转
- 队列的原生投递逻辑就是独占锁模式:任意一个消费者实例从队列拉取消息时,这条消息会被加上临时可见性锁,锁有效期内其他所有同队列的消费者完全无法看到这条消息,从机制上杜绝了多实例同时拿到同一条消息的可能。
- 你只需要把BackgroundWorker的消息发送目标从带多订阅的主题改为普通Service Bus队列,所有WebApi实例统一从这个队列拉取消息即可,不需要改动任何业务逻辑。
- 容错逻辑自动生效:如果拿到消息的实例处理崩溃、没在锁超时时间内完成ack确认,锁会自动释放,消息重新变为可见状态,由Service Bus自动分配给其他在线的可用实例处理,完全适配实例动态扩缩容的场景。
- 方案2:保留Service Bus主题路由能力的同订阅竞争消费
- 如果你后续需要基于主题做消息的多路由分发(比如后续要新增其他消费方处理同一份消息),只需要保证所有WebApi实例共同绑定同一个主题订阅,不要每个实例创建独立的实例专属订阅即可。
- 这个单订阅的底层消费逻辑和队列完全一致:所有绑定该订阅的WebApi实例走竞争消费模式,同一条消息只会被分配给其中一个实例,不会出现全量推送的问题。
- 要避开的典型坑:千万不要在WebApi实例启动时动态创建带实例唯一标识后缀的独立订阅,这种配置下同一条主题消息会被推送给所有订阅端点,必然出现重复消费的问题。
轻量兜底增强
就算用了上述原生机制,也可以加一层极低成本的防重复逻辑,应对极端场景下的消息重复投递(比如网络重试、锁超时后消息重投):
- 消费端拿到消息的第一时间,提取消息自带的全局唯一
MessageId - 用短周期的分布式缓存(比如Redis、App Service内置的内存缓存)记录已经处理过的
MessageId,如果遇到已存在的ID直接跳过处理即可,不会触发重复的通知推送。
内容的提问来源于stack exchange,提问作者Kazus
相关产品推荐
相关产品推荐

