说到消息中间件,大部分人的第一印象可能是Kafka
。毕竟Kafka
自问世以来,就顶着高并发,大流量的光环。当然了Kafka
也不负众望,在大数据处理方面独领风骚。
这里想说说另一款同样优秀的消息中间件RabbitMQ
。
选RabbitMQ还是Kafka
如果单机数据量没有到十万级以上,我觉得选哪个都OK,如果超过百万甚至到了千万级别那么建议选择Kafka
。
对了还有重要的一点,RabbitMQ
支持事务,而Kafka
不支持。所以如果你的业务系统要求支持事务,那么只能选RabbitMQ
。这也是很多金融系统选择RabbitMQ
作为消息中间件的原因。
RabbitMQ基本概念
先来说一下消息中间件的通用模型,所有的消息中间件的模型基本都是这样。
而RabbitMQ
是基于AMQP
协议实现的。模型大概是这个样子,如下图所示
重点关注中间两个框框,下面依次解读一下。
信道
建立TCP
链接是一件很费时的事情,所以很多提供高并发服务的软件都支持TCP
链接复用,比如HTTP
协议的KeepAlive
就是为了复用TCP
链接准备的。所以RabbitMQ
提出了信道的概念,一个TCP
链接里面可以支持多个信道同时通信,以提高通信效率。如下图所示。
broker
一个启动的RabbitMQ
实例,代表一个broker
。
虚拟主机
虚拟主机可以理解成命名空间的概念,方便队列管理。
交换器
向RabbitMQ
发送消息时,先把消息先发到交换器,再由交换器根据相关路由规则发到特定队列上。目前 RabbitMQ
的交换器共四种类型:direct
、fanout
、topic
、headers
。
headers交换器: 匹配消息的header
而不是路由键,除此之外headers
交换器和direct
交换器完全一致,但性能差很多,所以目前几乎不用。
direct交换器: 消息中的路由键(routing key)如果和Binding
(绑定的概念后面会介绍)中的 binding key 一致, 交换器就将消息发到对应的队列中。direct
模式是完全匹配的单播模式,也就是说路由键与队列名完全匹配,如果一个队列绑定到交换机要求路由键为“dog”,则只转发 routing key 标记为“dog”的消息,不会转发“dog.puppy”的消息。如下图所示。
fanout交换器:每个发送到fanout
交换器中的消息,不会去匹配路由键,而是直接把消息投递到所有绑定到fanout
交换器上的队列中。类似一个广播站,会向所有收听广播的用户发送消息。对应到实际应用中,它允许你针对一个消息作不同操作,比如用户上传了一张新的图片,系统要同时对这个事件进行不同的操作,比如删除旧的图片缓存、增加积分奖励等等。这样就大大降低了系统之间的耦合度了。
如下图所示。
topic交换器:topic
交换器和direct
交换器有相似之处,它通过模式匹配的方式分发消息,将路由键和某个模式进行匹配,此时队列需要绑定到一个模式上。它将路由键和绑定键的字符串切分成单词,这些单词之间用点隔开。它同样也会识别两个通配符:符号#
和符号*
。#
匹配0个或多个单词,*
匹配至少一个单词。如下图所示。
绑定
将消息队列和交换器进行关联。一个绑定就是基于路由键将交换器和消息队列连接起来的路由规则,所以可以将交换器理解成一个由绑定构成的路由表,如下图所示:
集群及高可用
因为RabbitMQ
是Erlang
开发的,所以天生支持分布式。所谓的天生支持的意思就是不用像Kafka
那样借助ZK
来实现集群。
先来看一下典型的RabbitMQ
集群架构图。
集群分为普通模式和镜像模式。另外RabbitMQ
集群分为内存节点和磁盘节点,集群当中必须存在磁盘节点。内存节点就是将数据存储到内存中,读写效率会比较高;而磁盘节点是将数据存储到磁盘上面,可以持久化保存数据。
普通模式:默认的集群模式。所谓的普通模式就是,多台机器上启动多个Rabbitmq
实例,每台机器启动一个实例。但是创建的queue
,只会放在一个Rabbtimq
实例上,每个实例都会同步queue
的元数据。消费的时候,如果连接到了另外一个实例,那么那个实例会从queue
所在实例上拉取数据过来。如果存放queue
的节点挂掉,那么就不能提供服务了。所以这种方式并不是真正的高可用。
镜像模式:创建的queue
,无论元数据还是消息都会存在于多个实例上,每次写消息到queue
的时候,都会自动把消息同步到多个实例的queue
里。任何一个节点挂掉后,其他节点可以继续提供服务。类似于Redis
集群的哨兵模式。关于Redis的高可用部署方式,可以看我这篇文章。
元数据:
元数据主要包含四类,分别是
队列元数据:队列名称及属性;
交换器元数据:交换器名称、类型及属性
绑定元数据:记录了如何将消息路由到队列的数据;
vhost元数据:为队列、交换器和绑定提供命名空间和安全属性的数据;
运维常用管理命令
启动/sbin/rabbitmq-server
重启/sbin/rabbitmqctl reset
停止
/sbin/rabbitmqctl stop
停止应用/sbin/rabbitmqctl stop_app
查看RabbitMQ
状态/sbin/rabbitmqctl status
查看绑定/sbin/rabbitmqctl list_bindings
查看交换器/sbin/rabbitmqctl list_exchanges
查看已声明的队列/sbin/rabbitmqctl list_queues
stop和stop_app 的区别
这里需要特别注意一下这两个命令的区别:由于 RabbitmMQ
是用Erlang
写的,Erlang
有节点的概念,也就是在一个Erlang
节点上,可以运行很多个Erlang
应用,stop
命令是使得整个Erlang
节点停止工作,而stop_app
则是使得当前应用停止工作,不会影响其它应用的正常运行。
总结
如果之前一直接触的Java开发的中间件,突然接触RabbitMQ
,会有些许不适应。特别是对于RabbitMQ
的集群部署及高可用的理解。不过了解之后会发现这只兔子还是很可爱的。毕竟存在就有存在的道理。
推荐阅读
1. Java并发编程那些事儿(十)——最后的总结
2. Java8的Stream流真香,没体验过的永远不知道
3. Awk这件上古神兵你会用了吗
4. 手把手教你搭建一套ELK日志搜索运维平台