本文目录导读:

最大努力通知(Best-Effort Notification)是一种常见的分布式系统最终一致性解决方案,通常用于支付回调、订单状态通知等场景,它的核心特点是:尽最大可能将消息通知到接收方,但不保证100%送达,且没有复杂的回滚机制。
当通知失败时,重试机制是保证可靠性的关键,以下是实现“最大努力通知”重试的几种典型策略和技术实现方案:
核心重试策略(阶梯式/退避式)
直接无限制地每隔1秒重试是不科学的,容易导致雪崩,通常采用阶梯式间隔或指数退避策略。
- 固定间隔(不推荐): 每隔30秒重试一次,问题在于短时间大量失败会对系统造成压力。
- 指数退避(推荐): 每次失败后,等待时间翻倍。
- 第1次重试:10秒后
- 第2次重试:20秒后
- 第3次重试:40秒后
- 上限:通常设置最大间隔(如5分钟)和最大重试次数(如15次)。
- 固定步长+增量: 依次等待 1分钟、2分钟、5分钟、10分钟、30分钟、1小时... 这种方式更灵活。
重试的触发与执行机制
主要有三种实现模式:
A. 定时任务扫描(最常用)
原理: 将需要发送的通知记录存储到数据库中(如notification表),包含状态字段(待发送、发送中、成功、失败)和retry_count(重试次数)、next_retry_time(下次重试时间)。
- 流程:
- 状态机: 初始状态为“待发送”。
- 定时任务(如Spring @Scheduled): 每隔几秒扫描一次数据库,查询
status = '待发送' AND next_retry_time <= NOW() AND retry_count < MAX_RETRY的记录。 - 发送: 发起HTTP/回调请求。
- 处理结果:
- 成功 -> 更新状态为“成功”,记录发送日志。
- 失败 ->
retry_count++,计算新的next_retry_time(根据策略),更新状态为“待发送”。
- 优点: 实现简单,持久化,支持幂等性。
- 缺点: 对数据库有一定压力,实时性略差(取决于扫描间隔)。
B. 消息队列(MQ)延迟消息(推荐)
原理: 利用RocketMQ、RabbitMQ等消息中间件的延迟队列或死信队列功能。
- 流程(以RocketMQ为例):
- 发送初始消息: 发送一条延迟级别为0的消息到普通Topic,消费者立即消费。
- 第一次发送失败: 消费者收到消息后,尝试回调第三方,如果失败,重新发送一条延迟级别为2(如5秒)的消息到相同的Topic,消费者在5秒后收到并再次重试。
- 多次重试: 每次失败都发送更高延迟级别的消息,RocketMQ支持固定的18个延迟级别(1s, 5s, 10s, 30s, 1m, 2m, ...)。
- 最终失败: 当达到最大重试次数后,将消息发送到死信队列,人工介入处理。
- 优点: 解耦,高吞吐,适合高并发场景,利用MQ的可靠性保证消息不丢。
- 缺点: 延迟级别有限(RocketMQ),RabbitMQ需要安装插件。
C. 本地重试+异步补偿
原理: 同步调用失败时,立即在内存中进行N次快速重试,如果仍然失败,则写入本地日志或数据库的异步表,由后台进程补偿。
- 流程: 同步调用 -> 失败 -> 线程sleep+重试(3次,每次间隔100ms) -> 全失败 -> 抛异常或写异步任务表 -> 后台线程处理。
- 优点: 实时性高,减少数据库扫描压力。
- 缺点: 内存重试占用线程资源,不适用于耗时长的重试。
关键注意事项(防重放与防雪崩)
- 接口幂等性(核心): 接收方(第三方)必须能处理重复通知,因为通知可能因网络抖动而重复发送。
- 实现: 通知消息中必须包含唯一的业务ID(如订单号、流水号),接收方需在本地存储该ID,处理前先判断是否已处理过。
- 去重表: 发送方(通知中心)每次发通知前,应检查该业务ID是否已标记为“成功”,避免浪费资源。
- 人工兜底(最终手段):
- 当重试次数耗尽(如总计重试15次)后,消息进入死信队列或人工处理列表。
- 提供运维后台:运营人员可以点击“手动重试”按钮,或通过脚本批量重试。
- 防止消息丢失:
- 事务消息: 利用RocketMQ的事务消息,保证“本地事务”和“发消息”的原子性。
- 本地消息表: 在业务库中建立消息表,通过本地事务将业务操作和写消息表放在同一个事务中,再由定时任务将“待发送”的消息可靠地发送出去。
- 限流与保护:
- 如果大量回调失败,不要疯狂重试,设置一个最大并发重试数和通知间隔,保护发送方和接收方系统。
- 可以考虑使用断路器模式:如果某个第三方连续失败N次,暂时将其标记为“不可用”,暂停对该第三方的一切通知,等待一段时间后再尝试。
一个典型的最优方案
- 生产消息: 使用本地消息表(保证业务与通知的强一致性)或RocketMQ事务消息。
- 消费并通知: 消费者尝试调用第三方接口。
- 阶梯重试: 重试策略采用 延迟队列或指数退避定时扫描。
- 兜底: 超过最大重试次数后,进入人工处理表(触发报警)。
- 安全: 通知接口必须支持幂等(非业务逻辑检查+去重);发送方要做限流和断路器。
这个模型就是“最大努力通知”的经典实现:能发就发,发失败就等一会儿再发,直到发成功或彻底放弃(转人工)。