【Java 开发日记】我们来说一下消息的可靠性投递

【Java 开发日记】我们来说一下消息的可靠性投递

目录

1. 核心概念

2. 面临的挑战

3. 关键实现机制

3.1 生产端保证

3.2 Broker端保证

3.3 消费端保证

4. 完整可靠性方案

4.1 事务消息方案(如RocketMQ)

4.2 最大努力投递方案

4.3 本地消息表方案(经典)

5. 高级特性与优化

5.1 顺序性保证

5.2 批量消息可靠性

5.3 监控与对账

6. 不同MQ的实现差异

7. 实践建议

总结

面试回答


1. 核心概念

可靠性投递(Reliable Delivery)是指确保消息从生产者成功到达消费者,即使面对网络故障、系统崩溃等异常情况也能保证不丢失、不重复、按顺序(部分场景)传递。

2. 面临的挑战

  • 网络不可靠:丢包、延迟、分区
  • 节点故障:生产者/消费者/中间件宕机
  • 重复消费:确认机制可能引发重复
  • 顺序保证:分布式环境下消息乱序

3. 关键实现机制

3.1 生产端保证
// 伪代码示例:生产端确认模式 public void sendWithConfirm(Message msg) { // 1. 持久化到本地数据库(防丢失) messageDao.save(msg); // 2. 发送到消息队列 String msgId = rabbitTemplate.convertAndSend(msg); // 3. 等待Broker确认 boolean ack = waitForAck(msgId, TIMEOUT); // 4. 失败重试(指数退避) if (!ack) { retryWithBackoff(msg); } // 5. 最终记录投递状态 updateDeliveryStatus(msgId, ack); }

技术要点

  • 事务机制:同步方式,性能差(不推荐)
  • 确认机制(Confirm)
    • 普通确认(每消息确认)
    • 批量确认(提高吞吐)
    • 异步监听(最佳实践)
  • 本地消息表:事务消息的替代方案
  • 消息持久化:设置delivery_mode=2
3.2 Broker端保证
消息处理流程: Producer → Broker接收 → 持久化存储 → 推送给Consumer → 等待ACK → 删除/重投

持久化策略

  • 队列持久化durable=true
  • 消息持久化delivery_mode=2
  • 镜像队列:多副本冗余(RabbitMQ)
  • 高可用集群:主从切换时不丢消息
3.3 消费端保证
// 消费端保证示例 @RabbitListener(queues = "order.queue") public void handleOrder(OrderMessage order, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long tag) { try { // 1. 业务处理 orderService.process(order); // 2. 手动确认(成功才ACK) channel.basicAck(tag, false); // 3. 更新消费记录 consumeRecordService.markConsumed(order.getId()); } catch (Exception e) { // 4. 失败处理:重试或进入死信队列 if (retryCount < MAX_RETRY) { channel.basicNack(tag, false, true); // 重入队列 } else { channel.basicNack(tag, false, false); // 进入死信队列 alarmService.notifyAdmin(order, e); } } }

消费端关键点

  • 手动ACK:避免自动确认导致消息丢失
  • 幂等性设计
public boolean processWithIdempotent(String msgId) { // 基于消息ID去重 if (redis.exists("processed:" + msgId)) { return true; // 已处理过 } // 业务处理 boolean success = doBusinessLogic(); // 记录处理状态 if (success) { redis.setex("processed:" + msgId, 24h, "1"); } return success; }
  • 死信队列(DLQ):处理无法消费的消息
  • 消费重试策略
    • 立即重试(瞬时故障)
    • 延迟重试(业务依赖)
    • 指数退避(防止雪崩)

4. 完整可靠性方案

4.1 事务消息方案(如RocketMQ)
两阶段提交: 1. 发送Half Message(预备消息) 2. 执行本地事务 3. 根据本地事务结果Commit/Rollback 4. Broker检查事务状态并投递/丢弃
4.2 最大努力投递方案
# 补偿机制实现 def reliable_delivery(message): max_retries = 5 for attempt in range(max_retries): try: # 尝试投递 result = mq_client.send(message) if result.confirmed: log_delivery_success(message.id) return True except Exception as e: log_failure(attempt, e) if attempt == max_retries - 1: # 最终失败,人工介入 send_alert_to_admin(message) save_to_compensation_table(message) return False # 等待后重试 sleep(backoff_time(attempt)) return False
4.3 本地消息表方案(经典)
-- 本地消息表结构 CREATE TABLE local_message ( id BIGINT PRIMARY KEY, biz_id VARCHAR(64), -- 业务ID content TEXT, -- 消息内容 status TINYINT, -- 0:待发送, 1:已发送, 2:已确认 retry_count INT, next_retry_time DATETIME, created_at TIMESTAMP );

工作流程

  1. 业务数据+消息记录原子性写入本地DB
  2. 定时任务扫描待发送消息
  3. 调用MQ发送,成功后更新状态
  4. 消费者处理完成后反向确认
  5. 对账程序定期校验数据一致性

5. 高级特性与优化

5.1 顺序性保证
  • 全局有序:单队列单消费者(性能低)
  • 局部有序:相同sharding key的消息发到同一队列
  • 牺牲场景:重试队列可能破坏顺序
5.2 批量消息可靠性
// 批量消息的可靠性处理 public class BatchMessageReliableSender { public void sendBatch(List<Message> batch) { // 1. 批量持久化到本地 batchMessageDao.saveAll(batch); // 2. 设置批次ID String batchId = generateBatchId(); // 3. 发送批次消息 boolean success = mqTemplate.sendBatch(batchId, batch); // 4. 批次确认(或单条补偿) if (success) { markBatchDelivered(batchId); } else { // 逐条重试或记录异常 compensateFailedMessages(batch); } } }
5.3 监控与对账
  • 实时监控
    • 堆积情况监控
    • 消费延迟报警
    • 失败率统计
  • 定期对账:
-- 消息对账SQL示例 SELECT DATE(create_time) as day, COUNT(*) as total_sent, SUM(CASE WHEN status=2 THEN 1 ELSE 0 END) as confirmed, SUM(CASE WHEN status=1 THEN 1 ELSE 0 END) as pending FROM message_record GROUP BY DATE(create_time) HAVING total_sent != confirmed;

6. 不同MQ的实现差异

特性RabbitMQKafkaRocketMQ
可靠性机制确认+持久化+镜像队列副本机制+ACK+Exactly-Once事务消息+本地存储
顺序性单队列保证Partition内有序Queue内有序
事务支持轻量级事务(性能差)支持Exactly-Once语义完整事务消息
最佳适用场景业务消息、高可靠要求日志流、大数据场景金融交易、订单业务

7. 实践建议

  1. 分级可靠性策略
    • 关键业务:事务消息+本地表+对账
    • 普通业务:确认机制+重试+死信队列
    • 日志类:最多一次投递即可
  2. 性能与可靠性的平衡
    • 同步刷盘 vs 异步刷盘
    • 同步复制 vs 异步复制
    • 根据业务重要性选择配置
  3. 灾难恢复设计:
# 配置示例:多级降级 mq: primary: url: "amqp://primary" timeout: 1000ms secondary: url: "amqp://secondary" timeout: 2000ms fallback-to-db: true # 最终降级到数据库

总结

消息的可靠性投递是一个系统工程,需要在生产端、Broker端、消费端协同设计,结合业务场景、性能要求、成本约束做出合适的选择。没有"银弹"方案,只有最适合的方案。建议从简单方案开始,随着业务复杂度增加逐步引入更完善的可靠性机制。

面试回答

首先,消息可靠性投递指的是:
一个消息从发送到被消费者成功处理,过程中不会丢失或重复,保证最终数据的一致性。在实际系统里,消息可能因为网络问题、服务重启等原因丢失或重复,所以我们需要一套机制来确保可靠。

为什么需要它呢?
比如在订单系统中,用户支付成功后要通知物流系统,如果消息丢了,物流就不会触发,用户体验就受损;如果消息重复,可能重复发货,造成损失。所以像金融、交易这些场景,可靠性特别重要。

常见的实现方式,我了解的有几种:

  1. 生产者确认机制
    生产者发消息后,MQ(比如RabbitMQ)会返回一个确认(ACK),如果没收到ACK,生产者可以重发。这样可以防止消息在发送阶段丢失。
  2. 消息持久化
    消息保存到磁盘,而不是只放在内存。这样即使MQ重启,消息也不会丢。
  3. 消费者手动ACK
    消费者处理完消息后,手动告诉MQ“我已经处理完了”,MQ才删除消息;如果处理失败,MQ可以把消息重新投递给其他消费者。避免消息在处理阶段丢失。
  4. 事务消息(比如RocketMQ)
    先发一个“半消息”,等本地事务执行成功,再确认投递;如果失败,就回滚。这适用于分布式事务场景。
  5. 消息去重
    为了避免重复消费,可以在消费端做幂等性设计。比如在数据库里记录消息ID,每次处理前先查一下是否已经处理过。

实际中我们一般会结合业务来设计。
比如一个订单状态同步的场景,我可能会用:生产者确认 + 消息持久化 + 消费者手动ACK + 消费端幂等性。这样基本能覆盖发送、存储、消费各个环节的可靠性。

当然,可靠性和性能之间需要权衡,比如持久化会降低吞吐量,手动ACK会增加延迟。所以要根据业务需求来选择合适的方案。

追加:遇到过消息丢失或重复的问题,你是怎么排查和解决的?
追加:是否了解最终一致性、最大努力通知等模式 ?

如果小假的内容对你有帮助,请点赞评论收藏。创作不易,大家的支持就是我坚持下去的动力!

Read more

在家也能做 AI 导演!本地部署 Wan2.1 视频生成模型全攻略

在家也能做 AI 导演!本地部署 Wan2.1 视频生成模型全攻略

文章目录 * 前言 * 1.软件准备 * 1.1 ComfyUI * 1.2 文本编码器 * 1.3 VAE * 1.4 视频生成模型 * 2.整合配置 * 3. 本地运行测试 * 4. 公网使用Wan2.1模型生成视频 * 4.1 创建远程连接公网地址 * 5. 固定远程访问公网地址 * 总结 前言 Wan2.1 模型搭配 ComfyUI 框架,能实现文本转视频、图片转动画等功能,生成的视频质量可媲美专业工具,普通 PC 就能运行,特别适合自媒体创作者、短视频团队和 AI 爱好者快速制作动态内容,无需复杂技术背景也能上手,且完全开源免费,性价比很高。 使用时发现,选择模型版本要结合显卡配置:

By Ne0inhk

零基础指南:从 Prompt 到上下文工程构建 AI Agent 【万字长】

PS:微信吃一些链接,详细链接信息见:https://github.com/phodal/build-coding-agent-context-engineering 过去两三年,我曾为多家公司的资深开发人员开展 Agent 开发培训;最近一个月,我也一直在为毕业生设计和培训 AI Agent。直到本周, 结合 AI 能力的模拟项目 showcase 完成后,我才真正理清楚:如何针对不同阶段的开发者,系统地构建 Agent 的学习路径。 这个过程, 也让我深刻体会到“知识诅咒”的存在——原来自己习以为常的知识,对于初学者来说,可能是最大的障碍。 我们可以简单把学习过程分为四部分: * 结构化提示词工程 —— 如何工程化设计高效、可复用的提示词。 * 上下文工程与知识检索 —— 知识检索、生成与压缩上下文信息,产生高质量的知识背景。 * 工具函数的系统化设计 —— 设计并实现可供 Agent 调用的工具与接口。 * Agent 规划与多 Agent —— 构建任务规划与执行路径,

By Ne0inhk
工程化路径:当我们信任并拥抱 AI,超级潜力才真正被点燃

工程化路径:当我们信任并拥抱 AI,超级潜力才真正被点燃

目录 一、为什么“信任”是 AI 超级潜力的启动器? 二、AI 的潜能不是线性累加,而是“信任阈值”后的指数跃迁 三、技术角度拆解:为什么信任 AI 会解锁超级潜力? (一)AI 能让系统从“局部最优”跳到“全局最优” (二)信任使得 AI 能真正进入“连续执行”模式 (三)当 AI 被赋予代理权,系统整合能力急剧放大 四、为什么很多组织看得到 AI,却吃不到 AI 的红利? (一)AI 不被信任,是因为组织仍在用“工具范式”理解它 (二)组织结构本身和“AI

By Ne0inhk
AI 开发必用的4个skills组合,用来流畅掌控AI开发流程 ,灵活控制AI(opencode skills)

AI 开发必用的4个skills组合,用来流畅掌控AI开发流程 ,灵活控制AI(opencode skills)

skills 一种技能增强器。 skills 可以理解为升级版的提示词,它的文件记录了某个skill(技能)的元信息,就是描述这个skills的名称等信息, 另外它的文件中还记录了skills的技能实现步骤。 以下4个skills在AI项目开发中,我认为必不可缺一。 这4个skills的引入,可更为方便我们去介入AI,控制AI,给AI制定边界。 我会用一个音乐机器人项目开发来介绍这4个skills,如何介入AI开发流程,如何行云流水的控制AI。 指令式 控制AI 开发流程的主控调度器:有4个SIKLLS 在我的项目中.opencode目录中存在4个skills, 4个skills技能结合和.opencode目录同级的AGNETS.md文档,AGNETS.md是主控配置文件, 是AI 开发流程的主控调度器,负责协调三个专业技能包(毒蛇产品经理、UI设计师、全栈开发工程师、ui-ux-pro-max) ui-ux-pro-max技能包,我120%的推荐,减少了不少UI配色的塑料感,可在文末看我此次,用技能包开发的UI界面,做一个效果对比。 skills技能指令: 我

By Ne0inhk