作者:杨斜2602934873 | 来源:互联网 | 2023-06-12 19:24
什么是消息队列?(Messagequeue,简称MQ)从字面理解就是一个保存消息的一个容器。那么我们为何需要这样一个容器呢?其实就是为了解耦各个系统,我们来举个例子:有这么一个简单
什么是消息队列?(Message queue,简称MQ)
从字面理解就是一个保存消息的一个容器。那么我们为何需要这样一个容器呢?
其实就是为了解耦各个系统,我们来举个例子:
有这么一个简单的场景,系统A负责生成userID,并调用系统B、C。如果系统BC频繁变化是否需要userID参数,则系统A的代码就得不断变化,如果哪天又来了系统DEF……也需要这个参数,则系统A又要加入很多业务逻辑,这样子各他系统之间就容易产生相互影响,另外大量的系统与A发生交互也容易产生问题。
加了消息队列后,A只负责产生userID,至于谁要用这个参数,怎么用?系统A不管。对这个数据感兴趣的系统自己去取用即可,各个系统之间就实现了解耦。而且解耦后,整个服务业变成了一个异步的方式,系统A产生数据后,不用依次调用BCD来累计耗时,各系统可以同时来取用消息队列的数据进行处理,加大吞吐。
消息队列的特点
- 先进先出:消息队列的顺序在入队的时候就基本已经确定了,一般是不需人工干预的。
- 发布订阅:发布订阅是一种很高效的处理方式,如果不发生阻塞,基本可以当成是同步操作。
- 持久化:持久化确保消息队列的使用不只是一个部分场景的辅助工具,而是让消息队列能像数据库一样存储核心的数据。
- 分布式:在现在大流量、大数据的使用场景下,支持分布式的部署,才能被广泛使用。消息队列的定位就是一个高性能的中间件。
什么是中间件?
非底层操作系统软件,非业务应用软件,不是直接给最终用户使用的,不能直接给客户带来价值的软件统称为中间件。
什么是消息中间件?
关注于数据的发送和接受,利用高效可靠的异步消息传递机制集成分布式系统。
消息中间件的作用(解耦,并发,削峰,异步)
- 可以在模块、服务、接口等不同粒度上实现解耦
- 订阅/消费模式可以在数据粒度上解耦
- 可提高系统的并发能力,集中力量办大事(同步部分),碎片时间做小时(异步部分)
- 可提高系统可用性,因为缓冲了系统负载
消息中间件的弊端
- 系统可用性降低:MQ宕机之后整套系统均不能正常使用,如果要保障队列可用,需要额外机制保障(双活或容灾)
- 系统复杂性提高:存在消息重复消费、消息丢失、消息传递顺序不能保证的问题
- 降低数据一致性:多个系统消费存在部分成功部分失败的问题,数据不一致了,如要保持强一致性,需要高代价的补偿(分布式事务,对账)
重复消费:系统发了两条,两条都插入了数据库
消息丢失:系统根本没法请求到目标系统
一致性问题:系统要再ABC三个系统都执行成功之后才返回成功,结果AB成功了,C失败了
消息队列的使用场景
消息队列的使用场景有很多,最核心的有三个:解耦、异步、削峰
解耦:一个系统或者一个模块,调用了多个系统或者模块,相互之间的调用很复杂,维护起来很麻烦。此时可以考虑使用消息队列来实现多个系统之间的解耦
异步:系统A接受一个请求,需要在自己本地写库,还需要在系统BCD三个系统写库,同步操作比较费时。
削峰:高峰时段系统接收到的请求缓存到消息队列,供系统根据负载慢慢消化
如秒杀、发邮件、发短信、高并发订单等。
不适合的场景如银行转账、电信开户、第三方支付等。
关键还是要意识到队列的优劣点,然后分析场景是否使用。
MQ的6种工作模式:
- 简单模式:一个生产者,一个消费者
- work模式:一个生产者,多个消费者,每个消费者获取到的消息唯一。
- 订阅模式:一个生产者发送的消息会被多个消费者获取。
- 路由模式:发送消息到交换机并且要指定路由key ,消费者将队列绑定到交换机时需要指定路由key
- topic模式:将路由键和某模式进行匹配,此时队列需要绑定在一个模式上,“#”匹配一个词或多个词,“*”只匹配一个词。
市面上常见的消息中间件
ActiveMQ、RabbitMQ、RocketMQ、kafka
消息中间件的对比
特性 |
ActiveMQ |
RabbitMQ |
RocketMQ |
kafka |
---|
语言 |
Java |
Erlang |
Java |
Scala |
单机吞吐 |
万 |
万 |
十万,支撑高吞吐 |
十万 |
可用性 |
高(主从架构) |
高(主从架构) |
非常高(分布式架构) |
非常高(分布式架构) |
管理界面 |
一般 |
比较好 |
一般 |
一般 |
时效性 |
ms |
us微秒级 |
ms |
ms(以内) |
功能特性 |
MQ功能齐全,被大量开源项目使用。 |
基础erlang开发,所以并发能力很强,性能极其好,延时很低,管理界面很丰富,支持多语言 |
MQ功能比较完备,扩展性佳,仅适用于Java语言。 |
只支持主要的MQ功能,像一些消息查询,消息回溯等功能没有提供,毕竟是为了大数据准备的,在大数据领域应用广。 |
适用场景 |
主要场景就是解耦和异步调用,较少在大规模吞吐的场景中使用 |
数据量没有那么大,小公司 |
目前在阿里被广泛应用在订单、交易、充值、流计算、消息推送、日志流式处理、binglog分发消息等场景。 |
一般配合大数据类的系统来进行实时数据计算、日志采集等场景。 |
broker端消息过滤 |
支持 |
不支持 |
可以支持Tag标签过滤和SQL表达式过滤 |
不支持 |
消息查询 |
支持 |
根据消息id查询 |
支持Message id或Key查询 |
不支持 |
消息回溯 |
支持 |
不支持 |
支持按时间来回溯消息,精度毫秒,例如从一天之前的某时某分某秒开始重新消费消息。 |
理论上可以支持时间或offset回溯,但是得修改代码。 |
路由逻辑 |
|
基于交换机,可配置复杂路由逻辑 |
根据topic,可以配置过滤消费 |
根据topic |
持久化 |
内存、文件、数据库 |
队列基于内存,只能少量堆积 |
磁盘,大量堆积 |
磁盘,大量堆积 |
顺序消息 |
支持 |
不支持 |
支持 |
支持 |
消费并行度 |
无影响 |
无影响 |
顺序消费方式并行度同kafka完全一致;乱序方式并行度取决于消费者的线程数,如topic配置10个队列,10台机器消费,每台机器100个线程,那么并行度为1000。 |
kafka的消费并行度依赖topic配置的分区数,如分区数为10,那么最多10机器来并行消费(每台机器只能开一个线程),或者一台机器消费(10个线程并行消费)。即消费并行度和分区数一致。 |
消息可靠性 |
有较低的概率丢失数据 |
基本不丢 |
经过参数优化配置,可以做到 0 丢失 |
同 RocketMQ |
各自的优缺点:
ActiveMQ
- 采用消息推送方式,所以最适合的场景是默认消息都可在短时间内被消费。数据量越大,查找和消费消息就越慢,消息积压程度与消息速度成反比。
- 主要场景就是解耦和异步调用,较少在大规模吞吐的场景中使用。
缺点:
- 吞吐量低。由于ActiveMQ需要建立索引,导致吞吐量下降。
- 偶尔会有较低概率丢失消息;
- 不支持消息自动分片机制。这是一个功能缺失,JMS并没有规定消息中间件的集群、分片机制。ActiveMQ初衷并不是为了处理海量消息和高并发请求。如果一台服务器不能承受更多消息,则需要横向拆分。ActiveMQ官方不提供分片机制,需要自己实现。
- 一般的业务系统要引入MQ,最早大家都用ActiveMQ,但是现在确实大家用的不多了,没经过大规模吞吐量场景的验证,社区也不是很活跃,所以不推荐用这个了。
RabbitMQ
- 项目开源,社区活跃,管理界面丰富,适用于中小型公司。
- erlang开发,队列基于内存,并发高,延时低,基于AMQP协议支持多语言接入。
- 特有的交换机模式,支持很多复杂场景的消息路由。
缺点:
- 基于erlang也影响java人员的探索和改造,遇到bug调试起来比较麻烦。
- 队列只能支持主备,不可分布式扩展,单点问题是硬伤,投递消息如果不在某台虚拟机上,还需要虚拟机转发请求。
- 队列基于内存,导致无法大量堆积消息。
- 不支持顺序消息,消息消费失败后会重回队列,打乱顺序。
- 需要学习比较复杂的接口和协议,学习和维护成本较高。
RocketMQ
- 阿里双十一经受了许多考验,使用广泛,基于Java语言适合大公司自己改造。
- 对比Kafka,MQ功能较为完善,额外支持消息查询,消息回溯,消息重试,延迟消息,适合复杂业务场景。
- 吞吐量相比RabbitMQ高,可用性非常高,分布式架构,扩展性好,支持分布式事务,解耦异步调用。
- 支持10亿级别的消息堆积,不会因为堆积导致性能下降。
- 消息可靠性:经过参数优化配置,消息可以做到0丢失。
- 提供 docker 镜像用于隔离测试和云集群部署。
- 天生为金融互联网领域而生,对于可靠性要求很高的场景,尤其是电商里面的订单扣款,以及业务削峰,在大量交易涌入时,后端可能无法及时处理的情况。
- RoketMQ在稳定性上可能更值得信赖,这些业务场景在阿里双11已经经历了多次考验,如果你的业务有上述并发场景,建议可以选择RocketMQ。
缺点:
- 社区不够活跃,github上讨论不多,社区可能有黄掉的风险。(目前RocketMQ已捐给Apache)
- 支持的语言不多,只支持java及c++,其中c++不成熟
- 没有在 mq 核心中去实现JMS等接口,有些系统要迁移需要修改大量代码
kafka
- 性能卓越,单机写入TPS约在百万条/秒,最大的优点,就是吞吐量高;
- 可用性非常高,kafka是分布式的,一个数据多个副本,某个节点宕机,Kafka 集群能够正常工作;
- 持久性、可靠性: Kafka 能够允许数据的持久化存储,消息被持久化到磁盘,并支持数据备份防止数据丢失,Kafka 底层的数据存储是基于 Zookeeper 存储的,Zookeeper 我们知道它的数据能够持久存储;
- 优秀的第三方Kafka Web管理界面Kafka-Manager;
- 在日志领域比较成熟,被多家公司和多个开源项目使用;
- 主要是用来进行实时数据计算的以及日志收集,现在的项目里面很少用它做消息中间件。
kafka高吞吐率的实现:
- 顺序读写:kafka将消息读写写入到了分区partition中,而分区消息是顺序读写的。顺序读写要远快于随机读写
- 零拷贝:生产者、消费者对于kafka中消息的操作都是采用零拷贝实现的
- 批量发送:kafka允许采用批量消息发送模式
- 消息压缩:kafka允许对消息集合进行压缩
缺点:
- Kafka单机超过64个队列/分区,Load会发生明显的飚高现象,队列越多,Load越高,发送消息响应时间越长。
- 功能比较简单,主要聚集于日志场景和流计算,对业务型场景支持不够。
- 使用短轮询方式,实时性取决于轮询间隔时间;
- 消费失败不支持重试;
- 支持消息顺序,但是一台代理宕机后,就会产生消息乱序;
- 社区更新较慢。
对MQ的吞吐量与可用性,可靠性上进行比对
单机吞吐量
ActiveMQ、RabbitMQ单机吞吐量为万级;
RocketMQ、Kafka可以达到10万级的吞吐量。
topic数量对吞吐量的影响
RocketMQ和Kafka会受到topic数量的影响。
Kafka的topic从几十到几百个增加的时候,吞吐量会大幅度下降。在同等机器下,kafka尽量保证topic数量不要过多,如果需要支撑大规模的topic,需要增加更多的机器资源。
因为Kafka的每个Topic、每个分区都会对应一个物理文件。当Topic数量增加时,消息分散的落盘策略会导致磁盘IO竞争激烈成为瓶颈。
RocketMQ的topic 可以达到几百/几千的级别,吞吐量会有较小幅度的下降,这是 RocketMQ 的一大优势,在同等机器下,可以支撑大量的 topic。
RocketMQ所有的消息是保存在同一个物理文件中的,Topic和分区数对RocketMQ也只是逻辑概念上的划分,所以Topic数量的增加对RocketMQ的性能不会造成太大的影响。
可用性
ActiveMQ和RabbitMQ在高可用上采用主从架构实现的高可用。
RocketMQ和Kafka采用的分布式架构来保证高可用性。
消息可靠性
ActiveMQ有较低的概念丢失数据。
RabiitMQ基本上不会丢, 但是还是无法得到保证。
RocketMQ与Kafka经过优化配置,可以做到0丢失。
综上,有如下建议:
首先,个人不推荐使用ActiveMQ,社区不活跃,并且没有经过大规模的吞吐量场景的验证。
你永远无法想象MQ消息丢失或者宕机带来的影响有多巨大。
RabbitMQ使用Erlang开发,对JAVA工程师来说较难深入的研究和掌握,对公司而言,几乎是处于不可控的状态。 但是, RabbitMQ开源社区一直都在活跃,并且有稳定的支持。
RocketMQ是阿里的开源产品(但是,阿里自己的内部版本是商业版本,在阿里云可以直接购买服务),阿里的开源产品,如果有使用的同学应该会有较大的感触,有可能会突然黄掉!所以,如果使用RocketMQ的话,需要对自己公司的技术能力有一定的要求。
Kafka主要支持简单的MQ功能,在大数据领域的实时计算以及日志采集被大规模使用。 Kafka目前无法支持如延迟队列(需要额外的业务处理支持), 在较新的版本只支持了事务消息。 但是整体而言,支持的业务场景较少。
总体而言:
- 中小型公司,技术实力较为一般,技术挑战不是特别高,用 RabbitMQ 是不错的选择;
- 大型公司,基础架构研发实力较强,用 RocketMQ 是很好的选择。(当然,需要对RocketMQ 进行一定的改造。 如对延迟消息的控制级别)。
- 如果是大数据领域的实时计算、日志采集等场景,用 Kafka 是业内标准的,绝对没问题,社区活跃度很高,绝对不会黄,何况几乎是全世界这个领域的事实性规范。
查看更多关于消息队列选型及原理
消息队列常见问题
如何保证消息队列的高可用?
RabbitMQ基于主从的高可用,分为单机模式、普通集群模式、镜像集群模式三种
普通集群模式:多台服务器部署RabbitMQ,一个queue只会保存在一个节点上,其他节点只会同步该queue的元数据,当请求从其他节点获取该queue的数据时,该节点会再次去存储该queue的节点上拉取所需数据。这样就导致使用时要么固定使用其中一个节点,要么随机节点再需要的时候拉取数据。如果存放数据的节点宕机了,其他节点就无法拉取数据,如果开启了消息持久化让RabbitMQ落地存储消息就不一定会丢失消息,得等这个实例恢复后才能继续从这个queue拉取数据。
镜像集群模式(高可用模式):创建的queue会同步到所有实例上来实现高可用。这样会带来同步数据的开销和扩展性降低(扩展机器会导致新增的机器同步queue增加更多同步数据的开销);配置方式可通过控制台配置。
Kafka的高可用:分布式消息队列
Kafka由多个broker组成,每个broker是一个节点,创建的一个topic划分为多个partition,每个partition可放在不同的broker上,每个partition只存放一部分数据。