消息队列
什么是消息队列?
在计算机科学中,消息队列(英语:Message queue)是一种进程间通信或同一进程的不同线程间的通信方式,软件的贮列用来处理一系列的输入,通常是来自使用者。消息队列提供了异步的通信协议,每一个贮列中的纪录包含详细说明的资料,包含发生的时间,输入装置的种类,以及特定的输入参数,也就是说:消息的发送者和接收者不需要同时与消息队列互交。消息会保存在队列中,直到接收者取回它。
消息队列是一个存放消息的容器,类似于数据结构中的队列,具有先进先出,双端操作的特性。消息队列是分布式系统中重要的组件,使用消息队列主要是为了通过异步处理提高系统性能和削峰、降低系统耦合性。
消息队列模型:
- 点对点:消息生产者向消息队列中发送了一个消息后,只能被一个消费者消费一次
- 发布/订阅:消息生产者向频道发送一个消息后,多个消费者可以从该频道订阅到这条消息并消费
发布/订阅模型与观察者模式的区别
- 观察者模式中,观察者和主题都知道对方的存在;而在发布与订阅模式中,生产者与消费者不知道对方的存在,它们之间通过频道进行通信
- 观察者模式是同步的,当事件触发时,主题会调用观察者的方法,然后等待方法返回;而发布与订阅模式是异步的,生产者向频道发送一个消息之后,就不需要关心消费者何时去订阅这个消息,可以立即返回
为什么着眼于异步性
在不使用消息队列时,用户的请求数据直接写入数据库,在高并发的情况下数据库压力剧增,使得响应速度变慢。但是在使用消息队列之后,用户的请求数据发送给消息队列之后立即返回,再由消息队列的消费者进程从消息队列中获取数据,异步写入数据库。由于消息队列服务器处理速度快于数据库(消息队列也比数据库有更好的伸缩性),因此响应速度得到大幅改善。
消息队列具有很好的削峰作用的功能——即通过异步处理,将短时间高并发产生的事务消息存储在消息队列中,从而削平高峰期的并发事务。举例:在电子商务一些秒杀、促销活动中,合理使用消息队列可以有效抵御促销活动刚开始大量订单涌入对系统的冲击。但是,用户请求数据写入消息队列之后就立即返回给用户了,但是请求数据在后续的业务校验、写数据库等操作中可能失败。因此使用消息队列进行异步处理之后,需要适当修改业务流程进行配合,比如用户在提交订单之后,订单数据写入消息队列,不能立即返回用户订单提交成功,需要在消息队列的订单消费者进程真正处理完该订单之后,甚至出库后,再通过电子邮件或短信通知用户订单成功,以免交易纠纷。
消息队列如何降低系统间的耦合性
如果模块之间不存在直接调用,那么新增模块或者修改模块就对其他模块影响较小,这样系统的可扩展性更好。在大型网站中通常用利用消息队列实现事件驱动结构。消息队列使利用发布/订阅模式工作,消息发送者(生产者)发布消息,一个或多个消息接受者(消费者)订阅消息。消息发送者(生产者)和消息接受者(消费者)之间没有直接耦合,消息发送者将消息发送至分布式消息队列即结束对消息的处理,消息接受者从分布式消息队列获取该消息后进行后续处理,并不需要知道该消息从何而来。对新增业务,只要对该类消息感兴趣,即可订阅该消息,对原有系统和业务没有任何影响,从而实现网站业务的可扩展性设计。另外为了避免消息队列服务器宕机造成消息丢失,会将成功发送到消息队列的消息存储在消息生产者服务器上,等消息真正被消费者服务器处理后才删除消息。在消息队列服务器宕机后,生产者服务器会选择分布式消息队列服务器集群中的其他服务器发布消息。
消息队列会带来的负面影响
- 系统可用性降低:需要着力设计消息丢失或 MQ 挂掉的处理策略
- 系统复杂度提高:需要保证消息没有被重复消费、处理消息丢失的情况、保证消息传递的顺序性等等问题
- 一致性问题:异步在提高响应速度时,由于消息不能保证绝对正确消费,可能会带来一致性问题
消息队列的专有名词
- Broker:消息服务器,作为 server 提供消息核心服务
- Producer:消息生产者,业务的发起方,负责生产消息传输给 broker
- Consumer:消息消费者,业务的处理方,负责从 broker 获取消息并进行业务逻辑处理
- Topic:主题,发布订阅模式下的消息统一汇集地,不同生产者向 topic 发送消息,由 MQ 服务器分发到不同的订阅者,实现消息的广播
- Queue:队列,PTP 模式下,特定生产者向特定 queue 发送消息,消费者订阅特定的 queue 完成指定消息的接收
- Message:消息体,根据不同通信协议定义的固定格式进行编码的数据包,来封装业务数据,实现消息的传输
消息队列的使用场景
- 异步通信:异步处理机制,允许用户把消息放入队列,但并不立即处理它
- 解耦:降低工程间的强依赖程度,针对异构系统进行适配
- 冗余:把数据进行持久化直到它们已经被完全处理,通过这一方式规避了数据丢失风险
- 扩展性:消息队列解耦了你的处理过程,所以增大消息入队和处理的频率是很容易的,只要另外增加处理过程即可
- 过载保护:消息队列能够使关键组件顶住突发的访问压力,而不会因为突发的超负荷的请求而完全崩溃
- 可恢复性:消息队列降低了进程间的耦合度,所以即使一个处理消息的进程挂掉,加入队列中的消息仍然可以在系统恢复后被处理
- 顺序保证:大多使用场景下,数据处理的顺序都很重要。大部分消息队列本来就是排序的,并且能保证数据会按照特定的顺序来处理
- 缓冲:消息队列通过一个缓冲层来帮助任务最高效率的执行,该缓冲有助于控制和优化数据流经过系统的速度
消息队列的常用协议
- AMQP:一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同开发语言等条件的限制
- MQTT:该协议支持所有平台,几乎可以把所有联网物品和外部连接起来,被用来当做传感器和致动器(比如通过Twitter让房屋联网)的通信协议
- STOMP:STOMP提供一个可互操作的连接格式,允许客户端与任意STOMP消息代理(Broker)进行交互
选型
消息队列选型的标准
- 功能需求
- 性能需求
- 可用性需求
- 易用性需求
- 横向对比
消息队列选型对比
数据可靠性
RocketMQ 的同步刷盘在单机可靠性上比 Kafka 更高,不会因为操作系统Crash,导致数据丢失。Kafka 同步 Replication 理论上性能低于 RocketMQ 的同步 Replication,原因是Kafka 的数据以分区为单位组织,意味着一个 Kafka 实例上会有几百个数据分区,RocketMQ 一个实例上只有一个数据分区,RocketMQ 可以充分利用 IO 的 Commit 机制。
性能对比
Kafka 与 RocketMQ均为10w/s以上。
消息投递实时性
Kafka使用短轮询方式,实时性取决于轮询间隔时间,0.8 以后版本支持长轮询;RocketMQ 使用长轮询,同 Push 方式实时性一致,消息的投递延时通常在几个毫秒。
失败重试
Kafka消费失败不支持重试;RocketMQ 消费失败支持定时重试,每次重试间隔时间顺延。
严格的消息顺序
Kafka支持消息顺序,但是一台代理宕机后,就会产生消息乱序;RocketMQ 支持严格的消息顺序,在顺序消息场景下,一台Broker宕机后,发送消息会失败,但是不会乱序。
定时消息
Kafka 不支持定时消息;RocketMQ 支持两类定时消息
场景题
如何保证消息的可靠性传输(如何处理消息丢失的问题)
RabbitMQ
- 生产者丢失消息:生产者在发送消息时由于网络问题可能会导致消息丢失。解决办法如下,开启RabbitMQ事务功能,事务提供回滚、重试、提交等功能,但是会影响吞吐量,且事务是同步的,无法异步执行;使用Confirm机制,每次写消息时,都会分配一个唯一的 id ,当向 MQ 中写消息时处理成功 MQ 会回传一个 ack 消息,如果MQ未能处理则回调一个nack接口,随后进行重试。事务机制和cnofirm机制最大的不同在于,事务机制是同步的,你提交一个事务之后会阻塞在那儿,但是confirm机制是异步的,你发送个消息之后就可以发送下一个消息,然后那个消息rabbitmq接收了之后会异步回调你一个接口通知你这个消息接收到了
- MQ丢失消息:掉电可能会引发数据丢失,解决办法是开启持久化。持久化不保证数据绝对不丢失,可能会以极小的概率导致少量数据丢失。持久化可以和生产者的 confirm 配合,消息持久化到磁盘后才通知生产者 ack
- 消费者丢失消息:消费者在消费中,突然宕机或进程挂了,可能导致数据丢失。解决办法是利用 ack 机制,而且关闭自动 ack,通过 API 调用,当逻辑处理完后再手动 ack
Kafka
- 消费者丢失消息:消费者已经提交 offset,然而该 offset 对应的消息尚未真正被消费完,导致消息丢失。同理,关闭自动提交offset,手动提交offset,这种情况虽然会产生消息重复消费,但只要保证消费幂等性,少量的重复消费也是可以接受
- Kafka丢失数据:某个 broker 宕机,导致重新竞选副本 leader,然而竞选时可能会出现某些 follower 还没有完成数据同步。解决方法如下:首先设置系统的副本因子必须大于1;设置 min.insync.replicas 值必须大于1,要求一个 leader 至少感知到有至少一个 follower 还跟自己保持联系;在 producer 端设置 acks=all,要求每个消息写入所有副本后才认为消息提交成功;在 producer 端设置 retries=MAX,一旦写入失败就无限重试
如何解决消息队列重复消费以及保证消息消费的幂等性
消息队列在使用中,可能会出现消息重复消费的情况:
- 例如,在 Kafka 中,使用 offset标记消息的序号,当消费者消费数据之后每隔一段时间(定时),会提交已消费消息的 offset 表明已消费这些消息,如果消费者重启,那么下次从 offset 编号的消息继续消费。但是如果消费者重启时,本次已处理的消息的 offset 还没有提交,那么下次必然会从上次提交的 offset 出开始消费,造成重复消费。
- 例如,在 rabbitMQ 中,生产者已把消息发送给 mq。而 mq 在给生产者返回 ack 时生产者断网,未接收到确认消息,生产者判定消息发送失败。下次网络重连时,生产者重发消息。消费者在消费 mq 中消息后向 mq 返回 ack 时断网,导致 mq 未收到确认消息,该条消息会重新发送给其他消费者或断网重连后发送给其他消费者,造成重复消费。
消息重复处理,可能会带来不必要的性能开销,但是如果能保证消息处理的幂等性,适量的重复消费也是可以接受的。实现消息消费幂等性的手段如下:
- 基于数据库插入,先根据主键查询,如果有数据就更新
- 基于Redis写入,Redis 使用 set 模式,具有天然的幂等性
- 消息队列对每条消息设置全局唯一且与业务无关的 id ,消费者消费时,先根据 id 查询(Redis)是否已消费,如果没消费,设置这个消息 id 为已消费。核心原理是做查询
- 对于数据库可以基于唯一键约束,重复插入会报错,导致数据库中不会出现脏数据
- 有限状态机的幂等
- TOKEN 机制:核心思想是为每一次操作生成一个唯一性的凭证,也就是token。一个token在操作的每一个阶段只有一次执行权,一旦执行成功则保存执行结果。对重复的请求,返回同一个结果。
如何保证消息顺序处理
首先先描述下一些消费顺序错乱的场景:
- RabbitMQ中,一个Queue,多个Consumer的情况
- Kafka中,一个Topic,一个Partition,一个Consumer但是内部具有多线程结构
解决方案:
- RabbitMQ拆分队列,每个Queue对应一个Consumer
- Kafka:消费者中使用内存队列解决,将相同Hash过的数据放在一个内存队列里,采用单线程消费,写n个内存的Queue,每个线程分别消费一个Queue
如何解决消息队列的延时以及过期失效问题
RabbtiMQ 可以设置过期时间的,也就是TTL。如果消息在queue中积压超过一定的时间就会被 RabbitMQ 给清理掉,这个数据就没了。此时不存在消息积压,而是消息丢失。解决办法是采用批量重导技术,被丢弃的数据在后半夜平峰时,重新灌入 MQ 处理。
消息队列满了以后该怎么处理
队列满了,可能是消费者出现了问题导致消费速度太慢。从多角度查询问题根源,可能是队列太长导致磁盘写满、下游 MySQL 宕机导致消费者 Hang 住。分情况解决问题。
如果有几百万消息持续积压几小时怎么解决
如果让消费者以默认速度消费,肯定会花费大量时间,不太容易接受这种解决办法。为了提高消费速度,可以采用紧急扩容,步骤如下:
- 先修复消费者的故障,恢复其处理速度,修复后先不要返工
- 新建 Topic ,令 partition 是原来的10倍,临时建立好原先 10-20 倍的队列数量
- 设置一个临时的分发数据的消费者程序,该程序用于消费积压的数据,直接将积压的数据均匀写入 10 倍扩容后的队列中
- 利用 10 倍的机器部署消费者,分别消费 10 倍的队列
- 快速消费积压数据后恢复原先的架构继续生产消费
如何设计一个消息队列
https://zhuanlan.zhihu.com/p/21649950
- 具有可伸缩性
- 保证高可用性
- 保证高稳定性
- 持久化功能
RocketMQ
RocketMQ如何解决消息的顺序性
消息有序指的是可以按照消息的发送顺序来消费。例如:一笔订单产生了 3 条消息,分别是订单创建、订单付款、订单完成。消费时,要按照顺序依次消费才有意义。与此同时多笔订单之间又是可以并行消费的。假如生产者产生了2条消息:M1、M2,要保证这两条消息的顺序,应该怎样做?
假定M1发送到S1,M2发送到S2,如果要保证M1先于M2被消费,那么需要M1到达消费端被消费后,通知S2,然后S2再将M2发送到消费端。这个模型存在的问题是,如果M1和M2分别发送到两台Server上,就不能保证M1先达到MQ集群,也不能保证M1被先消费。换个角度看,如果M2先于M1达到MQ集群,甚至M2被消费后,M1才达到消费端,这时消息也就乱序了,说明以上模型是不能保证消息的顺序的。如何才能在MQ集群保证消息的顺序?一种简单的方式就是将M1、M2发送到同一个Server上:
这个模型也仅仅是理论上可以保证消息的顺序,在实际场景中可能会遇到下面的问题:
只要将消息从一台服务器发往另一台服务器,就会存在网络延迟问题。如上图所示,如果发送M1耗时大于发送M2的耗时,那么M2就仍将被先消费,仍然不能保证消息的顺序。即使M1和M2同时到达消费端,由于不清楚消费端1和消费端2的负载情况,仍然有可能出现M2先于M1被消费的情况。那如何解决这个问题?将M1和M2发往同一个消费者,且发送M1后,需要消费端响应成功后才能发送M2。如果M1被发送到消费端后,消费端1没有响应,那是继续发送M2呢,还是重新发送M1?一般为了保证消息一定被消费,肯定会选择重发M1到另外一个消费端2,就如下图所示。
这样的模型就严格保证消息的顺序,细心的你仍然会发现问题,消费端1没有响应Server时有两种情况,一种是M1确实没有到达(数据在网络传送中丢失),另外一种消费端已经消费M1且已经发送响应消息,只是MQ Server端没有收到。如果是第二种情况,重发M1,就会造成M1被重复消费。也就引入了消息重复处理。
RocketMQ的解决方案是通过合理的设计或者将问题分解来规避。RocketMQ通过轮询所有队列的方式来确定消息被发送到哪一个队列(负载均衡策略)。
RocketMQ如何解决重复消息
造成消息重复的根本原因是:网络不可达。只要通过网络交换数据,就无法避免这个问题。所以解决这个问题的办法就是绕过这个问题。那么问题就变成了:如果消费端收到两条一样的消息,应该怎样处理?
- 消费端处理消息的业务逻辑保持幂等性
- 保证每条消息都有唯一编号且保证消息处理成功与去重表的日志同时出现
第1条很好理解,只要保持幂等性,不管来多少条重复消息,最后处理的结果都一样。第2条原理就是利用一张日志表来记录已经处理成功的消息的ID,如果新到的消息ID已经在日志表中,那么就不再处理这条消息。第1条解决方案,很明显应该在消费端实现,不属于消息系统要实现的功能。第2条可以消息系统实现,也可以业务端实现。正常情况下出现重复消息的概率其实很小,如果由消息系统来实现的话,肯定会对消息系统的吞吐量和高可用有影响,所以最好还是由业务端自己处理消息重复的问题,这也是RocketMQ不解决消息重复的问题的原因。RocketMQ不保证消息不重复,如果你的业务需要保证严格的不重复消息,需要你自己在业务端去重。
RocketMQ事务消息
以一个转帐的场景为例来说明这个问题:Bob向Smith转账100块。在单机环境下,执行事务情况如下:
当用户增长到一定程度,Bob和Smith的账户及余额信息已经不在同一台服务器上了,那么上面的流程就变成了这样:
这时候你会发现,同样是一个转账的业务,在集群环境下,耗时居然成倍的增长,这显然是不能够接受的。那如何来规避这个问题?答案是将大事务拆分成多个小事务异步执行。这样基本上能够将跨机事务的执行效率优化到与单机一致。转账的事务就可以分解成如下两个小事务:
图中执行本地事务(Bob账户扣款)和发送异步消息应该保证同时成功或者同时失败,也就是扣款成功了,发送消息一定要成功,如果扣款失败了,就不能再发送消息。那问题是:我们是先扣款还是先发送消息呢?首先看下先发送消息的情况,大致的示意图如下:
先发消息后执行本地事务存在的问题是:如果消息发送成功,但是扣款失败,消费端就会消费此消息,进而向Smith账户加钱。那么先执行本地事务扣款的情况如下:
存在的问题跟上面类似:如果扣款成功,发送消息失败,就会出现Bob扣钱了,但是Smith账户未加钱。RocketMQ支持事务消息,下面来看看RocketMQ是怎样来实现的。
RocketMQ第一阶段发送Prepared消息时,会拿到消息的地址,第二阶段执行本地事物,第三阶段通过第一阶段拿到的地址去访问消息,并修改消息的状态。如果确认消息发送失败了怎么办?RocketMQ会定期扫描消息集群中的事物消息,如果发现了Prepared消息,它会向消息发送端(生产者)确认,Bob的钱到底是减了还是没减呢?如果减了是回滚还是继续发送确认消息呢?RocketMQ会根据发送端设置的策略来决定是回滚还是继续发送确认消息。这样就保证了消息发送与本地事务同时成功或同时失败。
再回到转账的例子,如果Bob的账户的余额已经减少,且消息已经发送成功,Smith端开始消费这条消息,这个时候就会出现消费失败和消费超时两个问题,解决超时问题的思路就是一直重试,直到消费端消费消息成功,整个过程中有可能会出现消息重复的问题,按照前面的思路解决即可。
这样基本上可以解决消费端超时问题,但是如果消费失败怎么办?阿里提供给我们的解决方法是:人工解决。大家可以考虑一下,按照事务的流程,因为某种原因Smith加款失败,那么需要回滚整个流程。如果消息系统要实现这个回滚流程的话,系统复杂度将大大提升,且很容易出现Bug,估计出现Bug的概率会比消费失败的概率大很多。这也是RocketMQ目前暂时没有解决这个问题的原因,在设计实现消息系统时,我们需要衡量是否值得花这么大的代价来解决这样一个出现概率非常小的问题,这也是大家在解决疑难问题时需要多多思考的地方。
RocketMQ的其他特性
- 定时消息
- 消息的刷盘策略
- 主动同步策略:同步双写、异步复制
- 海量消息堆积能力
- 高效通信
如何保证消息队列的高可用
(集群架构怎么设计保证高容错性)
消息队列有具有三种可用性模式:
- 单机模式,易于部署,但是可用性低,一旦宕机,就无法提供服务
- 普通集群模式,无高可用性,在多台机器上启动多个 RabbitMQ 实例。用户创建的 Queue 只会存放在一个RabbitMQ实例上,每个实例都同步 Queue 的元数据。一旦用户连接的不是主实例,这个实例会去主实例拉取数据,然后提供服务。这种模式缺点如下:MQ 集群内部可能会产生大量数据传输;无法提供高可用性,主实例节点宕机,无法提供数据;并没有做到分布式,容易产生单点压力过大
- 镜像集群模式,每个实例保留其他实例的完整镜像,写入 Queue 后自动同步到其他实例的 Queue 上。虽然能保证节点宕机后,其他节点具有完整数据,服务不至于中断。但是这种模式会使得集群内部网络带宽消耗严重,扩展性不高;同步所有节点的数据容易超出机器的容量
Kafka的高可用设计
Kafka 由多个 broker 节点组成,每个 broker 都是一个节点;创建一个 topic, topic 是一个逻辑概念,代表了一类消息,可以被认为是消息被发送到的地方。topic 通常被多个消费者订阅,每个 topic 由多个 partition 分区组成,partition 具有自己专属的 partition 号,通常是从 0 开始的。用户从 partition 尾部追加写入消息。partition 中的每条消息都会被分配一个唯一的序列号,被称为 offset 位移, offset 是从 0 开始顺序递增的整数。一条消息的定位由一个三元组 topic-partition-offset 组成。
Kafka 为了实现高可用,采用了冗余机制,通过 replica 副本备份,防止数据丢失。其中,副本分为两类, Leader 副本和 Follower 副本。 Follower 副本不提供给客户端,即不响应客户端发送来的消息写入与消费请求,它仅仅被动地向 Leader 副本获取数据。 Leader 副本则负责提供服务。一旦 Leader 副本所在的 broker 宕机, Kafka 会从剩余的 replica 中选出新的 leader 继续提供服务。Kafka 保证同一个partition 的多个 replica 一定不会分配在同一个 broker 中,副本因子则决定了 partition 的备份数量。
此外,Kafka 还有一个 ISR 的概念,即与 Kafka 维护的 leader replica 保持同步的 replica 集合,只有该集合中的副本才有机会竞选为leader。而生产者写入的一条消息只有被 ISR 中所有副本都接收到时,消息才被视为已提交状态。Kafka 对于没有提交成功的消息不做任何交付成功保证,它只保证在 ISR 存活的情况下,以已提交的消息不会丢失。若 ISR 中有 N 个副本,那么该分区最多可以容忍 N-1 个副本崩溃而不丢失已提交消息。
Kafka
Kafka的设计精要
- 吞吐量/延时
- 消息持久化
- 负载均衡和故障转移
- 伸缩性
4Kafka为什么具有高吞吐低延时
Kafka的写入快,每次都将数据写入os的页缓存上,由os决定何时写回磁盘,这就有三个优势:
- os的页缓存(避免穿透到底层物理磁盘上获取数据)是在内存中分配的,所以消息写入速度非常快
- 繁琐的底层I/O由os处理
- 写入操作采用追加写入的方式,避免了磁盘随机写操作
- 利用了Linux上的sendfile系统调用,也即零拷贝技术
Kafka中的消息持久化
Kafka具有持久化功能,将信息持久化到磁盘上,好处如下:
- 解耦消息发送与消息消费:Kafka 提供了生产者-消费者的完整解决方案,消息生产完交给 Kafka 服务器保存即可,提高了整体的吞吐量
- 实现灵活的消息处理:支持对已处理过的消息在未来的某个时间点再次处理,即消息重演
- 持久化的刷盘策略不同, Kafka 所有数据都会立即被写入文件系统的持久化日志中,避免过高的内存消耗,将空间用于页缓存,进一步提高整体性能
Kafka的伸缩性
- 伸缩性表示向分布式系统中增加额外的计算资源时吞吐量提升的能力。如果服务器是无状态的或将状态交给专门的协调服务来做,例如ZooKeeper,那么整个集群的服务器间无需繁重的状态共享,可以极大降低维护复杂度。扩容时,简单地启动新机器,向ZooKeeper注册即可。
RabbitMQ
RabbitMQ是采用 Erlang 语言实现 AMQP(Advanced Message Queuing Protocol,高级消息队列协议)的消息中间件,它最初起源于金融系统,用于在分布式系统中存储转发消息。RabbitMQ 的具体特点可以概括为以下几点:
- 可靠性: RabbitMQ使用一些机制来保证消息的可靠性,如持久化、传输确认及发布确认等。
- 灵活的路由: 在消息进入队列之前,通过交换器来路由消息。对于典型的路由功能,RabbitMQ 己经提供了一些内置的交换器来实现。针对更复杂的路由功能,可以将多个交换器绑定在一起,也可以通过插件机制来实现自己的交换器。这个后面会在我们将 RabbitMQ 核心概念的时候详细介绍到。
- 扩展性: 多个RabbitMQ节点可以组成一个集群,也可以根据实际业务情况动态地扩展集群中节点。
- 高可用性: 队列可以在集群中的机器上设置镜像,使得在部分节点出现问题的情况下队列仍然可用。
- 支持多种协议: RabbitMQ 除了原生支持 AMQP 协议,还支持 STOMP、MQTT 等多种消息中间件协议。
- 多语言客户端: RabbitMQ几乎支持所有常用语言,比如 Java、Python、Ruby、PHP、C#、JavaScript等。
- 易用的管理界面: RabbitMQ提供了一个易用的用户界面,使得用户可以监控和管理消息、集群中的节点等。在安装 RabbitMQ 的时候会介绍到,安装好 RabbitMQ 就自带管理界面。
- 插件机制: RabbitMQ 提供了许多插件,以实现从多方面进行扩展,当然也可以编写自己的插件。感觉这个有点类似 Dubbo 的 SPI机制。
RabbitMQ交换器的作用
在RabbitMQ中,消息并不是被直接投递搭到Queue中,先经过Exchanger-交换器,然后把消息分配到对应的Queue中。Exchanger用来接收生产者发送的消息并将这些消息路由给服务器中的队列中,如果路由不到则会返回给生产者或直接丢弃。Exchanger有四种类型,对应不同的路由策略:
- fanout类型的Exchange路由规则非常简单,它会把所有发送到该Exchange的消息路由到所有与它绑定的Queue中,不需要做任何判断操作,所以fanout类型是所有的交换机类型里面速度最快的。fanout类型常用来广播消息
- direct类型的Exchange路由规则也很简单,它会把消息路由到那些Bindingkey与RoutingKey完全匹配的Queue中。direct 类型常用在处理有优先级的任务,根据任务的优先级把消息发送到对应的队列,这样可以指派更多的资源去处理高优先级的队列
- topic类型的交换器在匹配规则上进行了扩展,它与 direct 类型的交换器相似,也是将消息路由到BindingKey和 RoutingKey相匹配的队列中,匹配规则类似正则表达
- headers 类型的交换器不依赖于路由键的匹配规则来路由消息,而是根据发送的消息内容中的 headers 属性进行匹配