【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

带你了解Java中的Mono接口

带你了解Java中的Mono接口

目录 一、Mono 接口的基本概念 二、Mono 与 Flux 的区别 三、Mono的使用 创建 Mono 的常见方法 Mono 的操作符与链式调用 错误处理与回退机制 订阅与消费 Mono 四、Mono性能优化与调试技巧 调试技巧 性能优化建议 五、Mono使用总结 一、Mono 接口的基本概念 Mono 是 Project Reactor 响应式编程库中的核心接口之一,代表一个异步的、可能包含零个或一个元素的流。在响应式编程范式中,Mono 专门用于处理那些预期最多只有一个结果的异步操作。它遵循 Reactive Streams 规范,支持非阻塞的回压机制,使得开发者能够以声明式的方式构建高效的异步应用程序。 Mono 的设计哲学基于发布-订阅模式,数据生产者通过 Mono 发布数据,而消费者订阅这些数据。

By Ne0inhk
个人所得税的APP模拟器,纯java版代码开源,截图录屏都可以【仅供参考】

个人所得税的APP模拟器,纯java版代码开源,截图录屏都可以【仅供参考】

文件下载地址:https://wenshushu.vip/pan/index.php?id=36    提取码:7bf9 给大家分享一个用纯Java实现的个人所得税计算模拟器,包含完整的GUI界面和核心计算逻辑,适合Java学习者和税务计算需求者参考使用。 一、项目简介 这是一个使用Java Swing开发的个人所得税计算模拟器,模拟了官方个税APP的核心功能,包括: · 综合所得年度汇算计算 · 税率表查询 · 专项扣除项目设置 · 税务计算结果展示 项目特点: · 100%纯Java实现,无第三方依赖 · 完整GUI界面,支持用户交互 · 详细的代码注释 · 遵循2023年最新个税政策 二、核心代码实现 1. 主程序入口 ```java package com.tax.calculator; import javax.swing.*; /**  * 个人所得税计算模拟器 - 主程序  * @author TaxDeveloper  * @version

By Ne0inhk
用飞算JavaAI做项目:在线图书借阅平台设计与实现

用飞算JavaAI做项目:在线图书借阅平台设计与实现

目录 * 一、引言 * 二、环境准备 * 1. 下载并安装IntelliJ IDEA * 2. 安装飞算JavaAI插件 * 3. 登录飞算JavaAI * 三、模块设计与编码 * 1. 飞算JavaAI生成基础模块 * 2. 核心代码展示 * (1)entity包:核心实体类 * (2)dto包:数据传输对象(带参数校验) * (3)vo包:视图对象(向前端隐藏敏感字段) * (4)service包:业务逻辑实现(含核心校验) * 四、网页展示 * 1. 图书查询页 * 2. 借阅记录页 * 3. 图书管理页 * 五、优化与调试 * 1. 核心优化点 * 2. 调试中遇到的问题及解决 * 六、自我感想 * 七、

By Ne0inhk

Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=GBK 新版IDEA编码格式GBK问题 maven命令Picked up JAVA_TOOL_OPTION

📋 问题概述 问题现象 在使用新版IDEA执行 Maven 构建项目时,控制台输出警告信息: Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=GBK 🔍 问题排查过程 第一阶段:初步判断与假设 初始假设:系统环境变量设置了 Java 编码为 GBK 第二阶段:环境变量验证 cmd # 检查环境变量 echo %JAVA_TOOL_OPTIONS% # 输出:%JAVA_TOOL_OPTIONS%(表示变量未显式设置) 排查结果:系统环境中并未手动设置 JAVA_TOOL_OPTIONS 变量 第三阶段:深入排查IDEA配置 怀疑方向:IDEA内部设置或配置文件指定了GBK编码 检查项包括: 1. IDEA VM Options:

By Ne0inhk