技术选型-消息队列kafka和rabbitmq的比较

本文对比了消息队列系统Kafka和RabbitMQ在架构、消息确认机制和适用场景等方面的差异。RabbitMQ适合高可靠性的消息传递,Kafka则适用于高吞吐量的流式数据处理。Kafka通过多分区实现流量分散,提高性能。两者在消息持久化、事务处理和消费者确认方面各有特点,选择需根据实际需求进行。

消息队列的目的:解决消息的分布式消费,完成项目与服务的解耦。采取异步模式完成消息队列提供者和消费者的通信,提高了系统的响应能力和信息吞吐量。提高系统的可伸缩性

rabbitmq适合对可靠性要求比较高的消息传递上,适合企业级的消息发送订阅。

kafka用于处理活跃的流式数据,适合高吞吐量。常用在日志采集,数据采集上。

Kafka针对rabbitmq的改进:把一个队列的单一master变成多个master,把一个队列的流量均匀分散在多台机器上。

1、架构

rabbitmq

  • Producer:消息生产者
  • Consumer:消息消费者
  • Exchange:消息交换机,指定消息按什么规则传递到具体哪个队列中
    • 点对点转发:根据routingkey采取点对点的信息转发,每个队列根据routingkey获取不同类的数据。
    • 广播:不论RoutingKey是什么,这条消息都会被投递到所有与此Exchange绑定的队列中。
    • 主题交换器(topic exchange):根据Binding指定的RoutingKey,Exchange对key进行模式匹配(正则表达式)后投递到相应的队列。消息可能被投入一个或多个队列。
  • Queue:消息队列,消息的载体
  • Routingkey:路由关键字,Exchange根据Routingkey来投递消息给队列
  • Binding:绑定。作用就是将Exchange和Queue按照某种路由规则绑定起来。
  • Channel:消息通道,也称信道。在客户端的每个连接里可以建立多个Channel,每个Channel代表一个会话任务
                          消费                           生产

        集群中有两个节点,每个节点上有一个broker,每个broker负责本机上队列的维护,并且borker之间可以互相通信。集群中有两个队列A和B,每个队列都分为master queue和mirror queue。在master queue所在节点挂掉之后,系统把mirror queue提升为master queue,负责处理客户端队列操作请求。注意,mirror queue只做镜像,设计目的不是为了承担客户端读写压力

         RabbitMQ集群中的任何一个节点都拥有集群上所有队列的元信息。consumer连在master queue所在节点或mirror queue所在节点没有影响,因为所有的读写操作都必须都操作在master queue上,然后由master节点同步操作到mirror queue所在的节点。因此即使consumer连接到mirror queue所在节点,该consumer的操作也会被路由到master queue所在的节点上。

        生产者同理,连接到mirror queue所在节点的生产者的操作也会被路由到master queue。

kafka

和rmq相比,kafka相当于把一个队列的单一master queue变成多个master,分布在不同节点上,不同master间数据没有交集,即 一条消息要么发送到这个master queue,要么发送到另外一个master queue。 这里面的每个master queue 在Kafka中叫做Partition,即一个分片。一个队列(topic)有多个主分片,每个主分片又有若干副分片做备份,同步机制类似于RabbitMQ。

  • Consumer:主动从Broker拉数据,从而消费这些已发布的消息
  • Producer:向broker发布消息的应用程序
  • broker(server):AMQP服务端,接收生产者发送的消息并将这些消息路由给服务器中的队列,便于kafka将生产者发送的消息,动态的添加到磁盘并给每一条消息一个偏移量,所以对于kafka一个broker就是一个应用程序的实例
  • Topic:是特定类型的消息流。消息是字节的有效负载(Payload),话题是消息的分类名或种子(Feed)名;
  • partition:一个Topic中的消息数据按照多个分区组织,分区是kafka消息队列组织的最小单位,一个分区可以看作是一个FIFO( First Input First Output,先入先出队列)。kafka分区是提高kafka性能的关键所在,当你发现你的集群性能不高时,常用手段就是增加Topic的分区,分区里面的消息是按照从新到老的顺序进行组织,消费者从队列头订阅消息,生产者从队列尾添加消息。
  • zookeeper:
    • 无论是kafka集群,还是producer和consumer都依赖于zookeeper来保证系统可用性集群保存一些meta信息
    • Kafka使用zookeeper作为其分布式协调框架,很好的将消息生产、消息存储、消息消费的过程结合在一起
    • 同时借助zookeeper,kafka能够生产者、消费者和broker在内的所以组件在无状态的情况下,建立起生产者和消费者的订阅关系,并实现生产者与消费者的负载均衡

        一个topic可以分为多个partition,这么做的好处是,每个partition(目录)相当于一个巨型文件被平均分配到多个大小相等segment(段)数据文件中,一个group中的多个consumer加入时,server会进行partition负载均衡,一个group中的consumer可以分配多个partition,也可能一个都分不到。

        producer向topic发送消息,topic分配给所属的partitions。consumer声明group-id,并监听topic,启动后会接收系统分配partitions。一个partition只能绑定同一个group中的一个consumer,但能绑定多个不同group中的consumer。

2、消息确认机制

rabbitmq

2.1持久化

2.1.1队列持久化:

Queue.DeclareOk queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete,Map<String, Object> arguments) throws IOException;
durable=true

服务把持久化的queue存放在硬盘上,服务重启后队列可以恢复

2.1.2消息持久化:

void basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body) throws IOException;

MessageProperties.PERSISTENT_TEXT_PLAIN将BasicProperties的deliveryMode设置为2表示持久化

设置了队列和消息的持久化之后,当broker服务重启的之后,消息依旧存在。单只设置队列持久化,重启之后消息会丢失;单只设置消息的持久化,重启之后队列消失,既而消息也丢失

2.1.3exchange持久化

channel.exchangeDeclare(exchangeName, “direct/topic/header/fanout”, true);即在声明的时候讲durable字段设置为true即可

消息先存入buffer(大小为1M),buffer已满后写入文件(可能在cache上,未必写入磁盘)

磁盘刷盘周期25ms,buffer和未刷入磁盘的文件均被刷到磁盘。

将queue,exchange, message等都设置了持久化之后不能保证数据不丢失,因为消息未写入磁盘时broker crush了消息会丢失,引入mirror queue减轻丢失消息的可能性

2.2事务机制 transaction(AMQP协议层面)

channel.txSelect();//将当前channel设置成transaction模式
channel.basicPublish(ConfirmConfig.exchangeName, ConfirmConfig.routingKey, MessageProperties.PERSISTENT_TEXT_PLAIN, ConfirmConfig.msg_10B.getBytes());//提交事务
channel.txCommit();//回滚事务

如果txCommit提交成功了,则消息一定到达了broker了,如果在txCommit执行之前broker异常崩溃或者由于其他原因抛出异常,这个时候我们便可以捕获异常通过txRollback回滚事务了。

采用事务机制实现会降低RabbitMQ的消息吞吐量。

2.3生产者confirm

原理:信道上的消息被指派唯一ID,一旦消息被投递到所有匹配的队列,broker发送ack给生产者producer。ack在消息被写入消费者磁盘后发出

异步confirm:提供一个回调方法,服务端confirm了一条或者多条消息后Client端会回调这个方法

Channel对象提供的ConfirmListener()回调方法只包含deliveryTag(当前Chanel发出的消息序号),我们需要自己为每一个Channel维护一个unconfirm的消息序号集合,每publish一条数据,集合中元素加1,每回调一次handleAck方法,unconfirm集合删掉相应的一条(multiple=false)或多条(multiple=true)记录。SDK中的waitForConfirms()方法通过SortedSet维护消息序号的。

2.4消费者消息确认

消费者在声明队列时,可以指定noAck参数,当noAck=false时,RabbitMQ会等待消费者显式发回ack信号后才从内存(和磁盘,如果是持久化消息的话)中移去消息。否则,RabbitMQ会在队列中消息被消费后立即删除它。

对于RabbitMQ服务器端而言,队列中的消息分成了两部分:一部分是等待投递给消费者的消息;一部分是已经投递给消费者,但是还没有收到消费者ack信号的消息。如果服务器端一直没有收到消费者的ack信号,并且消费此消息的消费者已经断开连接,则服务器端会安排该消息重新进入队列,等待投递给下一个消费者(也可能还是原来的那个消费者)。

kafka

producer

org.springframework.boot.autoconfigure.kafka.KafkaProperties
spring.kafka.producer.acks取值0,1,-1

ack=0:就是说消息只要通过网络发送出去就不会再管,无论是否被服务器接收到
ack=1(默认):producer(消息发送方)收到leader副本的确认,才会认为发送成功
ack=-1(ALL):leader副本在返回确认或错误响应之前,会等待所有follower副本都收到消息,才会认为发送成功。这个ALL很好理解,就是所有副本都收到消息,才会认为发送成功

kafka Transactions:Kafka 事务机制支持了跨分区的消息原子写功能。具体来说,Kafka 生产者在同一个事务内提交到多个分区的消息,要么同时成功,要么同时失败。这一保证在生产者运行时出现异常甚至宕机重启之后仍然成立。

事务机制的核心特征是能跨越多个分区原子地提交消息集合,甚至这些分区从属于不同的主题。同时,被提交的消息集合中的消息每条仅被提交一次,并保持它们在生产者应用中被生产的顺序写入到 Kafka 集群的消息日志中。此外,事务能够容忍生产者运行时出现异常甚至宕机重启。

实现事务机制最关键的概念就是事务的唯一标识符( TransactionalID ),Kafka 使用 TransactionalID 来关联进行中的事务。TransactionalID 由用户提供,这是因为 Kafka 作为系统本身无法独立的识别出宕机前后的两个不同的进程其实是要同一个逻辑上的事务。

对于同一个生产者应用前后进行的多个事务,TransactionalID 并不需要每次都生成一个新的。这是因为 Kafka 还实现了 ProducerID 以及 epoch 机制。这个机制在事务机制中的用途主要是用于标识不同的会话,同一个会话 ProducerID 的值相同,但有可能有多个任期。ProducerID 仅在会话切换时改变,而任期会在每次新的事物初始化时被更新。这样,同一个 TransactionalID 就能作为跨会话的多个独立事务的标识。

producer提供了

initTransactions:事务协调者会获取 TransactionalID 对应的事务元数据信息。前面提到,这些元数据信息将被写在特殊主题 __transaction_state 上

beginTransaction(开始):将本地事务状态转移到 IN_TRANSACTION 状态,在真正的提交事务中的消息之前,不会有跟 Kafka 集群的交互

sendOffsetsToTransaction(发送):对于生产者发送的消息,仍然和一般的消息生产一样采用 ProduceRequest 请求。除了会在请求中带上相应的 TransactionalID 信息和属于事务中的消息的标识符,它跟生产者生产的普通信息别无二致。如果消费者没有配置读已提交的隔离级别,那么这些消息在被 Kafka 集群接受并持久化到主题分区中时,就已经对消费者可见而且可以被消费了

commitTransaction(提交事务)

abortTransaction (丢弃)

在一个事务相关的所有消息都发送完毕之后,生产者就可以调用 commitTransaction 方法来提交整个事务了。对于事务中途发生异常的情形,也可以通过调用 abortTransaction 来丢弃整个事务。这两个操作都是将事务状态转移到终结状态,彼此之间有许多相似点。

无论是提交还是丢弃,生产者都是给事务协调者发送 EndTxnRequest 请求,请求中包含一个字段来判断是提交还是丢弃。事务协调者在收到这个请求后,首先更新事务状态到 PrepareAbort 或 PrepareCommit 并更新状态到 __transaction_state 中。

如果在状态更新成功前事务协调者宕机,那么恢复过来的事务协调者将认为事务在 Ongoing 状态中,此时生产者由于收不到确认回复,会重试 EndTxnRequest 请求,并最终更新事务到 PrepareAbort 或 PrepareCommit 状态。

随后,根据是提交还是丢弃,分别向事务涉及到的所有分区的分区首领发送事务标志( TransactionMarker )

五个事务方法

3、总结

优点rabbitmqkafka
1支持消息的持久化、负载均衡和集群,且集群易扩展可扩展。Kafka集群可以透明的扩展,增加新的服务器进集群
2由Erlang语言开发,支持大量协议:AMQP、XMPP、SMTP、STOMP高性能。Kafka性能远超过传统的ActiveMQ、RabbitMQ等,Kafka支持Batch操作
3支持消息确认机制、灵活的消息分发机制容错性。Kafka每个Partition数据会复制到几台服务器,当某个Broker失效时,Zookeeper将通知生产者和消费者从而使用其他的Broker
缺点1由于牺牲了部分性能来换取稳定性,比如消息的持久化功能,使得RabbitMQ在大吞吐量性能方面不及Kafka和ZeroMQ

1)重复消息。Kafka保证每条消息至少送达一次,虽然几率很小,但一条消息可能被送达多次。

2)消息乱序。Kafka某一个固定的Partition内部的消息是保证有序的,如果一个Topic有多个Partition,partition之间的消息送达不保证有序。

3)不能像Rabbitmq那样用topic(#,*)匹配方式传递信息,只能死板地订阅topic。且rabbitmq可以通过fanout模式实现广播。

2由于支持多种协议,使RabbitMQ非常重量级,比较适合企业级开发复杂性。Kafka需要Zookeeper的支持,Topic一般需要人工创建,部署和维护比一般MQ成本更高

 区别

队列rabbitmq支持优先级队列、延迟队列、死信队列,kafka均不支持。rabbitmq参考延迟队列可二次封装重试队列,kafka比较困难
消费模式

消费模式分为推(push)模式和拉(pull)模式。推模式是指由Broker主动推送消息至消费端,实时性较好,不过需要一定的流制机制来确保服务端推送过来的消息不会压垮消费端。而拉模式是指消费端主动向Broker端请求拉取(一般是定时或者定量)消息,实时性较推模式差,但是可以根据自身的处理能力而控制拉取的消息量。kafka:拉模式;rabbitmq:拉+推

消息传递模式RabbitMQ是一种典型的点对点模式,而Kafka是一种典型的发布订阅模式。但是RabbitMQ中可以通过设置交换器类型来实现发布订阅模式而达到广播消费的效果,Kafka中也能以点对点的形式消费
消息回溯Kafka支持按照offset和timestamp两种维度进行消息回溯。RabbitMQ不支持,其消息一旦被确认消费就会被标记删除
消息堆积+持久化

RabbitMQ是典型的内存式堆积,但这并非绝对,在某些条件触发后会有换页动作来将内存中的消息换页到磁盘(换页动作会影响吞吐),或者直接使用惰性队列来将消息直接持久化至磁盘中。Kafka是一种典型的磁盘式堆积,所有的消息都存储在磁盘中。一般来说,磁盘的容量会比内存的容量要大得多,对于磁盘式的堆积其堆积能力就是整个磁盘的大小

消息追踪消息追踪最通俗的理解就是要知道消息从哪来,存在哪里以及发往哪里去。kafka不支持,虽然可以通过外部系统来支持,但是支持粒度没有内置的细腻。rabbitMQ中可以采用Firehose或者rabbitmq_tracing插件实现。不过开启rabbitmq_tracing插件件会大幅影响性能,不建议生产环境开启,反倒是可以使用Firehose与外部链路系统结合提供高细腻度的消息追踪支持
消息过滤指按照既定的过滤规则为下游用户提供指定类别的消息。kafka可以通过客户端提供的ConsumerInterceptor接口或者Kafka Stream的filter功能进行消息过滤
多租户指多用户的环境下公用相同的系统或程序组件,并且仍可以确保各用户间数据的隔离性。RabbitMQ就能够支持多租户技术,每一个租户表示为一个vhost,其本质上是一个独立的小型RabbitMQ服务器,又有自己独立的队列、交换器及绑定关系等,并且它拥有自己独立的权限。kafka不支持
多协议支持一般消息层面的协议有AMQP、MQTT、STOMP、XMPP等。rabbitMQ本身就是AMQP协议的实现,同时支持MQTT、STOMP等协议。kafka只支持定义协议,目前几个主流版本间存在兼容性问题。
跨语言支持都支持多种语言的客户端
流量控制针对的是发送方和接收方速度不匹配的问题,提供一种速度匹配服务抑制发送速率使接收方应用程序的读取速率与之相适应。通常的流控方法有Stop-and-wait、滑动窗口以及令牌桶等。kafka支持client和user级别,通过主动设置可将流控作用于生产者或消费者;RabbitMQ的流控基于Credit-Based算法,是内部被动触发的保护机制,作用于生产者层面
消息顺序性保证消息有序。kafka支持单分区(partition)级别的顺序性。rabbitmq不支持
安全机制都具备身份认证和权限控制两种安全机制
消息幂等性Exactly once,精确一次,每条消息肯定会被传输一次且仅一次。Kafka的幂等性是指单个生产者对于单分区单会话的幂等,而事务可以保证原子性地写入到多个分区,即写入到多个分区的消息要么全部成功,要么全部回滚,这两个功能加起来可以让Kafka具备EOS(Exactly Once Semantic)的能力。rabbitmq不支持
事务性消息事务是由事务开始(Begin Transaction)和事务结束(End Transaction)之间执行的全体操作组成。Kafka和RabbitMQ都支持,不过此两者的事务是指生产者发生消息的事务,要么发送成功,要么发送失败。消息中间件可以作为用来实现分布式事务的一种手段,但其本身并不提供全局分布式事务的功能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值