RabbitMQ技术预研报告
- RabbitMQ介绍
1.1 RabbitMQ背景
RabbitMQ是一个由erlang开发的AMQP(Advanced Message Queue )的开源实现。AMQP 的出现其实也是应了广大人民群众的需求,虽然在同步消息通讯的世界里有很多公开标准(如 COBAR的 IIOP ,或者是 SOAP 等),但是在异步消息处理中却不是这样,只有大企业有一些商业实现(如微软的 MSMQ ,IBM 的 Websphere MQ 等),因此,在 2006 年的 6 月,Cisco 、Redhat、iMatix 等联合制定了 AMQP 的公开标准。
RabbitMQ是由RabbitMQ Technologies Ltd开发并且提供商业支持的。该公司在2010年4月被SpringSource(VMWare的一个部门)收购。在2013年5月被并入Pivotal。其实VMWare,Pivotal和EMC本质上是一家的。不同的是VMWare是独立上市子公司,而Pivotal是整合了EMC的某些资源,现在并没有上市。
RabbitMQ的官网是http://www.rabbitmq.com
1.2 RabbitMQ基础概念
Queue
Queue(队列)RabbitMQ的作用是存储消息,队列的特性是先进先出。上图可以清晰地看到Client A和Client B是生产者,生产者生产消息最终被送到RabbitMQ的内部对象Queue中去,而消费者则是从Queue队列中取出数据。可以简化成表示为:
生产者Send Message “A”被传送到Queue中,消费者发现消息队列Queue中有订阅的消息,就会将这条消息A读取出来进行一些列的业务操作。这里只是一个消费正对应一个队列Queue,也可以多个消费者订阅同一个队列Queue,当然这里就会将Queue里面的消息平分给其他的消费者,但是会存在一个一个问题就是如果每个消息的处理时间不同,就会导致某些消费者一直在忙碌中,而有的消费者处理完了消息后一直处于空闲状态,因为前面已经提及到了Queue会平分这些消息给相应的消费者。这里我们就可以使用prefetchCount来限制每次发送给消费者消息的个数。详情见下图所示:
这里的prefetchCount=1是指每次从Queue中发送一条消息来。等消费者处理完这条消息后Queue会再发送一条消息给消费者。
Exchange
我们在开篇的时候就留了一个坑,就是那个应用结构图里面,消费者Client A和消费者Client B是如何知道我发送的消息是给Queue1还是给Queue2,有没有过这个问题,那么我们就来解开这个面纱,看看到底是个什么构造。首先明确一点就是生产者产生的消息并不是直接发送给消息队列Queue的,而是要经过Exchange(交换器),由Exchange再将消息路由到一个或多个Queue,当然这里还会对不符合路由规则的消息进行丢弃掉,这里指的是后续要谈到的Exchange Type。那么Exchange是怎样将消息准确的推送到对应的Queue的呢?那么这里的功劳最大的当属Binding,RabbitMQ是通过Binding将Exchange和Queue链接在一起,这样Exchange就知道如何将消息准确的推送到Queue中去。简单示意图如下所示:
在绑定(Binding)Exchange和Queue的同时,一般会指定一个Binding Key,生产者将消息发送给Exchange的时候,一般会产生一个Routing Key,当Routing Key和Binding Key对应上的时候,消息就会发送到对应的Queue中去。那么Exchange有四种类型,不同的类型有着不同的策略。也就是表明不同的类型将决定绑定的Queue不同,换言之就是说生产者发送了一个消息,Routing Key的规则是A,那么生产者会将Routing Key=A的消息推送到Exchange中,这时候Exchange中会有自己的规则,对应的规则去筛选生产者发来的消息,如果能够对应上Exchange的内部规则就将消息推送到对应的Queue中去。
ConnectionFactory、Connection、Channel
ConnectionFactory、Connection、Channel都是RabbitMQ对外提供的API中最基本的对象。Connection是RabbitMQ的socket链接,它封装了socket协议相关部分逻辑。ConnectionFactory为Connection的制造工厂。
Channel是我们与RabbitMQ打交道的最重要的一个接口,我们大部分的业务操作是在Channel这个接口中完成的,包括定义Queue、定义Exchange、绑定Queue与Exchange、发布消息等。
Connection就是建立一个TCP连接,生产者和消费者的都是通过TCP的连接到RabbitMQ Server中的,这个后续会再程序中体现出来。
Channel虚拟连接,建立在上面TCP连接的基础上,数据流动都是通过Channel来进行的。为什么不是直接建立在TCP的基础上进行数据流动呢?如果建立在TCP的基础上进行数据流动,建立和关闭TCP连接有代价。频繁的建立关闭TCP连接对于系统的性能有很大的影响,而且TCP的连接数也有限制,这也限制了系统处理高并发的能力。但是,在TCP连接中建立Channel是没有上述代价的。
1.3 RabbitMQ路由规则
1)fanout 广播
fanout类型的Exchange路由规则非常简单,它会把所有发送到该Exchange的消息路由到所有与它绑定的Queue中。
上图所示,生产者(P)生产消息1将消息1推送到Exchange,由于Exchange Type=fanout这时候会遵循fanout的规则将消息推送到所有与它绑定Queue,也就是图上的两个Queue最后两个消费者消费。
2)direct 定向
direct类型的Exchange路由规则也很简单,它会把消息路由到那些binding key与routing key完全匹配的Queue中。
当生产者(P)发送消息时Rotuing key=booking时,这时候将消息传送给Exchange,Exchange获取到生产者发送过来消息后,会根据自身的规则进行与匹配相应的Queue,这时发现Queue1和Queue2都符合,就会将消息传送给这两个队列,如果我们以Rotuing key=create和Rotuing key=confirm发送消息时,这时消息只会被推送到Queue2队列中,其他Routing Key的消息将会被丢弃。
3)topic 通配符
前面提到的direct规则是严格意义上的匹配,换言之Routing Key必须与Binding Key相匹配的时候才将消息传送给Queue,那么topic这个规则就是模糊匹配,可以通过通配符满足一部分规则就可以传送。它的约定是:
routing key为一个句点号“. ”分隔的字符串(我们将被句点号“. ”分隔开的每一段独立的字符串称为一个单词),如“stock.usd.nyse”、“nyse.vmw”、“quick.orange.rabbit”
binding key与routing key一样也是句点号“. ”分隔的字符串
binding key中可以存在两种特殊字符“”与“#”,用于做模糊匹配,其中“”用于匹配一个单词,“#”用于匹配多个单词(可以是零个)
当生产者发送消息Routing Key=F.C.E的时候,这时候只满足Queue1,所以会被路由到Queue中,如果Routing Key=A.C.E这时候会被同是路由到Queue1和Queue2中,如果Routing Key=A.F.B时,这里只会发送一条消息到Queue2中。
4)headers
headers类型的Exchange不依赖于routing key与binding key的匹配规则来路由消息,而是根据发送的消息内容中的headers属性进行匹配。
在绑定Queue与Exchange时指定一组键值对;当消息发送到Exchange时,RabbitMQ会取到该消息的headers(也是一个键值对的形式),对比其中的键值对是否完全匹配Queue与Exchange绑定时指定的键值对;如果完全匹配则消息会路由到该Queue,否则不会路由到该Queue。
- RabbitMQ环境搭建
2.1 RabbitMQ客户端环境
1)下载和安装
下载rabbitmq-c最新代码包:https://github.com/alanxz/rabbitmq-c/releases/tag/v0.9.0
下载cmake最新安装包并默认安装:https://cmake.org/download/
2)使用cmake编译生成适合自己编译环境的工程
第一步:填写源代码路径
第二步:填写建立后的路径,build的文件夹一般建立在源代码路径里,也可以放在其他位
第三步:点击配置按钮,在配置里面选择属于自己编译环境的名字
第四步:点击生成按钮,不出现运行失败就说明已经编译成功了RabbitMQ服务器环境
3)打开VS工程编译RabbitMQ库及测试代码
2.2 RabbitMQ服务器环境
1)下载和安装
下载Erlang并默认安装,下载地址: http://www.erlang.org/downloads
配置环境变量:ERLANG_HOME:C:\Program Files (x86)\erl10.3
下载RabbitMQ并默认安装,下载地址:https://www.rabbitmq.com/download.html
配置环境变量:RABBITQM_SERVER:C:\Program Files\RabbitMQ Server\rabbitmq_server-3.7.14
Path中增加:%ERLANG_HOME%\bin;%RABBITQM_SERVER%\sbin;
2)RabbitMQ配置
第一步:激活 RabbitMQ's Management Plugin
使用RabbitMQ 管理插件,可以更好的可视化方式查看Rabbit MQ 服务器实例的状态。
打开命令窗口,输入命令:
rabbitmq-plugins enable rabbitmq_management
第二步:重启服务
net stop RabbitMQ && net start RabbitMQ
第三步:创建用户,密码,绑定角色
使用rabbitmqctl控制台命令(位于C:\Program Files\RabbitMQ Server\rabbitmq_server-3.6.5\sbin>)来创建用户,密码,绑定权限等。
rabbitmq的用户管理包括增加用户,删除用户,查看用户列表,修改用户密码。
查看已有用户及用户的角色:
rabbitmqctl.bat list_users
新增一个用户:
rabbitmqctl.bat add_user username password
给 eric 变成 “超级管理员” 角色:
rabbitmqctl.bat set_user_tags username administrator
第四步:使用浏览器打开 http://localhost:15672 访问Rabbit Mq的管理控制台,使用刚才创建的账号登陆系统
- RabbitMQ数据发布和接收流程
3.1 数据发布流程
1、生产者连接到RabbitMQ Broker,建立一个连接connection,开启一个信道channel
2、生产者声明一个交换器,并设置相关属性,如交换机类型、是否持久化等
3、生产者声明一个队列并设置相关属性,如是否排他、是否持久化、是否自动删除等
4、生产者通过路由键将交换器和队列绑定起来
5、生产者发送消息至RabbitMQ Broker,其中包含路由键、交换器等信息
6、相应的交换器根据接收到的路由键查找相匹配的队列
7、如果找到,将消息存入相应的队列中
8、如果没有找到,根据生产者配置的属性选择丢弃或者退回给生产者
9、关闭信道
10、关闭连接
3.2 数据接收流程
1、消费者连接到RabbitMQ Broker,建立一个连接connection,开启一个信道channel
2、消费者向RabbitMQ Broker请求消费相应队列中的消息,可能会设置相应的回调函数,以及做一些准备工作
3、等待RabbitMQ Broker回应并投递相应队列中的消息,消费者接收消息
4、消费者确认ack接收到的消息
5、RabbitMQ从队列中删除相应已经被确认的消息
6、关闭信道
7、关闭连接
-
RabbitMQ特性
4.1 消费者确认
消费者确认或者说消费者应答ACK指的是RabbitMQ需要确认消息到底有没有被收到
在订阅消息的时候可以指定应答模式,当自动应答等于true的时候,表示当消费者一收到消息就表示消费者收到了消息,消费者收到了消息就会立即从队列中删除。
消息状态:Total代表队列中的消息总条数,Ready代表消费者还可以读到的条数,Unacked:代表还有多少条没有被应答
4.2 生产者确认
Confirm机制
channel.confirmSelect();
channel.waitForConfirms();
事务机制
channel.txSelect();
channel.txRollback();
通过生产者确认机制生产者可以判断消息是否发送到了exchange及queue
生产者确认机制:生产者将信道设置成confirm模式,消息被投递到匹配的队列后,会发送确认给生产者,包含消息的唯一ID;若消息和队列是可持久化的,那么确认消息会在消息写入磁盘后发出,要提高效率,需要使用批量confirm或异步conifrm。
事务机制和publisher confirm机制是互斥的;事务机制和publisher confirm机制确认的是消息能够正确的发送至MQ,如果找不到匹配的队列,消息依然会被丢弃
事务机制:channel.txSelect用于将当前信道设置成事务模式;channel.txCommit用于提交事务;channel.txRollback用于事务回滚
事务机制能够解决消息发送方和MQ之间消息确认的问题,只有消息成功被MQ接收,事务才能提交成功
注意:事务机制是非常非常非常消耗性能的,最好使用Confirm机制,Confirm机制相比事务机制性能上要好很多。
4.3 过期时间TTL
设置方法一:通过队列属性设置,队列中所有消息都有相同过期时间
设置方法二:对消息本身单独设置
若两者同时设置,以TTL较小者的值为准。时间超过TTL值时,会变成死信Dead Message
通过队列属性设置TTL的方法是,channel。queueDeclare方法中加入x-message-ttl参数实现,单位毫秒
也可以通过Policy的方式设置TTL
rabbitmqctl set_policy TTL ".*" '{"message-ttl":60000}' --apply-to queues
还可以通过调用HTTP API接口设置
curl -i -u root:root -H "content-type:application/json" -X PUT -d'{"auto_delete":false,"durable":true,"arguments":{"x-message-ttl":60000}}' http://localhost:15672/api/queues/{vhost}/{queuename}
若不设置TTL,则消息永不过期;若设置为0,表示除非可以直接将消息投递到消费者,否则消息立即丢弃
针对单条消息的TTL方法为,在channel.basicPublish中加入expiration属性,单位毫秒
也可以用HTTP API接口
curl -i -u root:root -H "content-type:application/json" -X POST -d '{"properties":{"expiration":"60000"},"routing_key":"routingKey","payload":"my body","payload_encoding":"string"}' http://localhost:15672/api/exchanges/{vhost}/{exchangename}/publish
第一种设置方法的消息到期后,会直接从队列中抹去;第二种方法设置的,在投递前进行判断
4.4 队列的TTL
队列声明时通过x-expires参数设置,当队列未使用(没有任何消费者、没有被重新声明、过期时间段内未调用过Basic.Get命令)时,会被删除。服务器重启后,持久化的队列过期时间会重新计算,x-expires单位为毫秒,不能设置为0
4.5 死信队列DLX
DLX,Dead-Letter-Exchange,死信交换器。当消息在一个队列中变成死信后,能被重新发送到另一个交换器中,这个交换器就是DLX,绑定DLX的队列就是死信队列
声明队列时通过x-dead-letter-exchange参数为该队列添加DLX
也可以通过Policy设置,rabbitmqctl set_policy DLX ".*" '{"dead-letter-exchange":"dlx_exchange"}' --apply-to queues
消息变成死信的情况:消息被拒,且设置requeue参数为false、消息过期、队列达到最大长度
DLX可以用来分析异常,或者配合TTL实现延迟队列的功能
4.6 优化级队列
优化级高的消息具备优先被消费的特权。通过设置队列的x-max-priority参数实现,配置一个队列的最大优先级,然后在发送消息时,为每条消息指定当前的优先级priority
4.7 持久化
提高可靠性,在异常(重启、关闭、宕机)时的数据防丢失。MQ持久化包括:交换器持久化、队列持久化、消息持久化
交换器持久化通过声明时将durable置为true实现,可以防止重启后交换器元数据的丢失,但消息不会丢失,只是不能再将消息发送到该交换器中
队列持久化通过声明时将durable置为true实现,若不设置持久化,重启后,队列会丢失,队列中的消息也会丢失。队列持久化可以保证本身元数据不丢失,但并不保证内部存储的消息不丢失
消息持久化通过将投递模式BasicProperties中deliveryMode属性设置为2实现
设置了队列和消息持久化,重启后,消息依旧存在
单单只设置队列持久化,重启后,消息会丢失
单单只设置消息持久化,重启后,队列消失,继而消息也丢失
所以,单单设置消息持久化而不设置队列持久化毫无意义,而且消息持久化会严重影响RabbitMQ的性能
持久化还需要autoAck配合,同时,缓存落盘也有风险,为真正确保消息不丢失,可以使用RabbitMQ的镜像队列机制,实际生产环境中关键业务队列一般都会设置镜像队列
4.8 性能
性能达到单线程11000条/秒
4.9 死信投递
队列设置了 x-dead-letter-exhcange 属性将会接收到被拒绝的、或超时的消息。消息设置了 x-dead-letter-routing-key 后 routing key 将会在死信投递后被改变。
4.10 RPC
远程过程调用,客户端发送请求消息,服务端回复响应消息。为了接收响应消息,需要在请求中发送一个回调队列(replyTo)replyTo,设置一个回调队列correlationId,关联请求request和其调用RPC之后的回复response
处理流程: 1、客户端启动时,创建一个匿名的回调队列,名称由MQ自动创建 2、客户端为RPC请求设置2个属性,replyTo用来告知RPC服务端回复请求时的目的队列,即回调队列;correlationId用来标记一个请求 3、请求发送到rpc_queue队列中 4、RPC服务端监听rpc_queue队列中的请求,当请求到来时,处理请求且把带有结果的消息发送给客户端,接收队列就是replyTo设定的回调队列 5、客户端监听回调队列,当有消息时,检查correlationId属性,若与请求匹配,即说明为结果
4.11 自动删除不再使用的队列
生产者或消费者可能异常退出导致队列被残留,大量的残留队列会影响RabbitMQ实例的性能。RabbitMQ提供了3种自动删除队列的方法。
设置队列的 TTL :如 TTL 为28天的队列,当持续28天没有被消费后会被自动删除
配置 auto-delete 队列: auto-delete 队列在最后一个消费者取消消费、或链接关闭后被删除
配置 exclusive queue: exclusive queue 只能在创建此队列的 Connection/Channel 中使用,当 Connection/Channel 关闭后队列被删除
4.12 队列大小设定
通过设置 TTL 或 max-length 来限制队列大小,从而让队列不超过设定大小。
4.13 服务端环境绿色安装
配置环境变量
运行erl10.3\Install.exe修改erl10.3\erts-10.3\bin\erl.ini配置
添加文件C:\Windows\SysWOW64\msvcr120.dll
rabbitmq-plugins enable rabbitmq_management
rabbitmq-server -detached 后台启动
Rabbitmq-server 直接启动,如果你关闭窗口或者需要在改窗口使用其他命令时应用就会停止
关闭:rabbitmqctl stop
验证:http://127.0.0.1:15672/ guest guest
相关链接:https://blog.csdn.net/zxl646801924/article/details/80435231
4.14 HTTP API接口管理
RabbitMQ Management插件不仅提供Web管理界面,还提供了HTTP API接口
POST方法创建的是无法用具体名称的资源,如绑定和发布消息等
创建名为queue33的队列
%2F是默认vhost:/的转义
curl -i -u admin:admin -H"content-type:application/json" -XPUT -d'{"auto_delete":false,"durable":true,"node":"rabbit@localhost"}' http://127.0.0.1:15672/api/queues/%2F/queue33
获取队列queue33的信息
curl -i -u admin:admin -XGET http://127.0.0.1:15672/api/queues/%2F/queue33
删除队列queue33
curl -i -u admin:admin -XDELETE http://127.0.0.1:15672/api/queues/%2F/queue33
向交换器exchange2发送一条消息
curl -i -u admin:admin -XPOST -d'{"properties":{},"routing_key":"rk2","payload":"my_body","payload_encoding":"string"}' http://127.0.0.1:15672/api/exchanges/%2F/exchange2/publish
清空队列queue2
curl -i -u admin:admin -XDELETE http://127.0.0.1:15672/api/queues/%2F/queue2/contents
从队列中消费消息
curl -i -u admin:admin -XPOST -d'{"count":5,"requeue":false,"encoding":"auto","truncate":50000}' http://127.0.0.1:15672/api/queues/%2F/queue2/get
获取到的结果[{"payload_bytes":7,"redelivered":false,"exchange":"exchange2","routing_key":"rk2","message_count":0,"properties":[],"payload":"my_body","payload_encoding":"string"}]
- RabbitMQ弊端
5.1不支持消息插队
5.2不支持批量数据
5.3不支持消息延迟机制
可以通过TTL和DLX特性组合实现
欢迎关注【技术型项目经理】公众号。可获取软件行业动态、技术积累和项目管理理念文章分享。选择「考试经验」菜单「PMP」、「高项」(信息系统项目管理师)、「CISSP」、「GoLang」可获取学习资料。