RabbitMQ与Kafka选型对比
RabbitMQ模型:
名词 | 描述 |
---|---|
Queue | 用于存储消息,消费者直接绑定Queue进行消费消息 |
Exchange | 生产者将消息发送到Exchange,由交换器将消息通过匹配Exchange Type、Binding Key、Routing Key后路由到一个或者多个队列中。 |
Exchange Type | Direct、Fanout、Topic、Headers |
Routing Key | 生产者发送消息给Exchange会指定一个Routing Key。 |
Binding Key | 在绑定Exchange与Queue时会指定一个Binding Key |
1.Exchange在声明时会绑定Queue和Binding Key,当Exchange收到消息会根据消息的
2.Routing Key与Exchange Type、Binding Key进行匹配,最后会路由到相关的队列当中。
Fanout,将消息发送到与该交换器所绑定的所有队列中,与Routing Key、Bind Key无关,这就是广播模式。
Topic,通过对消息的Routing Key和Exchange、Queue进行匹配,将消息路由给一个或多个队列,以此来达到发布/订阅模式。
Direct,把消息路由到哪些Bind Key和Routing Key完全匹配的队列中。
Headers,不依赖与路由键的匹配规则,基本用不上。
3.消费者会直接订阅Queue里的消息进行消费,多个消费者订阅同个Queue会形成消息竞争状态,以此达到负载均衡作用。
Kafka模型
名词 | 描述 |
---|---|
Topic | 队列是通过Topic进行隔离的,生产者发送消息必须指定Topic |
Broker | 一个Kafka Server的被称为一个Broker。 |
Partition | 每个Topic可以包含多个Partition,多个Partition会平均分配给同一个Consumer Group里的不同Consumer进行消费 |
Consumer Group | 不在同一个Group 的Consumer能重复消费同一条消息(订阅),相同Group的Consumer存在消费竞争(负载均衡) |
Kafka与RabbitMQ比没有Exchange的概念,生产者直接发消息Topic(队列)。
Kafka的订阅者是通过消费组(Consumer Group)来体现的,每个消费组都可以重复消费Topic一份完整的消息,不同消费组之间消费进度彼此不受影响。例如Message1能被Consumer Group 1和Consumer Group2里的消费者都消费一次。
消费组中包含多个消费者,同个Group的消费者之间是竞争消费的关系。例如Message2只能够被Consumer Group里某一个Consumer只消费一次。
Kafka具有消息存储的功能,消息被消费后不会被立即删除,因为需要被不同的Consumer Group多次消费同个消息,因此会在Topic维护一个Consumer Offset,每消费成功Offset自增1.
功能对比
对比项 | RabbitMQ | Kafka |
---|---|---|
吞吐量 | 低 | 高 |
有序性 | 全局有序性 | 分区有序性 |
消息可靠性 | 多策略组合 | 消息持久化 |
流处理 | 不支持 | 支持 |
时效性 | 高 | 中 |
运维便捷度 | 高 | 中 |
系统依赖 | 无 | zookeeper |
Web监控 | 自带 | 第三方 |
优先级队列 | 支持 | 不支持 |
死信 | 支持 | 不支持 |
客户端支持 | 支持多种语言 | |
社区生态 | 好 | |
安全机制 | (TLS/SSL、SASL)身份认证和(读写)权限控制 | |
消息回溯 | 支持 | 不支持 |
对比描述
共同点
RabbitMQ与Kafka都有很好的客户端语言支持、安全机制与生态支持。
性能
Kafka的诞生的是处理高并发日志的,吞吐量比较高,每秒请求数达到数十万量级
RabbitMQ每秒请求数则为万级别,有测试报告指出Kafka是RabbitMQ的10倍以上性能。
运维便捷
RabbitMQ相对比较方便,可以使用yum或者docker安装,自带Web管理UI,没有额外的依赖,除了需要做镜像队列外需要引入HAproxy。
Kafka则需要依赖Zookeeper,也没有自带的管理工具,可以使用第三方的Kafka Eagle代替,Kafka Manager过于难用,另外Kafka没有yum安装,docker镜像也是社区人员自己建的。
有序性
RabbitMQ理论上是全局有序性的,但是由于【发后既忘】+【自动确认】机制的原因,如果在同个队列的多个消费者做相同的业务处理时,他们的各自的执行任务无法保证有序完成。如果确保100%有序可以使用【非自动确认】,但会影响消费性能。
Kafka支持分区有序性,如果对有序性有严格要求可以设置单个Partition,可是单个Partition并发性比较低,因此在多个Partition情况下可以根据业务指定key把相关的消息路由到同一个Partition,例如相同UserId行为信息可以到Partition 1进行处理。
时效性
Kafka基本上无论在客户端还是服务端都是以【异步批量】的机制进行处理,通俗的讲就是先攒起来一堆消息,到了某个阀值再发送,也会导致一些消息可靠性与消息有时效上的问题,当然可以通过各种配置策略进行解决。
消息回溯
Kafka在消费完了消息后不会立即删除,只会修改offset,如果之前部分业务消费失败了可以重新设置offset进行重新消费。
RabbitMQ则是[发后既忘]的机制,一但消费者确认消息则删除,但是可以通过死信进行补偿消费。此外RabbitMQ在队列消息堆积多的情况下性能表现不佳,所以尽可能的及时消费消息。
特色功能
RabbitMQ具有死信的功能,可以通过死信形成重复消费与延时发送。
Kafka具有流处理功能,可以收集用户的行为日志进行存储与分析。
Kafka为什么快
关键核心技术点:
异步批量处理
磁盘顺序读写
操作系统PageCache缓存数据
零拷贝加速消费
Kafka的诞生就是为了高并发日志处理的,那么在他整个机制里使用了很多批量、异步、缓存。例如生产者客户端,他会积累一定量(条数、大小)的消息,再批量的发给kafka broker,如果在这段时间客户端服务挂了,就等于消息丢失了。当broker接受到了消息后,还有一堆骚操作-异步刷盘,也就是生产者发送给broker之后他是记录在缓存的,每隔一段时间才会持久化到磁盘,假如这段真空期broker挂了,消息也是丢了。
Kafka是否消息不可靠?
Kafka快是因为牺牲了消息可靠换取回来的性能,在最早期版本的确没提供消息可靠的策略,经过多个版本迭代后的功能完善,已经不存在这种旧观念。那么可靠的关键点有以下:
生产者
设置ack:
0:producer不等待broker的ack,broker一接收到还没有写入磁盘就已经返回,可靠性最低;
1:producer等待broker的ack,partition的leader刷盘成功后返回ack,如果在follower同步成功之前leader故障,那么将会丢失数据,可靠性中;
-1:producer等待broker的ack,partition的leader和follower全部落盘成功后才返回ack,数据一般不会丢失,延迟时间长但是可靠性高
消费者
设置enable.auto.commitrue,不管执行结果如何,消费者会自动提交offset。
false,需要用户需要手动提交offset,可以根据执行结果具体处理offset
RabbitMQ如何保证消息的可靠性
1、保证消息不丢失(三步)
1.1、开启事务(不推荐)
1.2、开启confirm(推荐)
1.3、开启RabbitMQ持久化(交换机、队列、消息)
1.4、关闭RabbitMQ自动ack(改成手动)
2、保证消息不重复消费
2.1、幂等性(每个消息用一个唯一标识来区分,消费前先判断标识有没有被消费过,若已消费过,则直接ACK)
3、RabbitMQ如何保证消息的顺序性
将消息放入同一个交换机,交给同一个队列,这个队列只有一个消费者,消费者只允许同时开启一个线程
4、RabbitMQ消息重试机制
消费者在消费消息的时候,如果消费者业务逻辑出现程序异常,这时候应该如何处理?
答案:使用消息重试机制(SpringBoot默认3次消息重试机制)
如何合适选择重试机制
消费者取到消息后,调用第三方接口,接口无法访问,需要使用重试机制
消费者取到消息后,抛出数据转换异常,不需要重试机制,需要发布者进行解决。
5、SpringBoot消息重试机制
@EnableRetry注解:表示启用重试机制(value表示哪些异常需要触发重试,maxAttempts设置最大重试次数,delay表示重试的延迟时间,multiplier表示上一次延时时间是这一次的倍数)
eg、@Retryable(value = Exception.class, maxAttempts = 3, backoff = @Backoff(delay = 2000, multiplier = 1.5))
@Recover注解:当重试次数达到设置的最大次数的时候,程序还是执行异常,调用的回调函数。
6、RabbitMQ死信队列
死信队列是当消息在一个队列因为下列原因:
a、消息被拒绝(basic.reject或basic.nack)并且requeue=false.
b、消息TTL过期
c、队列达到最大长度(队列满了,数据无法添加到mq中)
变成了 “死信队列” 后被重新投递(publish)到另一个Exchange,然后重新消费。说白了就是没有被消费的消息换个地方重新被消费
7、RabbitMQ解决分布式事务
经典案例,以目前流行的外卖为例,用户下单后,调用订单服务,订单服务调用派单系统通知送外卖人员送单,这时候订单系统与派单系统采用MQ异步通讯。
RabbitMQ解决分布式事务原理
答案:采用最终一致性原理
需要保证以下三要素:
a、确保生产者一定要将数据投递到MQ服务器中(采用MQ消息确认机制)
b、确保消费者能够正确消费消息,采用手动ACK模式(注意重试、幂等性问题)
c、如何保证第一个事务先执行,采用补偿机制,在创建一个补单消费者进行监听,如果订单没有创建成功,进行补单。(如果第一个事务中出错,补单消费者会在重新执行一次第一个事务,例如第一个事务是添加订单表,如果失败在补单的时候重新生成订单记录,由于订单号唯一,所以不会重复)
8、RabbitMQ保证消息不丢失的具体方案
前提:
(1)开启confirm
(2)开启RabbitMQ的持久化(交换机、队列、消息)
(3)关闭RabbitMQ的自动ack(改成手动)
(4)配置消费重试次数,消费重试间隔时间等
涉及到的技术点:
MQ、Redis、定时任务
8.1、保证投放消息不丢失
(1)先将消息放入生产者Redis(此时消息的状态为未投放),再放入队列
(2)根据confirm(ReturnCallback和ConfirmCallback)的结果来确定消息是否投递成功,
投递成功的,修改生产者redis中消息的投递状态为已投递
投递失败的消息将会放入失败的Redis,并从生产者Redis中删除,由定时任务定期扫描并重新投递
(3)生产者Redis定时任务
生产者Redis定时任务专门扫描生产者Redis中存放了一定时间,但是状态还是未投放的消息
此消息会被认为已经投递,但是没有任何反馈结果(由于不可知因素,导致没有ReturnCallback,也没有ConfirmCallback),
此类消息被扫描到后,会放入失败的Redis,并从生产者Redis中删除,由定时任务定期扫描并重新投递
(4)还需要一个专门的定时任务扫描生产者Redis中存放了很久,仍然未消费的数据(状态为已投递),此类消息被扫描到后,会放入失败的Redis,并从生产者Redis中删除,由定时任务定期扫描并重新投递
(5)扫描失败的Redis的定时任务都遵循一条原则,一条消息最多被重新投递三次,若投递了三次仍然失败,则记录日志,记录到数据库,不会再投递,需要人工干预处理
8.2、保证消费消息不丢失
(1)消费者取到消息后,从消息中取出唯一标识,先判断此消息有没有被消费过,若已消费过,则直接ACK(避免重复消费)
(2)正常处理成功后,将生产者Redis中的此消息删除,并ACK(告诉server端此消息已成功消费)
(3)遇到异常时,捕获异常,验证自己在消息中设定的重试次数是否超过阀值,若超过,则放入死信队列,若未超过,则向将消息中的重试次数加1,抛出自定义异常,进入重试机制
(4)有专门的消费者用于处理死信队列中消费多次仍未消费成功的数据,可以记录日志,入库,人工干预处理
选型总结
对于选择Kafka还是RabbitMQ,主要考虑三个因素:吞吐量、运维能力和平台熟悉度。如果是需要流处理和高并发的日志处理,首选Kafka。但是大部分公司并没有什么高并发的处理,因此可以着重考虑运维程度和平台熟悉度,前面提到Kafka也是有策略可以设置消息可靠的。RabbiMQ运维比较直接,包括客户端EasyNetQ使用简易性,基本上就是”开箱即用“。
客户端
MQ | 客户端名称 | 文档地址 |
---|---|---|
Kafka | confluent-kafka-dotnet | https://github.com/confluentinc/confluent-kafka-dotnet/wiki |
RabbitMQ | EasyNetQ | https://github.com/EasyNetQ/EasyNetQ/wiki/Introduction |