消息队列学习笔记

一、简介

1、什么是消息队列

消息队列(MQ):全称为Message Queue。“消息队列”是在消息的传输过程中保存消息的容器。它是典型的:生产者、消费者模型。生产者不断向消息队列中生产消息,消费者不断的从队列中获取消息。因为消息的生产和消费都是异步的,而且只关心消息的发送和接收,没有业务逻辑的侵入,这样就实现了生产者和消费者的解耦。

2、应用场景

从目前互联网应用中使用消息队列的场景来看,主要有以下三个:

  1. 异步处理数据

  2. 系统应用解耦

  3. 业务流量削峰

下面对上述每一种场景进行简单描述。

2.1、异步处理数据

第一个例子我们以现实生活中送快递来类比,在该例子中我们把暂存快递的快递柜比作暂存数据的消息队列。我们来看一下在现实生活中,没有快递柜时,快递员把快递送到目的地后,一般需要把联系收货人来签收快递,如果收货人此时有空,那一切都很顺利。但如果收货人此时不方便(开会、正在吃午饭、外出出差)。那对于快递员而言,就很尴尬,需要一直等待(开会 or 吃午饭)或者将快递拿回去(外出出差),导致白跑一趟。这对于快递员而言简直太不友好。

从这儿可以看出,当快递员送货时,是一个同步状态,即需要等待收货人签收后才能去送下一趟单子,对快递员而言效率太低。上述例子虽然有点牵强,大家凑合理解,意思能大概理解到位就 ok。

接着我们再来看一下,当有了快递柜后,对于快递员而言,每次需要送快递时,只需要将快递投掷到快递柜,然后再通过短信或者电话通知收货人具体的快递信息即可。他就可以继续去派送下一单。而对于收获人而言,也可以根据具体方便的时间来取件。这样一来,二者完全异步了,不用相互等待了。

在这个例子中,如果把快递员比作生产者,收货人比作是消费者,则快递柜就类似于消息队列。我们可以通过采用消息队列来实现异步数据的处理。

2.2、系统应用解耦

案例二我们以目前最主流的推荐系统中内容的流转来举例。在推荐系统中当创作者发布了一条内容后,该内容会首先经过安全部分的相关审核,通过审核后的内容,通常需要进行内容入库存储、送入模型进行特征的计算和生成。

假如后期我们想提升推荐的效果,需要单独构建一份冷启动的推荐池,此时也需要用到这部分内容,那问题来了,在没有使用消息队列时,对于上游服务而言,需要通过扩展新的逻辑来实现该功能。同时在该场景里,会存在依赖三个下游服务,如果其中一个下游服务失败后,该如何处理,是重试还是返回失败等这些细节的处理。如果后期这部分数据还想在其他渠道分发,那又该如何对接。明显这种场景下面临着系统紧耦合的问题。

我们再来看一下,如果我们一开始就引入了消息队列,那问题又会变成怎样的呢?当内容审核通过后,就直接将数据生产出来丢到消息队列中,下游的多个服务再从消息队列消费数据。当后续这一份数据需要扩展供其他系统使用时,也只要通过新的消费者来接入到消息队列消费就 ok。上游生产消息的模块不要做任何的改动。这样我们就通过消息队列进行了系统应用之间的解耦。这是消息队列的第二个用途。

2.3、业务流量削峰

消息对应的第三个使用场景便是削峰。在现如今的互联网世界中,电商场景中每年的 618 秒杀活动、双 11 抢购便是最典型的案例。这种场景中系统的峰值流量往往集中于一小段时间内,平常的流量比较可控,所以为了防止系统在短时间内的峰值流量冲垮,往往采用消息队列来削弱峰值流量,高峰值期间产生的订单消息等数据首先送入到消息队列中暂存,然后供下游系统根据自己的消费能力来逐步处理。同时这类消息往往对时延的要求不是很高,比较适合采用消息队列暂存。

 二、消息队列设计思想

1、消息队列模型

上图是几乎所有消息队列设计的一个核心模型。对于一个消息队列而言,从数据流向的维度,可以拆解为三大部分:生产者消息队列集群消费者,数据是从生产者流向消息队列集群,最终再从消息队列集群流向消费者,下面对这几个概念进行一一阐述。

1.1、生产者:

生产数据的服务,通常也称为数据的输入提供方,这里的数据通常指我们的业务数据,例如推荐场景中用户对内容的点击数据、内容曝光数据、电商中的订单数据等等。

生产者通常是作为客户端的方式存在,但在支持事务消息的消息队列中,生产者也被设计为服务端,实现事务消息这一特性。其次生产者通常会有多个,消息队列集群内部也会有多个分区队列,所以在生产者发送数据时,通常会存在负载均衡的一些策略,常见的有按 key hash轮询随机等方式。其本质是一条数据,被消息队列封装后也被称为一条消息,该条消息只能发送到其消息队列集群内部的一个分区队列中。因此只需按照一定的策略从多个队列中选择一个队列即可。

1.2、消息队列集群

消息队列集群是消息队列这种组件实现中的核心中的核心,它的主要功能是存储消息过滤消息分发消息

其中存储消息主要指生产者生产的数据需要存储到消息队列内部;存储消息可以说是消息队列的核心,一个消息队列吞吐量的高低、性能优劣都和它的存储模型脱不开关系。

过滤消息只指消息队列可以通过一定的规则或者策略进行消息的过滤,该项能力通常也被称为消息路由;过滤消息属于高阶的特性功能,AMQP 协议对这些能力抽象的比较完备,部分消息队列可以选择性的实现该协议来达到该功能,关于 AMQP 协议内容读者可以自行搜索资料阅读,此处不再展开。

分发消息是指消息队列通常需要将消息分发给处理同一逻辑的多个消费者处理或者处理不同逻辑的不同消费者处理。分发消息可以说和消费者模型想挂钩,这块会涉及到不同的数据获取方式,也会涉及到消费者消费消息的模型。

此外绝大部分的消息队列也都支持对消息进行分类,分类的标签称为topic(主题),一个 topic 中存放的是同一类消息。

1.3、消费者

最终消息队列存储的消息会被消费者消费使用,消费者也可以看做消息队列中数据的输出方。消费者通常有两种方式从消息队列中获取数据:推送(push)数据拉取(pull)数据,其次消费者也经常是作为客户端的角色出现在在消息队列这种组件中。

2、消息队列数据组织方式

在这一节中,我们详细看看消息队列存储消息这个环节的一些权衡考量,通常数据的存储无外乎就是两种,一种是存储在非易失性存储中,例如磁盘这种介质;另一种是选择存储在易失性存储中,典型的就是内存。

通常在大部分组件设计时,往往会选择一种主要介质来存储、另一种介质作为辅助使用。就拿 redis 来说,它主要采用内存存储数据,磁盘用来做辅助的持久化。拿 RabbitMQ 举例,它也是主要采用内存存储消息,但也支持将消息持久化到磁盘。而 RocketMQ、Kafka、Pulsar 这种,则是数据主要存储在磁盘,通过内存来主力提升系统的性能。关系型数据库例如 mysql 这种组件也是主要采用磁盘组织数据,合理利用内存提升性能。

针对采用内存存储数据的方案而言,难点一方面在于如何在不降低访问效率的情况下,充分利用有限的内存空间来存储尽可能多的数据,这个过程中少不了对数据结构的选型、优化;另一方面在于如何保证数据尽可能少的丢失,我们可以看到针对此问题的解决方案通常是快照+广泛意义的 wal 文件来解决。此类典型的代表就是 redis 啦。

针对采用磁盘存储数据的方案而言,难点一方面在于如何根据系统所要解决的特点场景进行合理的对磁盘布局。读多写少情况下采用 b+树方式存储数据;写多读少情况下采用 lsm tree 这类方案处理。另一方面在于如何尽可能减少对磁盘的频繁访问,一些做法是采用 mmap 进行内存映射,提升读性能;还有一些则是采用缓存机制缓存频繁访问的数据。还有一些则是采用巧妙的数据结构布局,充分利用磁盘预读特性保证系统性能。

总的来说,针对写磁盘的优化,要不采用顺序写提升性能、要不采用异步写磁盘提升性能(异步写磁盘时需要结合 wal 保证数据的持久化,事实上 wal 也主要采用顺序写的特性);针对读磁盘的优化,一方面是缓存、另一方面是 mmap 内存映射加速读

上述这些存储方案上权衡的选择在 kafka、RocketMQ、Pulsar 中都可以看到。其实抛开消息队列而言,这些存储方案的选择上无论是关系型数据库还是 kv 型组件都是通用的。

下图列举了几种磁盘上的数据组织方式,仅供大家参考。

图片

3、获取数据地两种方式对比

消费者在从消息队列中获取数据时,主要有两种方案: 1. 等待推送数据 2. 主动拉取数据

图片

在此处,个人想抛开消息队列谈一点关于这两种方案的理解,其实推拉模型不仅仅只用于消息队列这种组件中,更一般意义上,它解决的其实是数据传送双方的一个问题。本质是数据需要从一方流向另一方。顺着这个思路来看,下面这三个例子都是遵循这个原则。

网络中传输的数据: 在 IO 多路复用中,以 epoll 为例,当内核检测到监听的描述符有数据到来时,epoll_wait()阻塞会返回,上层用户态程序就知道有数据就绪了,然后可以通过 read()系统调用函数读取数据。这个过程就绪通知,类似于推送,但推送的不是数据,而是数据就绪的信号。具体的数据获取方式还是需要通过拉取的方式来主动读。

feeds 流系统用户时间线后台实现方案(读扩散、写扩散): 读扩散和写扩散更是这样一个 case。对于读扩散而言,主要采用拉取的方式获取数据。而对于写扩散而言,它是典型的数据推送的方式。当然在系统实现中,更复杂的场景往往会选择读写结合的思路来实现。

生活中的点外卖例子: 当下单点外卖时,通常也会有两种方式可以选择,外卖派送到店自取。不过通常外卖派送比较实时,我们通常就选择这种方式了而已。可以看出外卖派送其实就是一种推的方式,而到店自取,则是拉的方式。

4、消费模型

下图中上半部分展示了最简单的一种消费模型。一个生产者、一个消费者。但往往我们的一份数据通常会被不同场景所使用。那这个时候,首先就会存在每种场景需要使用全量的数据、而且不同场景之间不会相关影响,彼此独立。方便理解起见,我们假设有 N 个场景需要使用这同一份数据,每个场景需要消费全量的数据。而在 N 个场景中的一种场景里,又会有多个消费者一起分摊消费这些数据。我们假设一个场景里有 M 个消费者。由于每个场景中包含 M 个消费者,我们也将其采用消费者组来描述。通过上面的介绍,我们可以用下面一句话总结消息队列中的消费模型:

消费者消费者模型其实是一个 1:N:M 的关系,一份数据被 N 个消费者组独立使用,每个消费者组中有 M 个消费者进行分摊消费

其实这种模型也称为发布订阅模型,对于一条消息而言,组间广播、组内单播。一条消息只能被一个消费者组中的一个消费者使用。在消费者组内部也存在一些负载均衡的策略。常用的有:轮询随机hash一致性 hash等方案。

图片

二、主流消息队列

1、AMQP和JMS

MQ是消息通信的模型,并发具体实现。现在实现MQ的有两种主流方式:AMQP、JMS。

两者间的区别和联系:

  • JMS是定义了统一的接口,来对消息操作进行统一;AMQP是通过规定协议来统一数据交互的格式
  • JMS限定了必须使用Java语言;AMQP只是协议,不规定实现方式,因此是跨语言的。
  • JMS规定了两种消息模型;而AMQP的消息模型更加丰富

2、常见MQ产品

目前市场上的主流MQ主要有以下几个:pulsar、kafka、rabbitmq、rocketmq、activemq,对于不同Mq的选型,我们主要做一些分析。

2.1、ActiveMQ

(基于JMS)ActiveMQ 由 Apache 软件基金会基于 Java 语言开发的一个开源的消息代理。能够支持多个客户机或服务器。计算机集群等属性支持 ActiveMQ 来管理通信系统。

2.2、RabbitMQ

(基于AMQP),RabbitMQ 是实现了高级消息队列协议(AMQP)的开源消息代理软件(亦称面向消息的中间件)。RabbitMQ 服务器是用 Erlang 语言编写的,而集群和故障转移是构建在开放电信平台框架上的。所有主要的编程语言均有与代理接口通讯的客户端库。RabbitMQ 支持多种消息传递协议、传递确认等特性。

  • Producer: 数据的发送方,一个Message有两个部分:payload(有效载荷)和label(标签)。payload顾名思义就是传输的数据。label是exchange的名字或者说是一个tag,它描述了payload,而且RabbitMQ也是通过这个label来决定把这个Message发给哪个Consumer。AMQP仅仅描述了label,而RabbitMQ决定了如何使用这个label的规则。
  • Consumer: 数据的接收方。把queue比作是一个有名字的邮箱。当有Message到达某个邮箱后,RabbitMQ把它发送给它的某个订阅者即Consumer。当然可能会把同一个Message发送给很多的Consumer。在这个Message中,只有payload,label已经被删掉了。对于Consumer来说,它是不知道谁发送的这个信息的,就是协议本身不支持。当然了,如果Producer发送的payload包含了Producer的信息就另当别论了。
  • Queue: 消息队列,提供了FIFO的处理机制,具有缓存消息的能力。rabbitmq中,队列消息可以设置为持久化,临时或者自动删除。
  • 设置为持久化的队列,queue中的消息会在server本地硬盘存储一份,防止系统crash,数据丢失设置为临时队列,queue中的数据在系统重启之后就会丢失
  • 设置为自动删除的队列,当不存在用户连接到server,队列中的数据会被自动删除
  • Exchange: Exchange类似于数据通信网络中的交换机,提供消息路由策略。rabbitmq中,producer不是通过信道直接将消息发送给queue,而是先发送给Exchange。一个Exchange可以和多个Queue进行绑定,producer在传递消息的时候,会传递一个ROUTING_KEY,Exchange会根据这个ROUTING_KEY按照特定的路由算法,将消息路由给指定的queue。和Queue一样,Exchange也可设置为持久化,临时或者自动删除。
  • Binding: 绑定,它的作用就是把exchange和queue按照路由规则绑定起来
  • Routing Key: 路由关键字,exchange根据这个关键字进行消息投递
  • VHost: 虚拟主机,一个broker里可以开设多个vhost,用作不同用户的权限分离
  • Channel: 消息通道,在客户端的每个连接里,可建立多个channel,每个channel代表一个会话任务

2.3、Kafka

Apache Kafka 是由 Apache 软件基金会开发的一个开源消息系统项目,由 Scala 写成。Kafka 最初是由 LinkedIn 开发,并于 2011 年初开源。2012 年 10 月从 Apache Incubator 毕业。该项目的目标是为处理实时数据提供一个统一、高通量、低等待的平台。Kafka 是一个分布式的、分区的、多复本的日志提交服务。它通过一种独一无二的设计提供了一个消息系统的功能。

  • Producer: 生产者,向Kafka集群(Broker)中发送消息
  • Consumer: 消费者,从Kafka集群中(Broker)消费消息
  • Zookeeper: Zookeeper集群,用来管理kafka集群,主要是保存broker、consumer的注册消息、broker leader的选举等
  • Broker: 已发布的消息保存在一组服务器中,称之为Kafka集群。集群中的每一个服务器都是一个代理(Broker),是一个物理上的概念。消费者可以订阅一个或多个主题(topic),并从Broker拉数据,从而消费这些已发布的消息。
  • Topic : 每一个消息都属于一个Topic,是逻辑概念,每个Topic都有一个多个分区(Parition,物理概念)

2.4、RocketMQ 

(基于JMS)Apache RocketMQ 是一个分布式消息和流媒体平台,具有低延迟、强一致、高性能和可靠性、万亿级容量和灵活的可扩展性。它有借鉴 Kafka 的设计思想,但不是 kafka 的拷贝。

  • Producer:消息生产者,生产者的作用就是将消息发送到 MQ,生产者本身既可以产生消息,如读取文本信息等。也可以对外提供接口,由外部应用来调用接口,再由生产者将收到的消息发送到 MQ。
  • Consumer:消息消费者,简单来说,消费 MQ 上的消息的应用程序就是消费者,至于消息是否进行逻辑处理,还是直接存储到数据库等取决于业务需要。
  • NameServer:服务:提供了轻量级的服务发现和路由。每个NameServer服务记录完整的路由信息,提供一致的读写服务,支持快速存储扩展。
  • Broker:通过提供轻量级主题和队列机制来处理消息存储。它们支持Push和Pull模型,包含容错机制(2个副本或3个副本),提供了极强的峰值处理里能力和按照时间顺序存储数以百万记的消息存储能力,此外,代理提供了灾难恢复、丰富的度量统计和警报机制,这些都是在传统的消息传递系统中缺乏的
  • CommitLog:消息存储持久化咋commitLog中,一个broker只有一个commitLog,采用append的方式写入消息,为顺序写
  • ConsumerQueue:消费者队列,并没有全量消息,存储的是在CommitLog中的偏移量延迟队列

2.5、Pulsar

Apache Pulsar 是 Apache 软件基金会顶级项目,是下一代云原生分布式消息流平台,集消息、存储、轻量化函数式计算为一体,采用计算与存储分离架构设计,支持多租户、持久化存储、多机房跨区域数据复制,具有强一致性、高吞吐、低延时及高可扩展性等流数据存储特性,被看作是云原生时代实时消息流传输、存储和计算最佳解决方案。

3、各MQ之间的区别

3.1、 优先级队列

优先级高的消息具备优先被消费的特权,这样可以为下游提供不同消息级别的保证。不过这个优先级也是需要有一个前提的:如果消费者的消费速度大于生产者的速度,并且消息中间件服务器(一般简单的称之为Broker)中没有消息堆积,那么对于发送的消息设置优先级也就没有什么实质性的意义了,因为生产者刚发送完一条消息就被消费者消费了,那么就相当于Broker中至多只有一条消息,对于单条消息来说优先级是没有什么意义的。

  • Kafka:不支持优先级队列
  • RocketMQ:支持相对意义上的优先级队列,RocketMQ是通过建立不同的队列,每个队列有不同的优先级,当producer根据message的消息优先级发送到对于的队列
  • RabbitMQ:支持优先级队列,实现原理同上

3.2、 延迟队列

延迟队列存储的是对应的延迟消息,所谓“延迟消息”是指当消息被发送以后,并不想让消费者立刻拿到消息,而是等待特定时间后,消费者才能拿到这个消息进行消费。延迟队列一般分为两种:

基于消息的延迟和基于队列的延迟:基于消息的延迟是指为每条消息设置不同的延迟时间,那么每当队列中有新消息进入的时候就会重新根据延迟时间排序,当然这也会对性能造成极大的影响。
基于队列的延迟:设置不同延迟级别的队列,比如 5s、10s、30s、1min、5mins、10mins 等,每个队列中消息的延迟时间都是相同的,这样免去了延迟排序所要承受的性能之苦,通过一定的扫描策略(比如定时)即可投递超时的消息。

  • Kafka:不支持延迟队列
  • RabbitMQ和RocketMQ:采用的基于队列的延迟

3.3、 死信队列

由于某些原因消息无法被正确的投递,为了确保消息不会被无故的丢弃,一般将其置于一个特殊角色的队列,这个队列一般称之为死信队列。

Kafka没有死信队列,RocketMQ和RabbitMQ支持死信队列。

3.4、 重试队列

是一种回退队列,指消费端消费失败时,为防止消息无故丢失而将消息回滚到brocker中。 重试队列一般分多个等级,每个重试等级也会设置重新投递延时,重新投递次数越多,延时越大。 为此需要设置一个上限,超过投递次数就加入死信队列。

Kakfa不支持重试队列,RocketMQ和RabbitMQ支持重试队列。

3.5、消费模式

消费模式分为推(push)模式和拉(pull)模式。push模式是broker端推送消息到consumer端,实时性高,但是需要进行流量控制以防止consumer端被压垮;pull模式是broker端去broker拉取消息,实时性较推模式差,但是可以根据自身的处理能力而控制拉取的消息量。

  • Kafka:kafka是pull模式。
  • RocketMQ:Rocketmq消费分为push和pull两种方式,push为被动消费类型,pull为主动消费类型,push方式最终还是会从broker中pull消息。不同于pull的是,push首先要注册消费监听器,当监听器处触发后才开始消费消息,所以被称为“被动”消费。
  • RabbitMQ:支持pull模式和push模式。

3.6、广播模式

消息一般有两种传递模式:点对点(P2P,Point-to-Point)模式和发布 / 订阅(Pub/Sub)模式。对于点对点的模式而言,消息被消费以后,队列中不会再存储,所以消息消费者不可能消费到已经被消费的消息。虽然队列可以支持多个消费者,但是一条消息只会被一个消费者消费;发布订阅模式定义了如何向一个内容节点发布和订阅消息,这个内容节点称为主题(topic),主题可以认为是消息传递的中介,消息发布者将消息发布到某个主题,而消息订阅者则从主题中订阅消息。主题使得消息的订阅者与消息的发布者互相保持独立,不需要进行接触即可保证消息的传递,发布 / 订阅模式在消息的一对多广播时采用。

  • RabbitMQ 是一种典型的点对点模式
  •  Kafka和RocketMQ 是一种典型的发布订阅模式。但是 RabbitMQ 中可以通过设置交换器类型来实现发布订阅模式而达到广播消费的效果

Kafka 中也能以点对点的形式消费,你完全可以把其消费组(consumer group)的概念看成是队列的概念。不过对比来说,Kafka 中因为有了消息回溯功能的存在,对于广播消费的力度支持比 RabbitMQ 的要强。

3.7、消息回溯

一般消息在消费完成之后就被处理了,之后再也不能消费到该条消息。消息回溯正好相反,是指消息在消费完成之后,还能消费到之前被消费掉的消息。对于消息而言,经常面临的问题是“消息丢失”,至于是真正由于消息中间件的缺陷丢失还是由于使用方的误用而丢失一般很难追查,如果消息中间件本身具备消息回溯功能的话,可以通过回溯消费复现“丢失的”消息进而查出问题的源头之所在。消息回溯的作用远不止与此,比如还有索引恢复、本地缓存重建,有些业务补偿方案也可以采用回溯的方式来实现。

  • kafka支持消息回溯,可以按照offset和timestamp两种维度进行消息回溯。
  • RocketMQ支持按照时间来回溯消息,精度毫秒
  • RabbitMQ不支持消息回溯,RabbitMQ中消息一旦被确认消费就会被标记删除。

3.8、消息堆积+持久化

流量削峰是消息中间件的一个非常重要的功能,而这个功能其实得益于其消息堆积能力。从某种意义上来讲,如果一个消息中间件不具备消息堆积的能力,那么就不能把它看做是一个合格的消息中间件。

消息堆积分内存式堆积磁盘式堆积

RabbitMQ :是典型的内存式堆积,但这并非绝对,在某些条件触发后会有换页动作来将内存中的消息换页到磁盘(换页动作会影响吞吐),或者直接使用惰性队列来将消息直接持久化至磁盘中。

Kafka :是一种典型的磁盘式堆积,所有的消息都存储在磁盘中。一般来说,磁盘的容量会比内存的容量要大得多,对于磁盘式的堆积其堆积能力就是整个磁盘的大小。从另外一个角度讲,消息堆积也为消息中间件提供了冗余存储的功能。

如下图所示,Kafka中每个partition对应一个或者多个segment file,每个segment有一个index file(索引文件)和time index file(时间索引文件,需要先到index中查询得到索引,再去segment中获取消息),当前只能有一个segment是活跃的,消息以追加的方式写入segment。Kafka的这种存储方式,对于每个文件来说是顺序读写,但是当并发读写多个partition的时候,对于多个文件的读写,在文件系统的层面上还是随机读写,所以当topic或者partition的数目过多时,kafka性能急剧下降。


RocketMQ:如下图所示,RocketMQ中存储有CommitLog,MappedFileQueue和MappedFile,ConsumerQueue的概念。

​ 其中CommitLog : MappedFileQueue:MappedFile = 1:1:N;

​ ConsumerQueue: MappedFileQueue:MappedFile = 1:1:N;

一台机器上的所有Topic的所有queue的消息都存放到一个commitLog中,然后由后台异步线程同步到consumerqueue中,再由consumer进行消费。这里commitLog中存放有真正的消息,是物理地址,在consumerqueue中存储的是消息在commitLog中的offset,是一个逻辑地址。这样对于consumeeQueue是可以完全的进行顺序读写的,但是对于commitLog虽然是顺序写,但是是随机读的。commitLog利用mappedFileQueue和mappedFie来解决随机读问题,先定位到mappedFileQueue中的mappedFile,再在mappedFileQueue中进行读写。

RabbitMQ:RabbitMQ持久化分为Exchange、Queue、Message的持久化,具体还待研究。

3.9、消息过滤

消息过滤是指按照既定的过滤规则为下游用户提供指定类别的消息。就以 kafka 而言,完全可以将不同类别的消息发送至不同的 topic 中,由此可以实现某种意义的消息过滤,或者 Kafka 还可以根据分区对同一个 topic 中的消息进行分类。不过更加严格意义上的消息过滤应该是对既定的消息采取一定的方式按照一定的过滤规则进行过滤。

  • Kakfa:不支持Broker端的过滤,但是可以通过客户端提供的 ConsumerInterceptor 接口或者 Kafka Stream 的 filter 功能进行消息过滤。
  • RocketMQ:支持两种Broker端消息过滤方式。根据Message Tag来过滤,相当于子topic概念,向服务器上传一段Java代码,可以对消息做任意形式的过滤,甚至可以做Message Body的过滤拆分。
  • RabbitMQ:不支持消息过滤。但是可以通过简单的二次封装来达到消息过滤的效果。

3.10、流量控制

流量控制(flow control)针对的是发送方和接收方速度不匹配的问题,提供一种速度匹配服务抑制发送速率使接收方应用程序的读取速率与之相适应。通常的流控方法有 Stop-and-wait、滑动窗口以及令牌桶等。

  • Kafka:支持client和user级别,通过主动设置可将流控作用于生产者或消费者 。
  • RocketMQ:支持多种维度的流量控制。
  • RabbitMQ:流量控制基于credit-base算法,是内部被动触发的保护机制,作用于生产者层面 

3.11、顺序性消息

顾名思义,消息顺序性是指保证消息有序。这个功能有个很常见的应用场景就是 CDC(Change Data Chapture),以 MySQL 为例,如果其传输的 binlog 的顺序出错,比如原本是先对一条数据加 1,然后再乘以 2,发送错序之后就变成了先乘以 2 后加 1 了,造成了数据不一致。这里讲一下RocketMQ顺序消息的实现原理:
(1)Consumer 在严格顺序消费时,通过 三 把锁保证严格顺序消费。
(2)Broker 消息队列锁(分布式锁) :

集群模式下,Consumer 从 Broker 获得该锁后,才能进行消息拉取、消费。
广播模式下,Consumer 无需该锁。
(3)Consumer 消息队列锁(本地锁) :Consumer 获得该锁才能操作消息队列。
(4)Consumer 消息处理队列消费锁(本地锁) :Consumer 获得该锁才能消费消息队列。

3.12、幂等性

对于确保消息在生产者和消费者之间进行传输而言一般有三种传输保障(delivery guarantee):At most once,至多一次,消息可能丢失,但绝不会重复传输;At least once,至少一次,消息绝不会丢,但是可能会重复;Exactly once,精确一次,每条消息肯定会被传输一次且仅一次。对于大多数消息中间件而言,一般只提供 At most once 和 At least once 两种传输保障,对于第三种一般很难做到,由此消息幂等性也很难保证。

  • Kafka :自 0.11 版本开始引入了幂等性和事务,Kafka 的幂等性是指单个生产者对于单分区单会话的幂等,而事务可以保证原子性地写入到多个分区,即写入到多个分区的消息要么全部成功,要么全部回滚,这两个功能加起来可以让 Kafka 具备 EOS(Exactly Once Semantic)的能力。不过如果要考虑全局的幂等,还需要与从上下游方面综合考虑,即关联业务层面,幂等处理本身也是业务层面所需要考虑的重要议题。以下游消费者层面为例,有可能消费者消费完一条消息之后没有来得及确认消息就发生异常,等到恢复之后又得重新消费原来消费过的那条消息,那么这种类型的消息幂等是无法有消息中间件层面来保证的。如果要保证全局的幂等,需要引入更多的外部资源来保证,比如以订单号作为唯一性标识,并且在下游设置一个去重表。
  • RocketMQ:RocketMQ不保证消息不重复,如果你的业务需要保证严格的不重复消息,需要你自己在业务端去重。
  • RabbitMQ:不支持消息幂等。

3.13、性能

RabbitMQ :单机 QPS 在万级别之内

RocketMQ:单机写入TPS单实例约7万条/秒

 Kafka :单机 QPS 可以维持在十万级别,甚至可以达到百万级。

3.14、定时消息

  • Kafka不支持定时消息
  • RabbitMQ、RocketMQ支持定时消息

3.15、负载均衡

负载均衡(Load balance)是一种计算机网络技术,用于在多个计算机(计算机集群)、网络连接、CPU、磁盘驱动器或其他资源中分配负载,以达到最佳资源使用、最大化吞吐率、最小响应时间以及避免过载的目的。使用带有负载均衡的多个服务器组件,取代单一的组件,可以通过冗余提高可靠性。

负载均衡通常分为软件负载均衡和硬件负载均衡两种,Kafka、RocketMQ、RabbitMQ都是软负载均衡,这里主要讲一下它们的消费者负载均衡。

Kafka和RocketMQ的消费者负载均衡都是在consumer端做的,也都是将consumer和topic、partition/queue的订阅关系放在broker中的。至于为什么要将消费者负载均衡放在客户端做,其中一个重要原因是为了灵活性:如果让server分配,一旦需要新的分配策略,server集群要重新部署,这对于已经上线运行的集群来说,代价很大;而让client分配,server集群就不需要重新部署了。

Kafka:首先每个消费者组都有指定一个broker为coordinator,消费者通过向被指派的群组协调器(coordinator)的broker发送心跳来维持他们和群组的从属关系以及他们对分区的从属关系。当消费者要加入消费者group时,它会向coordinator发送一个JoinGroup请求。第一个加入group的被称为“群主”。群主从coordinator那里获得group的成员列表,并负责给每一个消费分配分区。分配完毕之后群主把分配情况列表发送给coordinator,coordinator再把这些信息发送给所有消费者。每个消费者只能看到自己的分配信息,只有群主知道group里所有消费者的分配信息。这个过程会在每次再均衡时重复发生。

RocketMQ:RocketMQ的消费者负载均衡省去了coordinator,而是所有的consumer都能得到consumer的订阅表,每个consumer自己做负载均衡 。

RabbitMQ:RabbitMQ如果有多个消费者同时订阅同一个Queue中的消息,Queue中的消息会被平摊给多个消费者。但是如果每个消息的处理时间不同,就有可能会导致某些消费者一直在忙,而另外一些消费者很快就处理完手头工作并一直空闲的情况。我们可以通过设置Prefetch count来限制Queue每次发送给每个消费者的消息数,比如我们设置prefetchCount=1,则Queue每次给每个消费者发送一条消息;消费者处理完这条消息后Queue会再给该消费者发送一条消息。

3.16、高可用和容错

消息可靠性也是衡量消息中间件好坏的一个关键因素。尤其是在金融支付领域,消息可靠性尤为重要。

Kafka:

Kafka中Broker是一个物理上的概念,一台物理机就对应一个Broker,一个Broker可以同时是 Leader和Follower。zookeeper维护每个partition的副本之间的关系,并维护一个isr集群,当partition的leader挂掉之后,从isr中随机选取一个作为leader;当isr全挂掉时,可以有两种方式:

(1)等待leader复活,但是会有一段时间不可用

(2)从剩下的副本中选取一个最接近leader的副本作为leader,但是可能会造成数据丢失,影响可靠性。


 

RocketMQ

RocketMQ中Broker是一个逻辑上的概念,同时包含一个Master和多个Slave,一台机器要么只能是Master,要么只能是Slave。这里的queue和Kafka中的partition含义一致。Master和Slave是通过Broker Name进行配对的具有相同Broker Name的Master和Slave属于同一个Broker.

RabbitMQ

RabbitMQ可以通过三种方法来部署分布式集群系统来实现高可用,分别是:cluster(集群),federation(联盟),shovel

此部分转载于博文:Kafka、RocketMQ、RabbitMQ的比较总结_kafka rabbitmq rocketmq-CSDN博客

  • 18
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值