实习项目中使用到,学习一下。
原博客:https://www.cnblogs.com/haolujun/p/9632835.html、https://www.cnblogs.com/haolujun/p/9641840.html
RabbitMQ架构
RabbitMQ是一个分布式系统,这里面有几个抽象概念。
- broker:每个节点运行的服务程序,功能为维护该节点的队列的增删以及转发队列操作请求。
- master queue:每个队列都分为一个主队列和若干个镜像队列。
- mirror queue:镜像队列,作为master queue的备份。在master queue所在节点挂掉之后,系统把mirror queue提升为master queue,负责处理客户端队列操作请求。注意,mirror queue只做镜像,设计目的不是为了承担客户端读写压力。
如上图所示,集群中有两个节点,每个节点上有一个broker,每个broker负责本机上队列的维护,并且borker之间可以互相通信。集群中有两个队列A和B,每个队列都分为master queue和mirror queue(备份)。那么队列上的生产消费怎么实现的呢?
队列消费
如上图有两个consumer消费队列A,这两个consumer连在了集群的不同机器上。RabbitMQ集群中的任何一个节点都拥有集群上所有队列的元信息,所以连接到集群中的任何一个节点都可以,主要区别在于有的consumer连在master queue所在节点,有的连在非master queue节点上。
因为mirror queue要和master queue保持一致,故需要同步机制,正因为一致性的限制,导致所有的读写操作都必须都操作在master queue上(想想,为啥读也要从master queue中读?和数据库读写分离是不一样的。),然后由master节点同步操作到mirror queue所在的节点。即使consumer连接到了非master queue节点,该consumer的操作也会被路由到master queue所在的节点上,这样才能进行消费。
队列生产
原理和消费一样,如果连接到非 master queue 节点,则路由过去。
所以,到这里小伙伴们就可以看到 RabbitMQ的不足:由于master queue单节点,导致性能瓶颈,吞吐量受限。虽然为了提高性能,内部使用了Erlang这个语言实现,但是终究摆脱不了架构设计上的致命缺陷。
可靠性
RabbitMq的mirror queue 在master queue宕机之后,会被提升为master queue,如下图所示。
队列A的consumer在消费的时候,机器宕机,此时客户端和服务端分别做如下动作:
- 服务端:把mirror queue提升为master queue
- 客户端:连接到新的master queue 所在的节点进行消费或者生产
当master queue 所在节点宕机后,其正在被消费的消息的相关信息全部丢失,即服务端不知道消费者对那一瞬间消费的消息是否进行了ACK,所以在mirror queue被提升为master queue时,会把宕机前正在进行消费的的消息全部重新发送一遍,即客户端重连后,消息可能被重复消费,这个时候就必须依靠应用层逻辑来判断来避免重复消费。
在持久化方面,RabbitMQ的master queue每次收到新消息后,都会立刻写入磁盘,并把消息同步给mirror queue。假设在master queue 收到消息后,消息未同步到mirror queue 之前master queue 宕机,则此时mirror queue中就没有刚刚master queue收到的那条消息,当这个mirror queue被提升为master queue时,消费者连接到新的master queue上进行消费时就丢了一条消息。所以,RabbitMQ也会丢消息,只不过这个丢消息的概率非常低。
Kafka
队列机制
针对RabbitMQ这个缺陷,Kafka改进的点就是:把一个队列的单一master变成多个master,即一台机器扛不住qps,那么我就用多台机器扛qps,把一个队列的流量均匀分散在多台机器上不就可以了么?注意,多个master之间的数据没有交集,即一条消息要么发送到这个master queue,要么发送到另外一个master queue。
这里面的每个master queue 在Kafka中叫做Partition,即一个分片。一个队列有多个主分片,每个主分片又有若干副分片做备份,同步机制类似于RabbitMQ。
如上图,我们省略了不同的queue,假设集群上只有一个queue(Kafka中叫Topic)。每个生产者随机把消息发送到主分片上,之后主分片再同步给副分片。
队列读取的时候虚拟出一个Group的概念,一个Topic内部的消息,只会路由到同Group内的一个consumer上,不会同时和同一Group内多个consumer进行路由,同一个Group中的consumer消费的消息是不一样的;Group之间共享一个Topic,看起来就是一个队列的多个拷贝。所以,为了达到多个Group共享一个Topic数据,Kafka并不会像RabbitMQ那样消息消费完毕立马删除,而是必须在后台配置保存日期,即只保存最近一段时间的消息,超过这个时间的消息就会从磁盘删除,这样就保证了在一个时间段内,Topic数据对所有Group可见(这个特性使得Kafka非常适合做一个公司的数据总线)。队列读同样是读主分片,并且为了优化性能,消费者与主分片有一一的对应关系,如果消费者数目大于分片数,则存在某些消费者得不到消息。
由此可见,Kafka绝对是为了高吞吐量设计的,比如设置分片数为100,那么就有100台机器去扛一个Topic的流量,当然比RabbitMQ的单机性能好。
可靠性
Kafka当主分片所在机器宕机后,服务端会把一个副分片提升为主分片,如下图所示。
服务端和客户端会有如下动作:
- 服务端:把副分片提升为主分片
- 客户端:连接到新的主分片
Kafka同样有主从同步,所以也必定存在与RabbitMQ同样丢消息的问题。但是Kafka的每个客户端保存了读取消息的偏移信息,故当一个主分片宕机后,Kafka客户端可以从副分片相应位移后继续消费,不会有重复消费的情况。
持久化方面,Kafka默认把消息直接写文件,但是由于操作系统的cache原因,消息可能不会立马写到磁盘上,这个时候就需要刷新文件到磁盘。由于刷新文件到磁盘是一个比较耗时的操作,故Kafka提供了两种不同的刷新配置:
1 | #每接收多少条消息刷一下磁盘 |
我们完全可以把log.flush.interval.messages设置为1,这样Kafka就能在持久化方面达到和RabbitMQ同样的安全级别。
但是Kafka集群依赖ZK,如上图所示,所以对于Kafka稳定性的评估必须考虑ZK集群的稳定性,而一般我们认为任何分布式集群的稳定性都小于1,故两个集群的串联稳定性会下降一些,维护更复杂一些,这点没有RabbitMQ有优势。
总结
其实好多开源组件随着时间推移,往往都进行了各种改进。就比如Kafka虽然是为了日志而生,给人第一印象是容易丢消息,但是经过这么多年的改进,其可靠性可能并不逊色RabbitMQ了,只需要你根据不同的业务场景配置不同的配置参数,即可达到适合自己的安全级别。
- 从吞吐量上看,在不要求消息顺序情况下,Kafka完胜;在要求消息先后顺序的场景,性能应该稍逊RabbitMQ(此时Kafka的分片数只能为1)。
- 从稳定性来看,RabbitMQ胜出,但是Kafka也并不逊色多少。
注:转载文章请注明出处,谢谢~