微服务架构中服务崩溃重启后未发送数据的处理方法
微服务跨节点投递崩溃场景的可靠数据处理方案
这个场景的核心是解决本地业务处理和跨服务投递的一致性问题,针对你列的四个流程节点,落地时不需要搞太复杂的分布式事务,用成熟的工程方案就能完全覆盖故障场景,没有数据丢失风险:
核心风险匹配
先对应你提到的四个节点明确风险边界,方案设计就是把每个节点的空窗期补上:
- 服务A成功接收数据:需要保证请求不会因为后续流程故障丢失,不会出现无记录的重复处理
- 服务A成功完成数据处理:需要保证本地业务处理结果、待发往B的数据状态强绑定,不会出现「业务处理成了,要发的数据没留下」的情况
- 服务A向B发数据前崩溃:这是核心故障点,本质是内存态的待发数据随进程退出丢失,没有持久化的补发凭据
- 服务A恢复上线:需要有自动机制识别崩溃前没发出去的数据,自动完成补发,同时避免重复投递搞乱服务B的业务
可落地方案
方案1:本地消息表(90%以上业务场景首选,改造成本最低)
这个方案完全基于本地数据库事务实现,不依赖额外中间件能力,稳定性最高:
- 服务A接收到请求后,开启本地数据库事务,在同一个事务内完成两个操作:
- 执行自身业务逻辑,把业务处理结果写入业务库
- 往同库的
local_message表插入一条记录,字段包含:全局唯一业务ID、要发给B的完整报文、消息状态(初始为待发送)、创建时间、重试次数
两个操作同生共死,只要事务提交成功,就绝对不会出现业务处理完、消息没存下来的情况。
- 本地事务提交成功后,再异步触发向服务B的投递动作(不管是HTTP调用B的接口,还是往MQ发消息给B消费,都必须放在事务提交之后执行,绝对不能把远程调用包在本地数据库事务里,避免长事务拖垮数据库)。
- 服务A内置定时补偿任务,按固定周期(比如30秒)扫描
local_message表中所有状态为待发送、且超过重试间隔的记录,重新向B发起投递。 - 服务B必须实现幂等逻辑:拿到消息后先根据全局唯一业务ID判断自己有没有处理过这条数据,处理过直接返回成功,没处理过再执行业务逻辑;B处理完成后回调A的接口,A把对应消息的状态更新为
已发送,后续补偿任务就不会再重复投递这条消息。 - 你提到的崩溃场景完全被覆盖:哪怕A在准备发数据给B的前一刻进程崩溃,待发送的消息早就持久化在数据库里了,等A恢复上线后,定时任务第一时间就能把这些未投递的消息捞出来补发,不会丢任何数据。
方案2:事务消息(已有RocketMQ等支持事务消息的中间件时可选)
如果你的技术栈已经在使用支持事务消息的MQ组件,可以直接用这个方案,省掉自己写本地消息表和补偿任务的工作量:
- 服务A接收到请求后,先向MQ发送半消息——这类消息对消费方B不可见,不会被提前消费。
- 半消息发送成功后,A执行本地业务逻辑处理,处理完成后给MQ发送提交确认,半消息就会转为正常可消费状态,服务B就能拉取到消息执行自身逻辑。
- 如果A在执行本地逻辑时崩溃、或者没来得及给MQ发送提交确认就退出,MQ会定时启动回查逻辑,询问A这条半消息对应的本地业务到底处理成功没有;等A恢复上线后收到回查请求,直接查自己的业务库,确认数据处理成功就告诉MQ可以投递消息,处理失败就告诉MQ回滚半消息即可。
必做的兜底规则
- 不要无限重试:单条消息重试次数达到阈值(比如16次)后,把消息状态标记为
投递失败,触发告警人工介入,避免持续重试把服务B打挂。 - 所有跨服务投递的报文必须带全局唯一业务ID,不要用自增ID当幂等依据,避免跨库ID重复导致幂等失效。
- 不要用分布式强一致性事务(比如XA、Seata AT这类)解决这个场景,性能损耗太大,普通业务场景完全没必要。
内容的提问来源于stack exchange,提问作者Grigor Penev
相关产品推荐
相关产品推荐

