🚀 在高并发场景下基于MQ实现可靠短信发送机制
作者:天天摸鱼的java工程师 · 8年经验
标签:Java · RocketMQ/Kafka · 高并发 · 消息重试 · 架构设计 · 分布式可靠性
📌 背景场景
在实际的业务系统中,短信通知是一个非常常见的非核心但又关键的功能。例如:
- 用户注册/登录时发送验证码
- 订单支付成功通知用户
- 活动营销、优惠券提醒等
短信服务属于强依赖外部服务的非确定性操作,可能因为第三方网关不稳定、网络抖动、限流等原因导致失败。如果你把短信发送逻辑直接写在业务流程内,那么一旦短信服务挂了,整个业务链就会受到影响,这是非常不可靠的。
🧠 设计目标
我们希望实现一个具备以下能力的短信发送系统:
- 异步解耦:业务系统不被短信服务影响;
- 可靠消息投递:短信失败后支持自动重试;
- 高并发支撑:能承受百万级并发消息流;
- 可观测性强:方便排查失败消息、查看状态。
🏗️ 架构设计
整体架构如下图:
css
[业务服务] ---> [MQ Topic: sms-topic] ---> [短信消费者服务] ---> [短信网关]
↑
[失败消息重试队列]
消息流转说明:
- 业务服务将短信消息发送到 MQ(如 RocketMQ/Kafka) ;
- 短信消费者异步消费消息,调用短信网关接口发送;
- 若发送失败(如第三方接口异常),将消息重新投递到延迟队列或 DeadLetter Queue;
- 系统根据重试策略进行补发,最大重试次数后仍失败则记录日志/人工告警。
🧪 核心代码实现(基于 RocketMQ)
1. 消息生产者发送短信任务
java
public class SmsProducer {
private final DefaultMQProducer producer;
public SmsProducer(String producerGroup) throws MQClientException {
producer = new DefaultMQProducer(producerGroup);
producer.setNamesrvAddr("localhost:9876");
producer.start();
}
public void sendSmsMessage(SmsMessage smsMessage) throws Exception {
Message message = new Message("sms-topic",
smsMessage.getBizType(),
JSONObject.toJSONString(smsMessage).getBytes(StandardCharsets.UTF_8));
message.setDelayTimeLevel(0); // 立即发送
SendResult result = producer.send(message);
log.info("消息发送成功:{}", result);
}
}
2. 消费者接收并发送短信
typescript
@RocketMQMessageListener(topic = "sms-topic", consumerGroup = "sms-consumer")
public class SmsConsumer implements RocketMQListener<String> {
@Autowired
private SmsGatewayClient smsGateway;
@Autowired
private RetryProducer retryProducer;
@Override
public void onMessage(String messageStr) {
SmsMessage sms = JSONObject.parseObject(messageStr, SmsMessage.class);
try {
boolean success = smsGateway.send(sms);
if (!success) {
throw new RuntimeException("短信发送失败");
}
} catch (Exception e) {
log.error("短信发送异常:{}", e.getMessage());
retryProducer.sendRetryMessage(sms); // 发送到重试队列
}
}
}
3. 重试队列逻辑(延迟队列)
java
public class RetryProducer {
private final DefaultMQProducer retryProducer;
public RetryProducer() throws MQClientException {
retryProducer = new DefaultMQProducer("sms-retry-group");
retryProducer.setNamesrvAddr("localhost:9876");
retryProducer.start();
}
public void sendRetryMessage(SmsMessage smsMessage) throws Exception {
if (smsMessage.getRetryCount() >= 3) {
log.warn("短信重试超过最大次数,记录日志告警:{}", smsMessage);
return;
}
smsMessage.setRetryCount(smsMessage.getRetryCount() + 1);
Message message = new Message("sms-topic",
smsMessage.getBizType(),
JSONObject.toJSONString(smsMessage).getBytes(StandardCharsets.UTF_8));
message.setDelayTimeLevel(3); // 延迟10s再重试(RocketMQ内置的延迟等级)
retryProducer.send(message);
}
}
⚙️ 高并发处理建议
1. 业务层异步解耦
使用 MQ 完全脱离主业务链路,业务响应时间更短:
scss
// 接口响应无需等待短信发送完成
smsProducer.sendSmsMessageAsync(smsMessage);
2. 消费者水平扩展
RocketMQ/Kafka 都支持 多个消费者实例并发消费,通过部署多个实例来提升吞吐量。
yaml
replicaCount: 5 # Kubernetes部署多个副本消费短信队列
3. 短信网关异步并发发送
如果使用第三方 SDK,可以使用线程池进行异步发送:
ini
ExecutorService executor = Executors.newFixedThreadPool(10);
executor.submit(() -> smsGateway.send(sms));
📈 可观测性与监控
- 失败消息入库:记录失败原因、重试次数、最终状态;
- Prometheus + Grafana:监控短信发送成功率、MQ堆积情况;
- 报警机制:短信失败率超过阈值自动报警;
✅ 总结
通过 MQ 解耦和异步处理机制,我们不仅提升了系统的 稳定性和可用性 ,还具备了应对 高并发、可重试、可观测 的能力。经验告诉我们:
- 外部服务一定要隔离,不能影响主业务链路
- 一定要有重试机制和失败告警
- 系统越大,越要注重每一环的可靠性设计