微服务异步通信实战:SNS+SQS扇出模式解析与代码示例

发布时间:2026/8/27 10:42:01
微服务异步通信实战:SNS+SQS扇出模式解析与代码示例 微服务架构做到一定复杂度后服务之间最难处理的不是怎么写代码而是怎么通信。同步 HTTP 调用简单直观但链路一长就会出现一个典型场景A 调用 BB 调用 CC 又去调 D结果 D 慢了几百毫秒A 的线程池很快被占满接口超时客户端重试流量翻倍雪崩就是这么来的。于是大家开始找异步方案。在 AWS 生态里最常用也最容易上手的答案就是 SNS SQS。这篇文章是“AWS SNS SQS 微服务架构”系列的第一篇。我会先把 SNS 和 SQS 的核心概念、适用边界、两者组合出来的扇出模式讲明白再带你用 AWS CLI、Python boto3、Java SDK 完整跑通一条“订单事件同时分发到多个服务”的消息链路最后给出生产环境最容易踩的坑和工程建议。读完这篇文章你应该能回答三个问题第一某个消息场景到底该用 SNS、SQS 还是两个都用第二怎么让一条 SNS 消息自动投递到多个 SQS 队列第三消息到了消费者手里为什么还会重复线上该怎么防御。1. 为什么微服务架构里要用 SNS SQS微服务之间的通信方式从大方向上分只有两类同步和异步。绝大多数团队最开始使用的都是同步 HTTP服务 A 发起调用阻塞等待服务 B 返回结果。这种模式不是不好而是对链路延时长、调用关系复杂的场景不友好。典型的问题有三个。第一个是调用链放大。A 调用 BB 调用 C如果 C 挂了或变慢A 的请求线程会被一直占用。流量高峰时线程池被打满后续请求全部排队。更糟的是客户端超时后通常会自动重试重试流量再涌进来系统很容易从一个小故障演变成大面积不可用。第二个是下游强耦合。订单服务在代码里写了“通知服务”的地址那它就是强依赖通知服务。通知服务重启、升级、甚至只是网络抖动都可能让订单服务接口 5xx。业务上“通知失败”并不应该导致“下单失败”但同步调用的代码会让这两件事强相关。第三个是处理能力错配。下单是突发的扣库存可能只要 5 毫秒但发短信要调第三方接口平均要 300 毫秒。用同步阻塞的方式一个下单线程会被慢操作拖住整个系统的吞吐就被最慢的那个环节锁死了。当然不是所有调用都应该异步化。实时查询类的操作比如查订单详情、登录校验天然需要同步拿到结果而事件通知、状态变更传播、任务执行这类操作并不要求调用方立刻拿到结果这些才是异步消息的应用场景。在 AWS 上做异步最常遇到的两个服务就是 SNS 和 SQS。2. SNS 与 SQS 核心概念与定位2.1 SQS分布式消息队列SQSSimple Queue Service是全托管的分布式消息队列。你不需要自己部署任何 MQ 服务只需要创建队列然后生产方发消息、消费方拉消息。用生活场景来理解SQS 就是一个快递柜。寄件人不用当面把东西交给收件人而是放进快递柜收件人什么时候有空就什么时候去取。快递柜本身并不关心寄件人和收件人是否同时在线。这种解耦就是队列最核心的价值。SQS 有两种队列类型选错会影响后续架构设计标准队列默认类型吞吐高支持近似无限并发但只保证至少一次投递也就是说消费方有可能重复收到同一条消息。消息顺序也不保证。FIFO 队列提供先入先出的严格顺序语义支持正好一次投递但吞吐量受限默认配额有限FIFO 队列的命名必须以.fifo结尾。在微服务架构里绝大多数业务队列场景用标准队列就够只有对消息顺序有硬要求的场景比如交易流水回放、订单状态机流转才必须用 FIFO。2.2 SNS发布订阅广播总线SNSSimple Notification Service是发布/订阅模式的消息服务。它的核心组件是 Topic主题。生产者把消息发布到 TopicTopic 再把消息推送给所有订阅者。还是用生活场景理解SNS 像广播电台的频道。电台只管发信号谁拿着收音机调到这个频道谁就能收到。发信号的广播台不需要知道有多少台收音机也不管收音机是否开机。SNS 支持的订阅端非常多HTTP/HTTPS 端点、Email、短信、移动推送、SQS 队列、Lambda 函数等。在微服务架构中最常用的两个订阅端是 SQS 队列和 Lambda 函数。SNS 本身不负责存储消息也不保证消费者一定处理成功它更像一个“事件交换机”发布一次转发多路。2.3 SNS 与 SQS 的核心区别很多新手分不清这两个服务我用一张表把它们放在一起对比。维度同步 HTTPSQSSNS通信模式一对一同步一对一异步一对多异步广播调用结果调用方阻塞等待消费者自主拉取订阅者各自接收削峰能力无强需要配合队列实现故障隔离弱强取决于订阅端消息存储无默认保留 4 天不存储立即推送时序保证自然有序标准无序FIFO 有序无序适用场景实时查询、强一致任务队列、削峰填谷事件通知、扇出分发一个简单的记忆方式是SQS 是“点对点”的消息管道一条消息只会被一个消费者取走SNS 是“广播”的喇叭一条消息会复制给所有订阅者。2.4 为什么是 SNS SQS 组合如果只用一个队列解决的是“一个生产者给一个消费者解耦”的问题。但很多需求是“一个事件要同时影响多个服务”。以订单创建为例订单服务只需要发布一条“订单创建成功”的事件但库存服务、通知服务、积分服务都需要这个事件而且它们各自的处理速度不同。如果直接把事件写到某个下游队列里其他下游就收不到如果让订单服务挨个往三四个队列各发一遍订单服务的代码就重复且耦合。SNS SQS 的组合正是解决这类“一对多”扇出需求的标准做法SNS 主题把事件广播到多个 SQS 队列每个队列对应一个消费者服务。生产者只需要跟 SNS 交互消费者只需要跟自己的 SQS 队列交互中间完全是解耦的。这个模式在 AWS 官方许多参考架构里都有出现是微服务事件驱动设计的基础。订单服务 → SNSorder-events ├── SQS 队列 A → 库存服务 ├── SQS 队列 B → 通知服务 └── SQS 队列 C → 积分服务3. 环境准备与前置条件要跑通本文示例需要准备以下环境。3.1 AWS 账号与 IAM 权限你需要一个能访问 SNS 和 SQS 的 AWS 账号。不要在生产账号直接用管理员权限做实验更稳妥的做法是创建一个最小权限 IAM 用户或者使用 AWS CloudShell 内置的临时凭证。本文示例需要的权限至少包括sqs:CreateQueuesqs:SendMessagesqs:ReceiveMessagesqs:DeleteMessagesqs:GetQueueAttributessqs:SetQueueAttributessns:CreateTopicsns:Subscribesns:Publishsns:GetTopicAttributes如果只是本地试验推荐使用 AWS CloudShell它直接内置了 AWS CLI 和临时凭证不用在本地配置密钥安全性也更高。如果你在本地操作则按下方方式配置。3.2 安装并配置 AWS CLI安装 AWS CLI 之后执行aws configure按提示输入 Access Key、Secret Key、默认区域和输出格式。验证是否配置成功aws sts get-caller-identity看到 Account 和 Arn 输出就说明配置完成。注意本文示例按us-east-1区域编写如果你使用其他区域请把命令和 ARN 中的区域替换成实际值。3.3 Java 与 Python 运行环境本文代码示例提供 Pythonboto3和 JavaAWS SDK for Java 2.x两个版本。Python 版只需要安装依赖pip install boto3Java 版需要 JDK 8 以上和 Maven并在 pom.xml 中引入software.amazon.awssdk:sqs与software.amazon.awssdk:sns两个依赖。版本号不要照抄网上写死的数字以 Maven 中央仓库当前稳定版本为准。如果使用 Spring Boot可以通过依赖管理统一维护版本。3.4 本地模拟选型LocalStack如果你不想在开发阶段连真实 AWS可以用 LocalStack 在本地模拟 SNS/SQS。用 Docker 启动docker run --name localstack -d -p 4566:4566 localstack/localstackPython 代码里指定 endpoint 即可连接本地模拟环境import boto3 sqs boto3.client( sqs, endpoint_urlhttp://localhost:4566, region_nameus-east-1, aws_access_key_idtest, aws_secret_access_keytest )LocalStack 适合单元测试和本地联调但它不是真正的 AWS 服务某些高级特性可能缺失。涉及生产问题排查时仍然要以真实环境为准。4. 整体架构设计订单事件扇出下面进入实践。我们先定义一个足够典型的业务场景。4.1 场景描述假设有一个电商系统用户提交订单后订单服务需要触发三件事库存服务扣减库存通知服务发送短信和 App 推送积分服务给用户增加积分。这三件事都不需要用户在请求里等待消费速度也各不相同。其中发短信还要调第三方接口最容易慢和失败。这就是典型的异步扇出场景。4.2 事件流转完整链路如下订单服务发布一条消息到 SNS 主题order-events消息内容是一段订单 JSON。SNS 将这条消息自动复制并推送到所有订阅了该主题的 SQS 队列。库存、通知、积分三个服务各自从自己的队列中拉取消息消费速度互不影响。如果某个服务消费失败消息不会丢失而是在队列里保留等待重试或进入死信队列。这里的关键点是订单服务只感知 SNS不感知任何下游服务的地址和状态。新增一个下游服务时订单服务一行代码都不用改。4.3 为什么不用 Kafka一部分读者会想这个场景 Kafka 也能做。确实能但两者侧重不同。Kafka 是一个