Go微服务: 分布式之通过可靠消息实现最终一致性

通过可靠消息实现最终一致性

  • 可靠消息,就是靠普消息,还是基于之前的这个案例
  • 比如这个订单服务,无论你是先发送消息,还是先新建订单,它其实都是发送的不可靠消息
  • 就是说如果这个消息,像mysql事务那样,只要订单服务不确认,下游就没办法消费
  • 如果你这个订单服务挂了,就可以取消这个消息,就不用做这个本地消息表了
  • 本地消息表,要有一个这个循环的这么一个查询,高并发的时候,你本地的数据库本身压力就大
  • 再弄这么一个查询,一直循环的查,它这个压力也不小,现在看一下基于事务消息,也称为可靠消息
  • 生产者就可以理解为这个订单服务,消费者就可以理解为积分服务,还有库存服务
  • 先看第一个,生产者先发一个半消息给消息队列,消息队列就回我这个半消息成功的信息
  • 这是一个半消息,然后拿到这个半消息成功的结果之后,我们往数据库里写一个Transaction事务
  • 我们就把本地事务执行了,执行之后,也就是到了第4步, 成功情况下就是确认这个半消息
  • 只要到第4步这个commit成功了,消费者就可以消费了,因为你的这个消息已经在这个消息队列中了
  • 这个解决方式是解决了只要我们能发出去的这个消息就是可靠的,只要能提交的消息,本地消息就一定是成功的
  • 因为你本地事务都已经执行成功了,先发半消息,消息队列回给我,回给我之后,我就能收到了,说明已经成功了
  • 然后这个时候你就开始干你本地的事儿,比如订单生产者自己开始建订单,建订单产品表,这都是你的事
  • 我本地能成功了之后,1,2,3步是为了能让消费者成功消费的准备工作
  • 这个思路就是说只要一提交,那我本地这边全部ok, 然后我这个消息队列, 肯定能保证我我的最终一致性
  • 如果在3之前出错,那不会做事务,那相当于准备工作没做好,那下游也不会做相应的这个事情
  • 同时,我们还要思考,有没有其他方面的情况会导致问题
    • 比如,更复杂的网络传输问题,别人的服务宕机了或有bug了
    • 因为微服务在开发的时候,每个小组可以时刻发布自己的服务,它不受控制
  • 如果上述分布式服务出问题了,消息队列也会有一个回查事务消息状态的机制
  • 我会问你这个生产者,哎,你这个状态是啥?然后生产者就会查询这个本地事务状态
  • 第5步和第6步,就是又一次为这个返回事务状态做commit和rollback,就是为下一步的工作做准备
  • 就是消息队列,不知道你这个消息要不要投递,也不能知道别人的状态是什么样的
  • 那消息队列就会问这个生产者,你这个消息事务是啥状态,在第6步得到查询的事务状态是commit
  • 那我消息队列就commit消息投递,一旦消息投递了,这个消费者就可以进行消费了
  • 假如说,返回的这个事务状态是rollback,消息队列就可以把消息扔了
  • 在这整个链路里,有成功的,让消费者消费消息;也有失败,让这个消息队列去丢弃消息
  • 还有中间状态,就是说我不确定这个事务是不是正确的?那询问还是有结果的,就是 commit 或 rollback
  • 其实我们说走到第5步,第6步,还有第7步的时候,他就可以再一次确认我们的消息是否要投递还是丢弃
  • 到这里,一直没有看到说有锁的存在,在高并发的情况下,消息队列就保证了我们最终的一致性
  • 就是说锁的存在,它一定是和高并发是这个对立的,我们尽量不要用锁的方式去考虑我们的并发

积分和库存业务场景的对比

  • 再回过头来看一下这个模型,如果你的生产者是订单,而消费者是库存的话
  • 如果库存不足,消费者是库存服务,库存不足,虽然成功的发送了到这个RocketMQ里
  • 但是库存没有办法成功消费这条消息,这个和其他业务形态上是不一样的
  • 比如订单服务,可以说是送积分,从技术角度来说,积分是没有上限的
  • 还有一种形态,就是我们说发短信,你成功的购买了某某产品等等
  • 这种业务形态, 你的生产者只要把消息放到了消息队列, 消息队列一定是可以保障的
  • 就是说你消息队列是集群吧,你在不挂的情况下,是一定能送达到消费者应用队列里的
  • 比如说, 积分服务,短信服务,你这边已经是commit了
  • 无论你是在第4步commit的,还是说第7步commit的下游是一定能消费到的
  • 但是库存服务不一样,如果库存不足了,你这边又没办法返回
  • 左边是库存服务和订单服务,右边是消息队列
  • 从正向来说,如果服务的提供方发送消息到这个消息队列,也就是生产者发送消息到消息队列
  • 只要消息队列集群不挂,那么我们的消费者是一定能收到这个消息的
  • 就是实现最终的一致性对于积分服务,短信服务的使用都是没有问题的
  • 因为积分理论上是无上限的,我们的短信是一定能发上去的,只要你短信账户里,有足够的余额
  • 但是当库存为零的时候,你的这个消息队列仍然收到了我们发送成功的消息
  • 但下游库存服务是没有办法消费成功的,我们不可能凭空多出来这么多库存
  • 让你去消费,因为没有那么多库存,我们又不能把这个消息队列成功投递库存不足的消息
  • 再返回给这个生产者,这个是不可能的,所以,我们能不能先发送一个归还库存的半消息
  • 这个半消息, 对于库存服务来说, 它是见不到的,我们发完半消息之后
  • 去调用扣减库存的Srv服务,这就是一个Grpc的这么一个调用,先看这个返回失败的情况
  • 如果调用库存失败了,这个时候返回一个rollback,因为一开始就是调用的是归还
  • 那你这个时候就调用rollback,如果我们库存执行失败了,那说明我们库存这个数据是没有变的
  • 既然你库存调用是失败,订单就不会创建, 因为我本地这个数据库就不会变
  • 那么我们两边的这个数据都没变,这个业务也是可以接受的
  • 就是说没有造成数据不一致,那你就不要给我发这个消息来告诉我,你要去归还库存了
  • 那我们直接rollback这个消息,然后这个消息队列, 就可以把这个归还库存的消息给它扔掉了
  • 扔掉之后,看左边这一边就没问题了,数据都没变
  • 你执行失败了,我这边又没执行,或者说,这两边的数据都保持一致,这就是OK的
  • 好,我们再看下一张执行成功的图,看我们订单服务发送一个半消息
  • 然后调用了Grpc扣减那个库存,然后它成功了
  • 成功之后, 我们就会执行这个本地 mysql 事务服务, 其实它可能成功,也可能失败
  • 因为我们如果在微服务里, 由于网络原因或宕机,Bug,停电等各种问题
  • 它都可能导致一个服务的运行失败
  • 如果我们先说这个执行本地mysql事务成功,在第4步成功,执行rollback
  • 还是从数据的角度来看,库存扣减成功,订单执行成功,我们本地也执行成功
  • 那就是说我们两边数据都改变了,这个业务上也是可以接受的
  • 订单生成成功了,库存也扣减了,那就相当于交易成功
  • 那你就不要给我发送这个归还库存的半消息了,所以是 rollback这个半消息
  • 然后,我们把这个消息就扔掉了
  • 那我们再看看,如果第4步执行失败了,就是说我们有任何情况
  • 订单服务有bug,断电或者其他场景,执行失败,那就执行一个commit
  • 因为订单执行失败了,相当于订单数据没有变,那你的库存现在是变了
  • 因为之前第三步已经执行成功了,那这个时候就要告诉库存,说给我扣减了,因为我执行失败了
  • 失败以后,提交一个commit之后,然后,这个库存服务就可以监听到这个消息队列里
  • 因为它 commit 了嘛,这个消息就能看到了,订阅了这个消息之后,就能去归还库存了
  • 如果你这个订单服务,还有一些其他问题,怎么办?其实这个消息队列还提供一种机制
  • 就是回查这个消息,比如说我现在不确定你这个是要提交还是rollback
  • 这个回查机制,就查这个订单的服务,那你还去你这个本地事务的数据库库里去捞数据
  • 你捞对了,就rollback,捞错了,还是commit, 你commit之后
  • 我还是能调用到这个库存服务,然后你再给我归还
  • 这张图就是看到了为了保持这个数据的一致性,我们这边整个业务流程的保证就是这样了
  • 再看上面这张图,还有问题,是在原来的基础上增加了第8条发送延迟消息和监听延迟消息。
  • 就是说我这个库存有一百个,我这个用户买完以后,就是不支付
  • 如果他一周不支付或者一个月不支付,你的库存永远不释放,别人永远买不了
  • 那不就把这个商城的库存给锁死了
  • 当我库存执行成功,这个本地的事务也执行成功的时候,我就把它发送一条延迟消息。
  • 假如我们规定半个小时,时间一到,这个延迟消息就会投递
  • 然后,我们就会根据这个消息去看,这个订单是 支付成功了,还是支付失败了
  • 如果是未支付或者是支付失败都可以,如果你执行失败了,那我就归还库存
  • 因为对商城来说,如果没收到钱,那我就归还库存,半个小时之后,仍然其他的用户就可以买
  • 这样就完美的解决了库存,订单和这个订单下单成功后不支付的这么一个场景
  • 这里的核心重点是:在一开始发送了一个归还库存的半消息
  • 执行commit和rollback的情况是反着来的
相关推荐
问道飞鱼1 小时前
【微服务知识】开源RPC框架Dubbo入门介绍
微服务·rpc·开源·dubbo
星染xr2 小时前
kafka 生产经验——数据积压(消费者如何提高吞吐量)
分布式·kafka
东方巴黎~Sunsiny2 小时前
如何监控Kafka消费者的性能指标?
分布式·kafka
飞升不如收破烂~2 小时前
kafka
分布式·kafka
CodingBrother3 小时前
软考之面向服务架构SOA
微服务·架构
龙哥·三年风水3 小时前
群控系统服务端开发模式-应用开发-前端个人信息功能
分布式·vue·群控系统
甘橘籽3 小时前
【RPC】 gRPC、pb基本使用--经验与总结
golang
杜杜的man4 小时前
【go从零单排】HTTP客户端和服务端
开发语言·http·golang
小码哥呀4 小时前
RabbitMQ集群搭建
分布式·rabbitmq
材料苦逼不会梦到计算机白富美4 小时前
golang分布式缓存项目 Day6 防止缓存击穿
分布式·缓存·golang