背景
微服务场景下需要同步信息的场景。
还是前文的栗子: 如下微服务
- 支付服务:负责完成支付操作,其中有支付流水数据。
- 账单服务:指定时间生成账单给用户,其中有账单流水数据。
此时产品上有个需求,在支付管理端根据是否出账搜索支付流水,而出账是账单服务的功能。所以这里涉及到信息的同步,那么,我们怎么保证同步一定能成功呢(最终一致性)。
消费者
保证在队列中的消息,一定会被消费。用白话来讲,就是不停消费直到成功。
方式比较简单:消息队列都使用手动提交。处理完了,再保证提交。尽量遵循触发-查询机制,提供可重入性,即消息队列只传递id这种非实质信息,收到之后再通过rpc查询拉取完整数据来更新。
生产者
主要是发送消息到队列这步的可靠性考量
方案一:浅尝辄止
以递增的时间间隔重试5次。如果失败了,上报到日志和告警,人工介入。同时,具体业务准备好重试的脚本。根据实时的情况进行处理。优点:
- 简单,能解决瞬间的网络抖动造成的失败。
缺点:
- 可靠性低。在消息队列故障30分钟这种场景下,无法自动恢复,同时从日志捞取信息,也不是特别方便。
方案二:内有波澜
失败之后,内存维护一个重试队列,先由5,10,20, 40, 80, 160, 320s的间隔重试。之后以5分钟一次的间隔请求。同时,也要打入日志系统,告警通知。
优点:可靠性会比一高很多,在消息队列故障30分钟这种场景下,也能自动恢复。可以做成package的方式,方便接入。
缺点:
- 服务重启或者机器挂了,消息就丢了。
限制:
- 消息处理需要遵循触发-查询机制
方案三:内有波澜plus
失败之后,内存维护一个重试队列,先由5,10,20, 40, 80,160, 320s的间隔重试。然后append到本地文件,同时以5分钟一次的频率做重试。重试完成之后,从磁盘中删除对应信息。当服务重启,从磁盘把数据导入内存即可。
优点:
- 可靠性会比一高很多,在消息队列故障30分钟这种场景下,也能自动恢复,可以做成包的方式。
- 具有比较强的通用型
缺点:
- 增加了和磁盘打交道的逻辑,引入了文件io。
限制:
- 消息处理需要遵循触发-查询机制
方案四:有备无患
实现任务重试微服务,该服务通过维护一张任务表,重试任务直到成功。相当于是消息队列这个可靠中间件有问题,就丢给这个重试服务这个自己实现的“中间件”。
优点:
- 可靠性会比一高很多,在消息队列故障30分钟这种场景下,也能自动恢复。
- 具有比较强的通用性。
缺点:
- 成本变高,需要额外服务。同时,如果服务也挂了,还是得依赖上报。(当然,上报也可能挂了)
限制:
- 消息处理需要遵循触发-查询机制
以上方案中,三,四基本能解决重试阶段写入消息队列的可靠性问题。但针对另一个场景:正想写本身服务就没了的情况(比如oom导致服务被系统kill了) 还是不行。
方案五:先入为主
要做变更之前,先写入到消息同步微服务,告诉他要做什么事(把什么消息放入消息队列),和流程最长执行时间,以及发给谁。该服务维护一张任务表,任务初始处于未激活状态。
等业务做完要同步的时候,再rpc请求触发激活。
任务管理微服务如果发现一个任务,超过最长执行时间没有激活。就说明激活rpc失败了,或者是服务崩溃,本身就没做变更。此时,自动激活即可。
优点:完全可靠(事务可能还是会失败,可靠指数据一定最终一致)。
缺点:
- 开发成本比较大,同时会增加消息调用。
- 增加一个节点,事务失败得可能性更高。
限制:
- 消息处理需要遵循触发-查询机制
总结
推荐:方案三
推荐理由:成本不高,可靠性较强。可靠度99.99%。(此处不论代码本身bug)