🚀 在高并发场景下基于MQ实现可靠短信发送机制

🚀 在高并发场景下基于MQ实现可靠短信发送机制

作者:天天摸鱼的java工程师 · 8年经验

标签:Java · RocketMQ/Kafka · 高并发 · 消息重试 · 架构设计 · 分布式可靠性


📌 背景场景

在实际的业务系统中,短信通知是一个非常常见的非核心但又关键的功能。例如:

  • 用户注册/登录时发送验证码
  • 订单支付成功通知用户
  • 活动营销、优惠券提醒等

短信服务属于强依赖外部服务的非确定性操作,可能因为第三方网关不稳定、网络抖动、限流等原因导致失败。如果你把短信发送逻辑直接写在业务流程内,那么一旦短信服务挂了,整个业务链就会受到影响,这是非常不可靠的。


🧠 设计目标

我们希望实现一个具备以下能力的短信发送系统:

  1. 异步解耦:业务系统不被短信服务影响;
  2. 可靠消息投递:短信失败后支持自动重试;
  3. 高并发支撑:能承受百万级并发消息流;
  4. 可观测性强:方便排查失败消息、查看状态。

🏗️ 架构设计

整体架构如下图:

css 复制代码
[业务服务] ---> [MQ Topic: sms-topic] ---> [短信消费者服务] ---> [短信网关]
                          ↑
                    [失败消息重试队列]

消息流转说明:

  1. 业务服务将短信消息发送到 MQ(如 RocketMQ/Kafka) ;
  2. 短信消费者异步消费消息,调用短信网关接口发送;
  3. 若发送失败(如第三方接口异常),将消息重新投递到延迟队列或 DeadLetter Queue;
  4. 系统根据重试策略进行补发,最大重试次数后仍失败则记录日志/人工告警。

🧪 核心代码实现(基于 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 解耦和异步处理机制,我们不仅提升了系统的 稳定性和可用性 ,还具备了应对 高并发、可重试、可观测 的能力。经验告诉我们:

  • 外部服务一定要隔离,不能影响主业务链路
  • 一定要有重试机制和失败告警
  • 系统越大,越要注重每一环的可靠性设计
相关推荐
高频因子挖掘机16 分钟前
历史行情返回顺序变了,为什么按行号计算指标很危险?
后端·github·api
紧跟先前的步伐1 小时前
Go AI Agent 实战 03|Token、上下文窗口与模型调用成本
java·人工智能·golang
const_qiu2 小时前
第四章 00:开篇 —— 从「会聊天」到「记得住、会干活、能协作」
java·开发语言·人工智能·python
Wang's Blog2 小时前
Java 项目实战: 交友项目 - 个人资料管理的更新接口实现与坑
java·开发语言·交友
团子哒哒2 小时前
数组、切片和Map
后端
Promising_GEO2 小时前
GEE实战02期:从卫星波段开始:利用 Sentinel-2 自己计算 NDVI
java·sentinel·遥感·空间分析
专注仿真2 小时前
ThinkPHP6 API接口开发实例:从零搭建RESTful风格接口
后端·php·restful
大文说跨境2 小时前
亚马逊多账号管理浏览器怎么选?云登/紫鸟/AdsPower 对比
java·前端·自动化
开心猴爷2 小时前
应对iOS审核4.3:可以使用 IpaGuard 来给属性名混淆的技术细节与平衡之道
后端·ios
1m555555552 小时前
RAG 工程实战(五):正确知识已经进入 Prompt,为什么大模型还是会答错?
后端·面试