RabbitMQ说明与安装
一、RabbitMQ介绍
1. RabbitMQ 的相关概念 2007 年发布,是一个在 AMQP(高级消息队列协议)基础上完成的,可复用的企业消息系统,是当前最主流的消息中间件之一。 RabbitMQ是一个由erlang开发的AMQP(Advanced Message Queue 高级消息队列协议 )的开源实现,由于erlang 语言的高并发特性,性能较好,本质是个队列,FIFO 先入先出,里面存放的内容是message RabbitMQ 是一个消息中间件:它接收消息并且转发,就类似于一个快递站,卖家把快递通过快递站,送到我们的手上,MQ也是这样,接收并存储消息,再转发。 2. 为什么要用 MQ 流量消峰 举个例子,如果订单系统最多能处理一万次订单,这个处理能力应付正常时段的下单时绰绰有余,正常时段我们下单一秒后就能返回结果。但是在高峰期,如果有两万次下单操作系统是处理不了的,只能限制订单超过一万后不允许用户下单。使用消息队列做缓冲,我们可以取消这个限制,把一秒内下的订单分散成一段时间来处理,这时有些用户可能在下单十几秒后才能收到下单成功的操作,但是比不能下单的体验要好。 简单来说: 就是在访问量剧增的情况下,但是应用仍然不能停,比如“双十一”下单的人多,但是淘宝这个应用仍然要运行,所以就可以使用消息中间件采用队列的形式减少突然访问的压力 应用解耦 以电商应用为例,应用中有订单系统、库存系统、物流系统、支付系统。用户创建订单后,如果耦合调用库存系统、物流系统、支付系统,任何一个子系统出了故障,都会造成下单操作异常。当转变成基于消息队列的方式后,系统间调用的问题会减少很多,比如物流系统因为发生故障,需要几分钟来修复。在这几分钟的时间里,物流系统要处理的内存被缓存在消息队列中,用户的下单操作可以正常完成。当物流系统恢复后,继续处理订单信息即可,中间用户感受不到物流系统的故障,提升系统的可用性。 如图: 把支付,库存,物流都交给MQ
异步处理
有些服务间调用是异步的,例如 A 调用 B,B 需要花费很长时间执行,但是 A 需要知道 B 什么时候可以执行完,以前一般有两种方式,A 过一段时间去调用 B 的查询 api 查询。或者 A 提供一个 callback api, B 执行完之后调用 api 通知 A 服务。
这两种方式都不是很优雅,使用消息总线,可以很方便解决这个问题,A 调用 B 服务后,只需要监听 B 处理完成的消息,当 B 处理完成后,会发送一条消息给 MQ,MQ 会将此消息转发给 A 服务。这样 A 服务既不用循环调用 B 的查询 api,也不用提供 callback api。同样 B 服务也不用做这些操作。A 服务还能及时的得到异步处理成功的消息。
3. 四大核心概念 生产者: 产生数据发送消息的程序是生产者 交换机 交换机是RabbitMQ非常重要的一个部件,一方面它接收来自生产者的消息,另一方面它将消息推送到队列中。交换机必须确切知道如何处理它接收到的消息,是将这些消息推送到特定队列还是推送到多个队列,亦或者是把消息丢弃,这个得有交换机类型决定 队列 队列是 RabbitMQ 内部使用的一种数据结构,尽管消息流经 RabbitMQ 和应用程序,但它们只能存储在队列中。队列仅受主机的内存和磁盘限制的约束,本质上是一个大的消息缓冲区。许多生产者可以将消息发送到一个队列,许多消费者可以尝试从一个队列接收数据。这就是我们使用队列的方式 消费者 消费与接收具有相似的含义。消费者大多时候是一个等待接收消息的程序。请注意生产者,消费者和消息中间件很多时候并不在同一机器上。同一个应用程序既可以是生产者又是可以是消费者。 4. RabbitMQ 核心部分 Hello Wold 简单模式 Work queues工作 队列模式 Publish/Subscribe发布订阅模式 Routing 路由模式 Topics 主题模式 Publisher Confirms 发布确认模式 5. RabbitMQ工作原理
Broker:接收和分发消息的应用,RabbitMQ Server 就是 Message Broker Virtual host:出于多租户和安全因素设计的,把 AMQP 的基本组件划分到一个虚拟的分组中,类似于网络中的 namespace 概念。当多个不同的用户使用同一个RabbitMQ server 提供的服务时,可以划分出多个 vhost,每个用户在自己的 vhost 创建 exchange/queue 等 Connection:publisher/consumer 和 broker 之间的 TCP 连接 Channel:如果每一次访问 RabbitMQ 都建立一个 Connection,在消息量大的时候建立 TCP Connection 的开销将是巨大的,效率也较低。Channel 是在 connection 内部建立的逻辑连接,如果应用程序支持多线程,通常每个 thread 创建单独的 channel 进行通讯,AMQP method 包含了 channel id 帮助客户端和 message broker 识channel,所以 channel 之间是完全隔离的。Channel 作为轻量级的Connection 极大减少了操作系统建立 TCP connection 的开销 Exchange:message 到达 broker 的第一站,根据分发规则,匹配查询表中的 routing key,分发消息到 queue 中去。常用的类型有:direct (point-to-point), topic (publish-subscribe) and fanout (multicast) Queue:消息最终被送到这里等待 consumer 取走 Binding:exchange 和 queue 之间的虚拟连接,binding 中可以包含 routing key,Binding 信息被保存到 exchange 中的查询表中,用于 message 的分发依据 工作原理 生产者发送消息到 broker server(RabbitMQ),在 Broker 内部,用户创建Exchange/Queue,通过 Binding 规则将两者联系在一起,Exchange 分发消息,根据类型/binding 的不同分发策略有区别,消息最后来到 Queue 中,等待消费者取走。
二、RabbitMQ 单机部署
RabbitMQ是一个开源的遵循AMQP协议实现的基于Erlang语言编写,**即需要先安装部署Erlang环境再安装RabbitMQ环境。两者版本号的对应表(https://www.rabbitmq.com/which-erlang.html),安装相应版本的Erlang和RabbitMQ,只需在下文修改命令里面的版本号即可。 [root@localhost7h ~]# wget --content-disposition https://packagecloud.io/rabbitmq/erlang/packages/el/7/erlang-22.3.4.12-1.el7.x86_64.rpm/download.rpm [root@localhost7h ~]# wget https://packagecloud.io/rabbitmq/rabbitmq-server/packages/el/7/rabbitmq-server-3.7.22-1.el7.noarch.rpm # Key 导入(可选) [root@localhost7h ~]# rpm --import https://www.rabbitmq.com/rabbitmq-release-signing-key.asc #安装 [root@localhost7h ~]# yum install erlang-22.3.4.12-1.el7.x86_64.rpm [root@localhost7h ~]# yum install rabbitmq-server-3.7.22-1.el7.noarch.rpm #启动 systemctl start rabbitmq-server.service #RabbitMQ 插件管理: 5672:消费者访问的 端口 15672:web 管理端口 25672:集群状态通信端口 #开启 web 界面管理插件 [root@localhost7h ~]# rabbitmq-plugins list [root@localhost7h ~]# rabbitmq-plugins enable rabbitmq_management 默认禁止使用guest登陆 web 管理界面: rabbitmq 从 3.3.0 开始禁止使用 guest/guest 权限通过除 localhost 外的访问, [root@localhost7h ~]#vim /usr/lib/rabbitmq/lib/rabbitmq_server-3.3.3/ebin/rabbit.app 39 {loopback_users, []}, #删除被禁止登陆的 guest 账户 [root@localhost7h ~]# systemctl restart rabbitmq-server.service #重启 rabbitmq 服务 #添加用户,默认的virtual hosts 是 / [root@localhost7h ~]# rabbitmqctl add_user jack 123456 #设置用户分配操作权限。 [root@localhost7h ~]# rabbitmqctl set_user_tags jack administrator #设置对”virtual hosts 是 /“的权限。 [root@localhost7h ~]# rabbitmqctl set_permissions jack ".*" ".*" ".*"
三、RabbitMQ 集群部署
Rabbitmq 集群分为二种方式: 普通模式:创建好 RabbitMQ 集群之后的默认模式。 镜像模式:把需要的队列做成镜像队列。 普通集群模式:queue 创建之后,如果没有其它 policy,消息实体只存在于其中 一个节点,A、B 两个 Rabbitmq 节点仅有相同的元数据,即队列结构,但队列的 数据仅保存有一份,即创建该队列的 rabbitmq 节点(A 节点),当消息进入 A 节 点的 Queue 中后,consumer 从 B 节点拉取时,RabbitMQ 会临时在 A、B 间进行 消息传输,把 A 中的消息实体取出并经过 B 发送给 consumer,所以 consumer 可 以连接每一个节点,从中取消息,该模式存在一个问题就是当 A 节点故障后,B 节点无法取到 A 节点中还未消费的消息实体。 镜像集群模式: 把需要的队列做成镜像队列,存在于多个节点,属于 RabbitMQ 的 HA 方案(镜 像模式是在普通模式的基础上,增加一些镜像策略) 该模式解决了普通模式中的数据丢失问题,其实质和普通模式不同之处在于,消 息实体会主动在镜像节点间同步,而不是在 consumer 取数据时临时拉取,该模 式带来的副作用也很明显,除了降低系统性能外,如果镜像队列数量过多,加之 大量的消息进入,集群内部的网络带宽将会被这种同步通讯大大消耗掉,所以在 对可靠性要求较高的场合中适用,一个队列想做成镜像队列,需要先设置 policy, 然后客户端创建队列的时候,rabbitmq 集群根据“队列名称”自动设置是普通集 群模式或镜像队列。 集群中有两种节点类型: 内存节点:只将数据保存到内存 磁盘节点:保存数据到内存和磁盘。 内存节点虽然不写入磁盘,但是它执行比磁盘节点要好,集群中,只需要一个磁盘节点来保存数据就足够了如果集群中只有内存节点,那么不能全部停止它们,否则所有数据消息在服务器全部停机之后都会丢失。 推荐设计架构: 在一个 rabbitmq 集群里,有 3 台或以上机器,其中 1 台使用磁盘模式,其它节点使用内存模式,内存节点无访问速度更快,由于磁盘 IO 相对较慢,因此可作为数据备份使用。 后期rabbitmq,千万不要改主机名.
集群状态监控:API接口:
[root@localhost7J local]# curl -s -u guest:guest http://192.168.80.170:15672/api/nodes
1.主机名解析配置 [root@localhost7h local]# cat /etc/hosts 192.168.80.170 localhost7H.localdomain 192.168.80.180 localhost7I.localdomain 192.168.80.190 localhost7J.localdomain 2.安装软件 [root@localhost7# yum install erlang-22.3.4.12-1.el7.x86_64.rpm [root@localhost7# yum install rabbitmq-server-3.7.22-1.el7.noarch.rpm 3.同步 RabbitMQ 集群的cookie文件: Rabbitmq 的集群是依赖于 erlang 的集群来工作的,所以必须先构建起 erlang 的集群环境,而 Erlang 的集群中各节点是通过一个 magic cookie 来实现的,这个 cookie 存放在 /var/lib/rabbitmq/.erlang.cookie 中,文件是 400 的权限,所以必须保证各节点 cookie 保持一致,否则节点之间就无法通信。 [root@localhost7h ~]#scp /var/lib/rabbitmq/.erlang.cookie 192.168.80.180:/var/lib/rabbitmq/.erlang.cookie [root@localhost7h ~]#scp /var/lib/rabbitmq/.erlang.cookie 192.168.80.190:/var/lib/rabbitmq/.erlang.cookie 设置文件权限属性(三台都设置) [root@localhost7J local]# chown rabbitmq:rabbitmq /var/lib/rabbitmq/.erlang.cookie 创建 RabbitMQ 集群: 在localhost7h 作为内存节点添加到 localhost7J,并作为内存节点(默认为磁盘节点),在localhost7h操作 [root@localhost7h ~]#rabbitmqctl stop_app #停止 app 服务 [root@localhost7h ~]#rabbitmqctl reset #清空元数据 [root@localhost7h ~]#rabbitmqctl join_cluster rabbit@localhost7J --ram #注意:我用了短域名。 [root@localhost7h ~]#rabbitmqctl join_cluster rabbit@localhost7J.localdomain --ram #失败。 [root@localhost7i ~]#rabbitmqctl stop_app [root@localhost7i ~]#rabbitmqctl reset [root@localhost7i ~]#rabbitmqctl join_cluster rabbit@localhost7J --ram 查看当前集群状态 [root@localhost7h local]# rabbitmqctl cluster_status Cluster status of node rabbit@localhost7h ... [{nodes,[{disc,[rabbit@localhost7J]}, #磁盘节点,集群中至少有一个节点是磁盘节点用于数据持久化 {ram,[rabbit@localhost7h,rabbit@localhost7I]}]}, #内存节点 {running_nodes,[rabbit@localhost7I,rabbit@localhost7J,rabbit@localhost7h]}, # 节点 {cluster_name,<<"rabbit@localhost7h.localdomain">>}, # #当前正在运行的节点. {partitions,[]}, {alarms,[{rabbit@localhost7I,[]}, {rabbit@localhost7J,[]}, {rabbit@localhost7h,[]}]}] 其它命令说明: #添加用户,默认的virtual hosts 是 / [root@localhost7h ~]# rabbitmqctl add_user jack 123456 #创建 vhost [root@localhost7J local]# rabbitmqctl add_vhost zzhz Adding vhost "zzhz" ... #列出所有 vhost [root@localhost7J local]# rabbitmqctl list_vhosts Listing vhosts ... name / magedu zzhz #列出所有队列 [root@localhost7J local]# rabbitmqctl list_queues Timeout: 60.0 seconds ... Listing queues for vhost / ... #删除指定 vhost root@mq-server1:~# [root@localhost7J local]# rabbitmqctl delete_vhost magedu Deleting vhost "magedu" ... #设置 jack 用户对 zzhz 的 vhost 有读写权限,三个点为配置正则、读和写 [root@localhost7J local]# rabbitmqctl set_permissions -p zzhz jack ".*" "" "" Setting permissions for user "jack" in vhost "zzhz" ...
#设置镜像策略
[root@localhost7J local]# rabbitmqctl list_policies
Listing policies for vhost "/" ...
[root@localhost7J local]# rabbitmqctl set_policy ha-all "#" '{"ha-mode":"all"}'
Setting policy "ha-all" for pattern "#" to "{"ha-mode":"all"}" with priority "0" for vhost "/" ...
[root@localhost7J local]#
[root@localhost7J local]#
[root@localhost7J local]#
[root@localhost7J local]# rabbitmqctl list_policies
Listing policies for vhost "/" ...
vhost name pattern apply-to definition priority
/ ha-all # all {"ha-mode":"all"} 0