0%

基于Raft的分布式消息队列RMBQ

准备从以下方向论述

  1. 为什么要用消息队列(mq)
  2. 对于高可靠、强一致性的场景现在都有Rabbitmq解决方案了,那为什么还要用Raft算法实现一个mq

架构演变——mq在架构中有什么用

  1. 异步请求
  2. 应用解耦
  3. 削峰

异步请求

以下是对图中的三种场景进行的解释分析,讲的是一个注册业务系统

  1. 串行业务:client向数据库写入注册信息,写入成功后,进行下面的业务,发送邮件,然后发送短信,假设写入数据库的时间为50,发送邮件50,发短信50,那么用户完成注册收到响应为150

  2. 并行:发邮件和发短信分开成两个业务系统,并行处理。此时用户得到响应只需要100

  3. 加入mq中间件:将注册的消息放入mq后,就可以返回给client,然后邮件系统、短信系统从mq中拉取消息进行消费,这种场景中,写入mq的时间为5,client收到响应的时间为55。

应用解耦

  1. 订单系统与库存系统耦合:订单系统调用库存系统的接口,如果减库存失败,则订单服务也会失败。
  2. 加入mq解耦:订单系统下单成功,放入消息到mq里,库存系统订阅,拉取后进行消费。如果库存系统宕掉了,也不会影响订单系统的服务进行。

上面只是简单描述了mq在分布式系统的部分功能,重点不在于此

关键

发现RabbitMQ与我们类似!
相同:强一致性、可靠
不同:一致性算法 ->QGuaranteed Multicast

希望

  1. 如果找到RabbitMQ的适用场景,我们是可以复用的。
  2. 比较RaftMq和RabbitMQ的一致性算法差异->性能比较(比它厉害的地方

场景

  1. 高可靠场景
  2. 强一致性场景

高可靠场景(消息不允许丢失)

  1. 用户下订单成功后,放入消息到mq中,积分系统需要取出,进行加积分服务,此时mq的消息如果丢失的话,用户会少了积分!

  2. 这是前文提及的注册服务,用户在填写注册信息后,需要等待邮箱或者短信通知,需要填写验证码进行下一步,如果mq里的消息丢失了,用户就收不到验证码!

  3. 记账系统,如果记账发生了失败,需要放到mq里,到时候从mq里拉取消息,进行二次记账,此时是绝对不允许丢失的!

那么如何高可靠呢?

保证高可靠的三点

  1. 消息持久化
  2. 消费者消费成功后,返回ACK确认,queue删除消息
  3. 如果宕机,follower重新选举leader,新的leader跟原来的数据一致

重点说一下第三点:分布式系统中,需要数据的复制副本是一件很常见的事情,数据的冗余来保证可靠性,机器宕掉了,另一台备份顶上,问题关键在于如何来复制,如何来保证一致性,收到消息的指令,是马上返回ack告诉服务上游,还是等当所有副本都达到一致性才返回ack。(强一致性问题)

强一致性:client能读取到最新正确的数据

前提

我们以{N=2 ,W = 1, R = 1}为环境,A、B一主一备。

N — 数据复制的份数
W — 更新数据是需要保证写完成的节点数
R — 读取数据的时候需要读取的节点数

如果W+R<=N,则是弱一致性。例如对于一主一备异步复制的关系型数据库,N=2,W=1,R=1,则如果读的是备库,就可能无法读取主库已经更新过的数据,所以是弱一致性。

所以我们即将讨论的是一个弱一致性的环境下。

场景1:顺序不一致

比如说金融经常出现的交易啊,记账啊等情景
A:set x = 100;add x+= 10 ;mul x= 110%
B:set x = 100;mul x
= 110%; add x+= 10

A、B两个节点在复制数据的时候,可能会导致存储消息顺序不一致。比如加钱操作和加息操作顺序不是一致的,在我们{N=2 ,W = 1, R = 1}的环境下就会出错。

场景2:长度不一致

比如支付系统中,转账情景下。
A:set x = 100;add x+= 10 ;
B:set x = 100;

A此时 有加钱消息,而B没有复制到此条消息。

上面两种场景会导致实时的交易出错问题

另一个问题:现在分布式场景中,是如何保证上述场景的正常工作?
我们这里先只谈消息中间件的解决方案。

rabbitmq 这个中间件是可以满足金融方面那些强一致性,高可靠的场景!

和rabbitmq pk一下

用Raft比较rabbitmq的一致性算法。

  1. 响应client时机,复制处理的时间延迟
  2. 可用性,吞吐量

rabbitmq

  1. 镜像队列的可靠多播(Guaranteed Multicast)
  2. Guaranteed Multicast:发送者将消息发给集群中的每个节点,这要求集群节点是全联通的。当发送节点挂掉时,消息可能没有发到集群中的每个节点,这就引入了集群中哪些节点要为已挂掉节点负责、继续发送消息的问题。
  3. 环状结构:GM组将集群中的节点组成一个环
    如果某一个节点宕掉了,左右相邻的节点会知道
    上游节点负责将挂掉节点未转发的消息继续发给最近的下游节点

这就要求GM组中每个节点缓存收到的消息(gm缓存队列),以便当下游节点挂掉时重新转发该消息。消息的最初发送者在收到自身发出的消息时,必须能够再次发出该消息的确认信息(gm ack message)在GM组中同步,这样其他节点才能够将缓存消息删除。当消息最初发送者再次收到确认消息时,说明整个集群已经同步该消息。此时新加入GM组的节点,将不会再收到该消息。

Raft vs. Guaranteed Multicast

  1. 复制对比
    GM:所有节点同步之后才能向客户端返回成功,需要在所有的节点上进行两次处理才可以保证同步
    Raft:确认同步大多数follower,就向客户端返回成功

  2. RabbitMq缺点:时延高,在有大量消息堆积的情况下性能会下降

总结

在过往解决高可靠 强一致性场景中,我们用rabbitmq这样的消息中间件可以解决,但是存在不少的缺点:时延高,在有大量消息堆积的情况下性能会下降

目的:mq架构是一个多副本集群,使用Raft强一致性算法在复制的过程加以改善。

附:

基于Raft的分布式消息队列(2)