①->和第三方交互(http)保证两边某笔交易处理信息一致
- 失败重试
- 异步回调
- 主动查询(job)
②<-和第三方交互(http)保证两边某笔交易处理信息一致
- 请求落表(幂等性)
- 主动查询(job)
- 异步工作场景:落库后快速给调用者应答,随后处理业务
- 解耦工作场景:多个服务都需要用到某个交易,只需订阅即可
- 削峰工作场景:降低数据库压力(目前没用过)
①分布式事务(同一服务不同的库)
XA协议(2PC),可以用开源框架atomikos
②分布式事务(内部不同服务【都是rpc接口】)-->同步
tcc(熔断->降级),后面再处理,可以用开源框架byteTcc,tcc-transaction
③分布式事务(内部不同服务【中间件关联】)-->异步
方案一:本地消息列表(异步确保)
- 生产者:参照《100%消息可靠性投递》
- 消费者:参照《100%消息可靠性投递》,很多人在这里都提到,如果消费失败,就异步通知生产者回滚之前的业务逻辑 但参照了下淘宝沈询的说辞,分布式事务最好不好搞回滚(回滚的代价太大,一个走不通的消费场景,发生的概率太低,完全可以人工介入 努力送达模型才是王道),又会增加复杂度(为解决回滚后产生的bug可能比消费者处理不了发生的次数都还要多)
方案二:RocketMQ(可靠消息服务)
- 如果上游服务的数据库没操作成功,下游服务是不会收到任何通知【待确认->[已发送-MQ(一个事务)]】
- 如果下游服务的数据库操作成功了,可靠消息服务死活都会确保投递一个消息给下游服务,而且一定确保下游服务务必成功处理这条消息 【已发送->已完成】
原则:通过【消息状态确认】和【消息重发】两个功能,可以确保上游应用、可靠消息服务和下游应用数据的最终一致性
- 生产者(发送方): 发消息(状态“
待发送
”)和业务操作在同一个本地事务里。发消息的时候消息并不立即发出,在事务提交的时候再异步将消息发出。
- 如果遇到MQ异常或网络问题(状态保持不变),会有另外一个服务(job)不断地将这些消息扫出重新发送,需设置retry次数,时间间隔一次比一次大。
- 如果发送消息成功则(状态“
已发送
”)
ps:①如何判断消息是否发送投递成功?
答:收到MQ的正确回执后(MQ Broker监听可以做到)
②是不是会碰到ack为false,然后消息重试,但是其实网络恢复了,或者MQ正常了,消息重复投递了
答:会的,所以需要消费者做到幂等性
- 消费者(接收方): 前提条件:MQ一定要设置成非自动ack(不同的MQ都会有自己的配置,一般默认自动ack) 原因是万一刚接收到消息,已经ack了,准备消费,结果服务down掉了,后面服务恢复了想消费都没消息了 这里面提供两种方案保证消息一定会被消费掉
- 使用本地消息列表,接收到消息直接落表,然后ack;随后开始消费,就算消费失败,由于已经有消息落表,还可以继续重试消费。
这种方案的弊端是又引入了job,好的地方是当然是快速ack,也是一种有效解决消息堆积的办法,尤其是消费时间比较长的消费者- 不使用本地消息列表,等消费成功再ack,如果消费失败呢,也简单,可以通知MQ再发送一次;如果碰到消费者认为此类消息有问题,
不需要再次处理,可以通知MQ删除(RabbitMQ的话会进入死信队列)
落表前消息去重(幂等性){实现方案:select + insert(并发不高时可用)}
- 通过轮询所有队列的方式来确定消息被发送到哪一个队列(负载均衡策略)。订单号相同的消息会被先后发送到同一个队列中,
- 在获取到路由信息以后,会根据算法来选择一个队列,同一个 OrderId 获取到的肯定是同一个队列。
- 服务拆分:将整个项目拆分成多个子项目或者模块,分而治之,将项目进行水平扩展。
- 服务化:解决服务调用复杂之后的服务的注册发现问题。
- 消息队列:解耦,异步处理
- 缓存:各种缓存带来的并发
- 集群
- 限流
- 降级