前言

Github:https://github.com/HealerJean

博客:http://blog.healerjean.com

1、消息队列使用场景

消息发布者:只管将消息发布到消息队列中而不需要管谁来获取。

消息使用者:只管从消息队列中取消息,而不管是谁发布的。这样发布者和使用者都不需要知道对方的存在

1.1、解耦

⬤ 一个系统(模块)调用多个系统(模块)的场景

⬤ 多个系统的添加或减少都需修改调用系统的代码

⬤ 且不需要同步调用接口的情况

举例:签到后,送积分 。从业务上来说,签到后我们一定要保证用户的签到正常,而积分的赠送我们可以延迟给用户传递,但是最终我们一定要保证签到后给积分,只是延迟而已。

某一时刻积分服务不正常。不会影响到用户签到,专业积分服务即使报错了,我们后台可以自动重试,保证积分顺利送出,而不出现异常

image-20210801161052816

1.2、流量削峰

每个系统性能都是有极限的,类似淘宝双十一,数据量猛增,这个时候就需要把用户的请求放入消息队列中,每次从队列中取出最大数据(刚好不会使系统崩溃的数据),这样请求就会堆积在消息队列中,等到闲时请求就少了,但系统人以最大吞吐去处理数据,所以堆积消息很快会被解决

image-20210801161153063

1.3、异步

涉及到时间消耗问题,当一个信息传入同时保存到多个系统,期间需要一个时间消耗,这时只需要信息先保存当前系统,剩下的放入消息队列

image-20210801161352505

## 1.3、日志记录

相信没有人认为日志是不重要的,在我们已知的日志框架中,有可能会以为写入日志时的某些故障而导致业务系统访问阻塞,请求延迟,所以我们可以构建一个日志系统,用来提供分析数据

1.4、事务一致性

举个例子,我正在开发的小米供应链金融,我们有用户的金额信息,我们也有个业务系统是账户系统,它里面也有余额信息,涉及到钱,我们就必须保证二者是一致的, 那么这个时候我们就可以使用消息队列来进行处理

2、常见问题

2.1、为什么用消息队列而不是用接口

答案:

1、解耦:一个生产者可以对应多个消费者应用服务

2、流量削峰:放入消息队列可以消息挤压,慢慢处理消息(消费者挂掉之后,可以慢慢处理)

3、异步处理:如果一个消息需要传递给多个消费者,使用消息的方式可以异步处理

2.2、kafka 高吞吐原因

2.2.1、批量发送

Kafka 允许进行批量发送消息,先将消息缓存在内存中,然后一次请求批量发送出去比如可以指定缓存的消息达到某个量的时候就发出去,或者缓存了固定的时间后就发送出去如100条消息就发送,或者每5秒发送一次这种策略将大大减少服务端的I/O次数

2.2.2、顺序读写

kafka 的消息是不断追加到文件中的,这个特性使 kafka可以充分利用磁盘的顺序读写性能顺序,读写不需要硬盘磁头的寻道时间,只需很少的扇区旋转时间,所以速度远快于随机读写

2.2.3、文件分段

1、Kafka topic中一个parition大文件分成多个小文件段,通过多个小文件段,就容易定期清除或删除已经消费完文件,减少磁盘占用。

2、通过索引文件稀疏存储,可以大幅降低index文件元数据占用空间大小。

3、通过索引信息可以快速定位 message和确定response的最大大小。

4、通过 index 元数据全部映射到 memory缓存,可以避免segment file IO磁盘操作。

2.2.4、零拷贝

Kafka 使用零复制技术向客户端发送消息——也就是说,Kafka 直接把消息从文件(或者更确切地说是 Linux 文件系统缓存)里发送到网络通道,而不需要经过任 何中间缓冲区,这是 Kafka 与其他大部分数据库系统不一样的地方,其他数据库在将数据 发送给客户端之前会先把它们保存在本地缓存里。这项技术避免了字节复制,也不需要管理内存缓冲区,从而获得更好的性能。

2.2.5、数据压缩。

Kafka 还支持对消息集合进行压缩,Producer 可以通过 GZIPSnappy 格式对消息集合进行压缩压缩的好处就是减少传输的数据量,减轻对网络传输的压力 Producer压缩之后,在 Consumer 需进行解压,虽然增加了 CPU 的工作,但在对大数据处理上,瓶颈在网络上而不是CPU,所以这个成本很值得

3、消息队列选型

3.1、主流 MQ 介绍

3.1.1、RabbitMQ

采用 Erlang 语言实现的 AMQP 协议的消息中间件,起源于金融系统,广泛应用在分布式系统中,承担消息转发的职责。RabbitMQ 发展历史比较久远,影响范围比较大,被很多开发者认可,在可靠性、可用性、可扩展性、功能性方面有着非凡表现。

3.1.2、RocketMQ

阿里开源的消息中间件,目前已经捐献给 Apache 基金会,它是由 Java 语言开发的,具备高吞吐量、高可用性、适合大规模分布式系统应用等特点。并且在阿里的双11、618等重要活动中经受住了考验。

3.1.3、Kafka

起初是由 LinkedIn 公司采用 Scala 语言开发的一个分布式、多分区、多副本且基于 zookeeper 协调的分布式消息系统,现已捐献给 Apache 基金会。它是一种高吞吐量的分布式发布订阅消息系统,以可水平扩展和高吞吐率而被广泛使用。

目前越来越多的开源分布式处理系统如 ClouderaApache StormSparkFlink 等都支持与 Kafka 集成。

特性 RabbitMQ RocketMQ kafka
开发语言 erlang java scala
支持协议 AMQP 自定义 基于 TCP 自定义
消息存储能力 内存、磁盘。支持少量堆积。 磁盘。支持大量堆积。 内存、磁盘、数据库。支持大量堆积。
消息事务性 支持(信道设置事务模式,性能有影响) 支持 支持
单机吞吐量 万级 10 万级+ 10 万级+
时效性 us ms ms 级以内
消息重复 支持at least onceat most once 支持at least once 支持at least onceat most once
消息回溯 不支持 支持指定时间点的回溯 支持指定分区 offset 位置的回溯
消息重试 不支持,但可以设置 autoACK = false,未收到确认的会重入队列 支持 不支持,但可以通过消息回溯的方式来实现
可用性 高(主从架构) 非常高(分布式架构) 非常高(分布式架构)
功能特性说明 基于 erlang开发,所以并发能力很强,性能极其好,延时很低;管理界面较丰富 MQ 功能比较完备,扩展性佳 只支持主要的 MQ功能,像一些消息查询,消息回溯等功能支持的不是很强,在大数据领域应用广。

3.1、选型建议

3.1.1、系统规模

中小型系统建议选用 RabbitMQ,数据量相对较小 。选型应首选功能比较完备的,所以 kafka 排除。RocketMQ是阿里出品,如果阿里放弃维护,中小型公司一般很难投入人力进行 RocketMQ的定制化开发,因此不推荐。

3.1.2、业务规模角度

根据具体使用规模在 RocketMQkafka 之间二选一。

⬤ 大型业务系统:有实际的业务体量需求,比如足够大规模的分布式环境,以及足够大的数据量。这时候 RocketMQkafka 都是10w+的吞吐量,都可以在考虑范围内。

⬤ 如果你有业务定制需求,可以优先选用 RocketMQ,毕竟是开源的,大的业务系统也愿意花精力去优化 JAVA 源码的。至于kafka根据业务方向选择,类似日志采集功能,首选 kafka,因为他在日志上报、监控数据采集方面有着大规模的实践经验,这也是他们主打的应用场景

具体该选哪个,看使用场景。引入MQ 之后,也会有一定的弊端,必然一定程度上降低系统可用性,增加复杂性。

3.1.3、功能性层面

功能项 Kafka(1.1.0+) RabbitMQ(3.6.10+)
优先级队列 不支持 支持:具有优先被消费的特权,建议优先级大小设置在10以内,否则价值不大
延迟队列 不支持 支持
死信队列 不支持 支持:保存无法被正确投递的消息,避免消息被无端丢弃。
重试模式 不支持 不支持:RabbitMQ 中可以参考延迟队列实现一个重试队列,需要再封装一下,也不是太难。如果要在 kafka 中实现重试队列,首先要实现延迟队列的功能,相对比较复杂。
消费模式 拉 模式 推+拉 模式
广播消费(pub/sub) 支持:kafka 对广播消费的支持比较强大 支持:能力相比较 kafka 弱一些
消息回溯 支持:kafka 可以按照 offset(偏移量)和 timestamp(时间戳) 两种维度进行消息回溯。 不支持:RabbitMQ 消息一旦被确认消费便丢弃
消息堆积 支持 支持:内存堆积过大会影响性能,如果仅考虑吞吐量因素,kafka的堆积效率比RabbitMQ 总体高很多。
持久化 支持 支持
消息追踪 不支持:消息追踪可以通过外部系统来支持,但是支持粒度肯定没有内置的细腻 支持:RabbitMQ 中可以采用 ``Firehose 或者 rabbitmq_tracing 插件实现。开启 rabbitmq_tracing 插件会大幅度影响性能,不建议在生产环境启用。使用 Firehose 与 外部 trace `系统结合的模式来提供更稳健的消息追踪能力。
消息过滤 客户端级别的支持 不支持,可以定制化封装
多租户 不支持 支持
多协议支持 只支持自定义协议,目前几个主流版本中存在兼容性问题。 RabbitMQ 本身就是 AMQP协议的实现,同时支持MQTT、STOMP等协议
跨语言支持 采用ScalaJava 编写,支持多种语言的客户端 采用 Erlang 编写,支持多种语言的客户端
流量控制 支持 clientuser 级别,可将流控配置在生产者和消费者层面 RabbitMQ的流控基于 Credit-Based 算法,是内部被动触发的保护机制,仅用于生产者层面。
消息顺序性 支持单分区(partition)级别的顺序性,在各自的分区中排序 顺序性消费的条件比较苛刻,需要单线程发送、单线程消费,这样吞吐量就下来了。而且无法使用延迟队列、优先队列等一些高级功能,所以一般不使用。
安全机制 (TLS/SSL、SASL)身份认证和(读/写)权限控制 与kafka相似
幂等性 单个生产者+单 partition + 单会话 场景下,支持幂等性 不支持
事务性消息 支持 支持

3.1.3.1、优先级队列:

可配置优先级,优先级高的消息具备优先被消费的特权,这样可以为下游服务提供不同消息级别的保证。这种模式只是在生产效率高于消费效率的时候才有效果。如果消费者的消费速度大于生产者的速度,消息中间件服务器( Broker )中没有消息堆积,就不存在对待消费数据进行优先级排序的需求了。

3.1.3.2、延迟队列:

延迟队列会存储对应的延迟消息,延迟消息是指消息被生产后,并不马上消费,而是等待一定时间后,消费者才拿到消息进行消费。

延迟队列的模式分为两种,基于消息的延迟和基于队列的延迟。

⬤ 基于消息的延迟:是指为每条消息设置不同的延迟时间,那么每当队列中有新消息进入的时候就会重新根据延迟时间排序,但是这会对性能造成很大的影响。

⬤ 基于队列的延迟:设置不同延迟级别的队列,如 15s、30s、1m、10m 等,每个队列中消息的延迟时间都是相同的,这样不需要消耗大量性能去做延迟时间排序,每个消息都有固定的投递时间。

3.1.3.3、死信队列:

由于某些原因消息无法被正确的投递,为了确保消息不会被无故的丢弃,一般会存储到一个特殊的队列中,我们称之为死信队列

与此对应的还有一个“回退队列”的概念,试想如果消费者在消费时发生了异常,那么就不会对这一次消费进行确认(Ack), 进而发生回滚消息的操作之后消息始终会放在队列的顶部,然后不断被处理和回滚,导致队列陷入死循环。为了解决这个问题,可以为每个队列设置一个回退队列,它和死信队列都是为异常的处理提供的一种机制保障。实际情况下,回退队列的角色可以由死信队列和重试队列来扮演

3.1.3.4、重试队列:

重试队列其实可以看成是一种回退队列,具体指消费端消费消息失败时,为防止消息无故丢失而重新将消息回滚到 Broker 中。与回退队列不同的是重试队列一般分成多个重试等级,每个重试等级一般也会设置重新投递延时,重试次数越多投递延时就越大。比如第一次重试延迟时间为 5s,再次消费失败后延迟重试时间为 10s,以此类推,重试越多次重新投递的时间就越久。为了避免延迟时间被无限放大,需要有个重试次数限制,超过就写入死信队列。

这边需要注意:延迟队列动作由内部触发,重试队列动作由外部消费端触发

3.1.3.5、消费模式:

消费模式分为推(push)模式和拉(pull)模式。

⬤ 推模式是指由 Broker 主动推送消息至消费端,实时性较好,不过需要保证服务端推送的消息不会严重超过消费端消化能力。

⬤ 拉模式是指消费端定时定量主动向 Broker 端请求拉取消息,虽然实时性较差,但是可以根据自身的消费能力来拉取。

3.1.3.6、广播消费:

消息一般有两种发送模式:点对点(P2PPoint-to-Point)模式和发布/订阅(Pub / Sub)模式。

点对点:消息被消费以后,队列中不会再存储,即使有多个消费者,一条消息只会被一个消费者消费。

发布订阅(Pub / Sub):定义了如何向一个内容节点发布和订阅消息,这个内容节点称为主题(topic),主题可以认为是消息传递的中介,消息发布者将消息发布到某个主题,而消息订阅者则从主题中订阅消息。主题使得消息的订阅者与消息的发布者互相保持独立,不需要进行接触即可保证消息的传递,发布 / 订阅模式在消息的一对多广播时采用。RabbitMQ 是一种典型的点对点模式,而 Kafka 是一种典型的发布订阅模式。

3.1.3.7、消息回溯:

一般消息在消费完成之后就被处理了,之后再也不能消费到该条消息。消息回溯正好相反,是指消息在消费完成之后,还能追溯到之前被消费掉的消息。

3.1.3.8、消息堆积 + 持久化:

进行流量的削峰填谷是消息中间件的一个核心功能,实现的能力主要体现在消息堆积能力上。

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

RabbitMQ 是典型的内存式堆积,可以通过一些方式持久化到磁盘中,但是会降低一些性能。

Kafka 是典型的磁盘式堆积,所有的消息都存储在磁盘中,存储容量是有了很大的提升,但是磁盘性能会比内存差很多。

3.1.3.9、消息追踪:

在消息中间件中,消息的链路追踪非常重要,它可以对生产和消费过的消息进行 trace追踪。这样,在出现故障的时候,就可以快速的定位问题。

3.1.3.10、消息过滤:

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

3.1.3.11、流量控制:

当生产者和消费者 处理速度不均衡问题,通过对生产者和消费者的限流,来保障两者的均衡。通常的流控方法有 Stop-and-wait、滑动窗口以及令牌桶等。

3.1.3.12、消息顺序性:

顺序性是指保证消息有序,特别是分布式场景下,有序的执行,是保证一致性 ( Consistency )的前提。

3.1.3.13、消息幂等性:

对于确保消息在生产者和消费者之间进行传输而言一般有三种传输保障( delivery guarantee):

At most once,至多一次,消息可能丢失,但绝不会重复传输;

At least once,至少一次,消息绝不会丢,但是可能会重复;

Exactly once,精确一次,每条消息肯定会被传输一次且仅一次。对于大多数消息中间件而言,一般只提供 At most once 和 At least once 两种传输保障,对于第三种一般很难做到,由此消息幂等性也很难保证。

3.1.3.14、事务性消息:

原子性事务中的操作为一个整体,要么都做,要么都不做。即一旦出错,就回滚事务,事务是由事务开始(Begin Transaction)和事务结束(End Transaction)之间执行的全体操作组成。Kafka RabbitMQ 都支持,不过仅仅指的是生产者发送消息是一个事务性操作,要么发送成功,要么发送失败

3.1.4、 性能层面

功能维度是消息中间件选型中的一个重要的参考维度,但性能也是考虑的一个重要环节。

吞吐量角度:Kafka 在开启幂等、事务功能的时候会使其性能降低,RabbitMQ 在开启 rabbitmq_tracing 插件的时候也会极大的影响其性能。消息中间件的性能一般是指其吞吐量,虽然从功能维度上来说,RabbitMQ 的优势要大于 Kafka,但是 Kafka 的吞吐量要比 RabbitMQ 高出 1 至 2 个数量级,一般 RabbitMQ 的单机 QPS 在万级别之内,而 Kafka 的单机 QPS 可以维持在十万级别,甚至可以达到百万级。

时延角度:另外一个是时延,作为性能维度的一个重要指标,却往往在消息中间件领域所被忽视,因为一般使用消息中间件的场景对时效性的要求并不是很高,如果要求时效性完全可以采用 RPC 的方式实现。消息中间件具备消息堆积的能力。Kafkams以内,RabbitMQus 级别的。

3.1.5、高可用角度

高可用角度是指系统的出错概率和无故障运行时长。如消息丢失,是使用消息中间件时所不得不面对的一个同点,其背后消息可靠性也是衡量消息中间件好坏的一个关键因素。尤其是在金融支付领域,消息可靠性尤为重要。然而说到可靠性必然要说到可用性,注意这两者之间的区别,消息中间件的可靠性是指对消息不丢失的保障程度;

RabbitMQ 基于主从的高可用,分为单机模式、普通集群模式、镜像集群模式三种

普通集群模式:多台服务器部署 RabbitMQ,一个 queue 只会保存在一个节点上,其他节点只会同步该 queue 的元数据,当请求从其他节点获取该 queue的数据时,该节点会再次去存储该 queue的节点上拉取所需数据。这样就导致使用时要么固定使用其中一个节点,要么随机节点再需要的时候拉取数据。如果存放数据的节点宕机了,其他节点就无法拉取数据,如果开启了消息持久化 让RabbitMQ 落地存储消息就不一定会丢失消息,得等这个实例恢复后才能继续从这个 queue 拉取数据。

镜像集群模式(高可用模式):创建的 queue 会同步到所有实例上来实现高可用。这样会带来同步数据的开销和扩展性降低(扩展机器会导致新增的机器同步 queue增加更多同步数据的开销);配置方式可通过控制台配置。

Kafka的高可用:分布式消息队列

Kafka 由多个 broker 组成,每个 broker 是一个节点,创建的一个 topic 划分为多个 partition,每个 partition 可放在不同的 broker 上,每个 partition 只存放一部分数据。

3.1.6、社区力度及生态发展

KafkaRabbitMQ 都有一系列开源的监控管理产品,社区活跃,产品生态都很不错。

3.1.7、建议

3.1.7.1、MQ 场景建议

RabbitMQ:对 JAVA 工程师来说较难深入的研究和掌握,对公司而言,几乎是处于不可控的状态。 但是, RabbitMQ 开源社区一直都在活跃,并且有稳定的支持。

RocketMQ是阿里的开源产品(但是,阿里自己的内部版本是商业版本,在阿里云可以直接购买服务),阿里的开源产品,如果有使用的同学应该会有较大的感触,有可能会突然黄掉! 所以,如果使用 RocketMQ 的话,需要对自己公司的技术能力有一定的要求。

Kafka主要支持简单的 MQ 功能,在大数据领域的实时计算以及日志采集被大规模使用。 Kafka 目前无法支持如延迟队列(需要额外的业务处理支持), 在较新的版本只支持了事务消息。 但是整体而言,支持的业务场景较少。

3.1.7.2、公司实力建议

⬤ 中小型公司,技术实力较为一般,技术挑战不是特别高,用 RabbitMQ 是不错的选择;

⬤ 大型公司,基础架构研发实力较强,用 RocketMQ 是很好的选择。(当然,需要对 RocketMQ 进行一定的改造。 如对延迟消息的控制级别)。

⬤ 如果是大数据领域的实时计算、日志采集等场景,用 Kafka 是业内标准的,绝对没问题,社区活跃度很高,绝对不会黄,何况几乎是全世界这个领域的事实性规范。

ContactAuthor