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

  • 外部服务一定要隔离,不能影响主业务链路
  • 一定要有重试机制和失败告警
  • 系统越大,越要注重每一环的可靠性设计
相关推荐
带刺的坐椅6 小时前
Solon AI Skills 会是 Agent 的未来吗?
java·agent·langchain4j·solon-ai
jacGJ6 小时前
记录学习--文件读写
java·前端·学习
花间相见7 小时前
【JAVA开发】—— Nginx服务器
java·开发语言·nginx
扶苏-su7 小时前
Java---Properties 类
java·开发语言
cypking7 小时前
四、CRUD操作指南
java
橙子家8 小时前
WebAPI 项目通过 CI/CD 自动化部署到 Linux 服务器(docker-compose)
后端
2301_780669868 小时前
文件字节流输出、文件复制、关闭流的方法
java
剑锋所指,所向披靡!9 小时前
C++之类模版
java·jvm·c++
钟离墨笺9 小时前
Go语言--2go基础-->基本数据类型
开发语言·前端·后端·golang
Coder_Boy_9 小时前
基于SpringAI的在线考试系统-0到1全流程研发:DDD、TDD与CICD协同实践
java·人工智能·spring boot·架构·ddd·tdd