RabbitMQ技术详解

RabbitMQ技术预研报告

  1. 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基础概念


RabbitMQ技术详解_第1张图片
image.png

Queue
Queue(队列)RabbitMQ的作用是存储消息,队列的特性是先进先出。上图可以清晰地看到Client A和Client B是生产者,生产者生产消息最终被送到RabbitMQ的内部对象Queue中去,而消费者则是从Queue队列中取出数据。可以简化成表示为:


RabbitMQ技术详解_第2张图片
image.png

生产者Send Message “A”被传送到Queue中,消费者发现消息队列Queue中有订阅的消息,就会将这条消息A读取出来进行一些列的业务操作。这里只是一个消费正对应一个队列Queue,也可以多个消费者订阅同一个队列Queue,当然这里就会将Queue里面的消息平分给其他的消费者,但是会存在一个一个问题就是如果每个消息的处理时间不同,就会导致某些消费者一直在忙碌中,而有的消费者处理完了消息后一直处于空闲状态,因为前面已经提及到了Queue会平分这些消息给相应的消费者。这里我们就可以使用prefetchCount来限制每次发送给消费者消息的个数。详情见下图所示:
RabbitMQ技术详解_第3张图片
image.png

这里的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中去。简单示意图如下所示:


RabbitMQ技术详解_第4张图片
image.png

在绑定(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中。


RabbitMQ技术详解_第5张图片
image.png

上图所示,生产者(P)生产消息1将消息1推送到Exchange,由于Exchange Type=fanout这时候会遵循fanout的规则将消息推送到所有与它绑定Queue,也就是图上的两个Queue最后两个消费者消费。

2)direct 定向
direct类型的Exchange路由规则也很简单,它会把消息路由到那些binding key与routing key完全匹配的Queue中。


RabbitMQ技术详解_第6张图片
image.png

当生产者(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中可以存在两种特殊字符“”与“#”,用于做模糊匹配,其中“”用于匹配一个单词,“#”用于匹配多个单词(可以是零个)

RabbitMQ技术详解_第7张图片
image.png

当生产者发送消息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。

  1. 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服务器环境


RabbitMQ技术详解_第8张图片
image.png

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的管理控制台,使用刚才创建的账号登陆系统

  1. 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、关闭连接

  1. RabbitMQ特性
    4.1 消费者确认
    消费者确认或者说消费者应答ACK指的是RabbitMQ需要确认消息到底有没有被收到
    在订阅消息的时候可以指定应答模式,当自动应答等于true的时候,表示当消费者一收到消息就表示消费者收到了消息,消费者收到了消息就会立即从队列中删除。
    消息状态:Total代表队列中的消息总条数,Ready代表消费者还可以读到的条数,Unacked:代表还有多少条没有被应答


    image.png

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"}]

  1. RabbitMQ弊端
    5.1不支持消息插队
    5.2不支持批量数据
    5.3不支持消息延迟机制
    可以通过TTL和DLX特性组合实现

欢迎关注【技术型项目经理】公众号。可获取软件行业动态、技术积累和项目管理理念文章分享。选择「考试经验」菜单「PMP」、「高项」(信息系统项目管理师)、「CISSP」、「GoLang」可获取学习资料。

你可能感兴趣的:(RabbitMQ技术详解)