RabbitMQ作为分布式消息存储和转发系统,已广泛使用于分布式系统中。本文简要介绍RabbitMQ相关概念、集群架构和消息转发流程,并与Kafka做了简要对比,以加深理解。
消息(Message)是应用之间的传送数据,包括文本字符串或者嵌入式对象。消息从一端立即返回到另一端称为同步通信;消息从一端发出后先进入临时缓冲存储,达到一定条件后再发送给另一端称为异步通信。这个临时缓冲存储的具体实现是消息队列(Message queue),消息队列是应用之间的通信方式。
那么什么是AMQP呢?AMQP,即Advanced Message Queuing Protocol,高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。
AMQP的内部架构如上图所示:
RabbitMQ是AMQP协议的开源实现,基于Erlang语言开发,支持多种客户端,多用于分布式系统中的消息存储和转发。RabbitMQ主要特性如下:
RabbitMQ作为一个分布式消息存储和转发的系统,适用于以下场景:
RabbitMQ集群设计的目的是允许消费者和生产者在节点奔溃的情况下继续运行,并且通过线性扩展增加节点来提升消息的吞吐量。RabbitMQ集群分为两种模式:
1)普通模式
对于普通模式,集群中各节点有相同的队列结构,但消息只会存在于集群中的一个节点也就是Master节点。当消息进入Master节点的Queue后,consumer从slave节点拉取数据,RabbitMQ会临时从Master、slave节点之间进行数据传输,将Master节点中的消息实体数据取出并经过slave节点发送给consumer。
应用场景: 该模式存在一个问题就是当Master节点故障时候,slave节点无法拉取到master节点中的消息数据,因此只是保证了服务可用,但是无法保证高可用。如果做了队列持久化或消息持久化,那么得等Master节点恢复,然后才可被消费,并且在Master节点恢复之前其它节点不能再创建Master节点已经创建过的持久队列;如果没有持久化的话,消息就会失丢。因此,该模式更适合于消息无需持久化的场景,只有当队列非持久化,客户端才能重新连接到集群里的其他节点,并重新创建队列。假如该队列是持久化的,那么唯一办法是将故障节点恢复起来。
2)镜像模式
与普通模式不同之处是消息实体会主动在镜像节点间同步,而不是在获取数据时临时拉取,这种模式能够保证高可用。在镜像模式下包括1个master节点和n个slave节点,当master节点故障时候,会选出新的主并自动切换到新主。在实际的镜像模式架构中,客户端通过LVS负载均衡连接到HAProxy,HAProxy再将访问请求代理到管理的RabbitMQ Server上,实现高可用和负载功能。
应用场景:镜像场景适用于对可靠性要求高的场景,镜像集群也是基于普通集群,只有先搭建普通集群,然后才能设置镜像队列。但是镜像模式中如果镜像队列过多并且消息体量过大,集群内部的网络带宽被这种消息镜像同步占用,影响系统的性能。
RabbitMQ在默认情况下考虑存储空间、性能的原因,所以集群内部仅同步元数据
在RabbitMQ集群中,元数据信息在所有节点中都是一致的。因此,当用户访问其中任何一个RabbitMQ节点时,通过rabbitmqctl查询到的queue/user/exchange/vhost等信息都是相同的。
RabbitMQ集群中有RAW Node和Disk Node两种类型:
对于RabbitMQ集群,要求集群中至少有一个disk node,其它节点可以是RAW Node。当节点加入或离开集群时,需要将变更通知到至少一个磁盘节点。如果只有一个磁盘节点,刚好又是该节点崩溃了,那么集群可以继续路由消息,但是不能创建队列、创建交换器、创建绑定、添加用户、更改权限、添加或删除集群节点。因此通常在集群中添加2台以上的磁盘节点,这样其中一台发生故障了,集群仍然可以保持运行,且能够在任何时候保存元数据变更。
Exchange根据不同的exchange type和Binding规则进行消息的路由分发,目前常用的有4种exchange:Direct exchange、Fanout exchange、Topic exchange和headers。
Direct exchange的实现原理是通过消息中的路由键(routing key),与queue中的routing key进行比对,如果二者匹配,则将消息发送到这个消息队列。
如图所示,生产者以routingKey=”error”发送消息到Exchange,则消息会路由到Queue1(amqp.gen-S9b…)和Queue2(amqp.gen-Agl…);如果以routingKey=”info”或routingKey=”warning”来发送消息,则消息只会路由到Queue2;如果以其他routingKey发送消息,则消息不会路由到这两个Queue中。
Fanout exchange路由不需要routkey,当exchange收到消息后,会将消息复制多份转发给绑定的消息队列。这种路由策略有点像路由广播,每台子网内的主机都获得了一份复制的消息,这种路由机制是最快的。
如图所示,生产者发送到Exchange的所有消息都会路由到图中的两个Queue,并最终被两个消费者C1和C2消费。
Topic exchange是direct exchange的通配符模式,通过模式匹配分发消息的路由键属性,将路由键和某个模式进行匹配,此时队列需要绑定到一个模式上。exchange支持“#”和“”的通配,“#”匹配0个或多个单词,“”匹配一个单词。
如图所示,routing key为"quick.orange.rabbit"的消息会同时路由到Q1和Q2、routing key为"lazy.orange.elephant"的消息也会同时路由到Q1和Q2。“quick.orange.fox"和"lazy.brown.fox"的消息分别路由到Q1和Q2,但是"lazy.pink.rabbit"只会路由到Q2一次,虽然能匹配到2条规则。其它的比如"quick.brown.fox”,则不能被匹配到会被丢弃。
Headers类型的Exchange不依赖于routing key与binding key的匹配规则来路由消息,而是根据发送的消息内容中的headers属性进行匹配。在创建queue的时候需要设置绑定的头部信息,分为全部匹配和部分匹配。当消息发送到Exchange时,RabbitMQ会获取到该消息的headers,对比其中的键值对是否完全匹配Queue与Exchange绑定时指定的键值对;如果完全匹配则消息会路由到该Queue,否则不会路由到该Queue。和其它类型相比,性能会差很多,实际使用的机会很少。
RabbitMQ消息的可靠性是基于持久化实现的,持久化可以防止在异常情况下丢失数据。RabbitMQ的持久化分为三个部分:Exchange持久化、队列持久化和消息的持久化。
1)Exchange持久化
交换器持久化可以通过在声明队列时将durable参数设置为true。如果交换器不设置持久化,那么在RabbitMQ服务重启之后,相关的Exchange元数据会丢失,但是消息不会丢失,只是不能将消息发送到这个Exchange了。
2)队列持久化和消息持久化
队列的持久化能保证其本身的元数据不会因异常情况而丢失,但是不能保证内部所存储的消息不会丢失。要确保消息不会丢失,需要将其设置为持久化。队列的持久化可以通过在声明队列时将durable参数设置为true。设置了队列和消息的持久化,当RabbitMQ服务重启之后,消息依然存在。如果只设置队列持久化或者消息持久化,重启之后消息都会消失。
持久化是保证在服务器重启的时候可以保持不丢失相关信息,但是将所有的消息设置为持久化会影响RabbitMQ的性能,因为磁盘的写入速度比内存的写入要慢得多。对于可靠性不是那么高的消息可以不采用持久化处理以提高整体的吞吐量。在实际中,需要根据实际情况在可靠性和吞吐量之间做一个权衡。
为了保证消息从队列可靠地到达消费者,RabbitMQ提供了消息确认机制。消息确认机制分为生产者和消费者。
1)生产者消息确认机制
生产者将消息投递到所有匹配的队列后,会发送一个确认消息给生产者,使得生产者得知消息已经正确达到。如果消息和队列是持久化存储的,那么确认消息会在消息写入磁盘之后发出。
2)消费者消息确认机制
消费者订阅队列的时候,可以指定autoAck参数
消息确认机制是RabbitMQ消息可靠性投递的基础,只要设置autoAck参数为false,消费者就有足够的时间处理消息,不用担心处理消息的过程中消费者进程挂掉后消息丢失的问题。因为RabbitMQ会一直等待并持有消息,直到消费者确认了该消息。
当消息在一个队列中变成死信(dead message)时,会通过交换机发送到死信队列中,这个交换机称为DLX(Dead Letter Exchange)。以下场景下消息会变成死信:
DLX和一般的交换机一样,当这个队列中有死信的时候,RabbitMQ会自动将这个消息重新发送到设置的交换器上,进而被路由到另一个队列。后续的程序可以根据死信队列中的内容分析当时发生的异常,进而改善和优化系统。定义死信队列指定参数:
延迟队列就是进入该队列的消息会被消费者延迟消费,也就是当消息被生产者发送以后,等待特定的时间后,消费者才能拿到这个消息进行消费。
RabbitMQ可以对消息和队列设置TTL(Time To Live),TTL是一条消息在队列中的最大存活时间,单位是毫秒。
如果两种方式一起使用,则过期时间以两者中较小的那个数值为准。当然也可以不设置TTL,如果不设置表示消息不会过期;如果设置为0,则表示除非此时可以直接将消息投递到消费者,否则该消息将被立即丢弃。
1)安装erlang语言环境
[root@tango-centos01 local]# mkdir erlang
[root@tango-centos01 otp_src_25.1.2]# ./configure --prefix=/usr/local/erlang/
[root@tango-centos01 otp_src_25.1.2]# make
[root@tango-centos01 otp_src_25.1.2]# make install
2)设置环境变量
#更新/etc/profile,添加内容
$PATH:/usr/local/erlang/bin
#source /etc/profile
3)检查erlang版本
[root@tango-rac01 ~]# erl
Erlang/OTP 25 [erts-13.1.2] [source] [64-bit] [smp:1:1] [ds:1:1:10] [async-threads:1]
Eshell V13.1.2 (abort with ^G)
1>
1)解压安装包
tar -xvf rabbitmq-server-generic-unix-3.11.3.tar.xz
2)启动服务
[root@tango-rac01 sbin]# ./rabbitmq-server -detached
带-detached参数的命令是后台启动方式
3)检查状态
[root@tango-rac01 sbin]# ./rabbitmqctl status
Status of node rabbit@tango-rac01 ...
Runtime
OS PID: 27316
OS: Linux
Uptime (seconds): 41
Is under maintenance?: false
RabbitMQ version: 3.11.3
RabbitMQ release series support status: supported
4)启用RabbitMQ Management管理台
[root@tango-rac01 sbin]# ./rabbitmq-plugins enable rabbitmq_management
Enabling plugins on node rabbit@tango-rac01:
rabbitmq_management
The following plugins have been configured:
rabbitmq_management
rabbitmq_management_agent
rabbitmq_web_dispatch
Applying plugin configuration to rabbit@tango-rac01...
The following plugins have been enabled:
rabbitmq_management
rabbitmq_management_agent
rabbitmq_web_dispatch
started 3 plugins.
5)访问控制管理台http://192.168.112.135:15672/
提示guest用户无法远程访问,需修改rabbit.app配置文件
在/usr/local/rabbitmq_server-3.11.3/plugins/rabbit-3.11.3/ebin修改
{loopback_users, [<<"">>]},
6)重启RabbitMQ服务
./rabbitmqctl shutdown
./rabbitmq-server -detached
7)重新访问控制台
在前文中介绍了Kafka相关知识,本文又介绍了RabbitMQ相关概念,二者之间有什么样的区别。RabbitMQ作为一种消息中间件,作为服务总线利用消息队列实现分布式系统中的消息存储与转发;Kafka则是一种分布式流式系统实现,利用消息订阅实现分布式的消息发布。
RabbitMQ和Kafka对比如下表所示:
参考资料: