一 消息队列介绍
1.1 什么是消息队列
MQ(Message Quene) : 消息队列, 是典型的生产者和消费者模型, 生产者不断向消息队列中生产消息,消费者不断的从队列中获取消息。
因为消息的生产和消费都是异步的,而且只关心消息的发送和接收,没有业务逻辑的侵入, 轻松的实现系统间解耦。
消息队列也可称作: 消息中间件
消息队列就是基础数据结构中的 “先进先出” 的一种数据机构。
想一下,生活中买东西,需要排队,先排的人先买消费,就是典型的 "先进先出"
1.2 MQ 解决问题的问题
MQ 是一直存在,不过随着微服务架构的流行,成了解决微服务之间问题的常用工具。
应用解耦
以电商应用为例,有订单系统、库存系统、物流系统、支付系统。
用户创建订单后,如果耦合调用库存系统、物流系统、支付系统,任何一个子系统出了故障,都会造成下单操作异常。
当转变成基于消息队列的方式后,系统间调用的问题会减少很多,比如物流系统因为发生故障,需要几分钟来修复。
在这几分钟的时间里,物流系统要处理的内存被缓存在消息队列中,用户的下单操作可以正常完成。
当物流系统恢复后,继续处理订单信息即可,中单用户感受不到物流系统的故障。提升系统的可用性
流量消峰
如果订单系统最多能处理一万次订单,这个处理能力应付正常时段的下单时绰绰有余,正常时段我们下单一秒后就能返回结果。
但是在高峰期,如果有两万次下单操作系统是处理不了的,只能限制订单超过一万后不允许用户下单。
使用消息队列做缓冲,我们可以取消这个限制,把一秒内下的订单分散成一段时间来处理,这事有些用户可能在下单十几秒后才能收到下单成功的操作,但是比不能下单的体验要好。
消息分发
多个服务队数据感兴趣,只需要监听同一类消息即可处理,就是典型的观察者模式,又叫发布订阅
例如 A 产生数据,B 对数据感兴趣。如果没有消息的队列 A 每次处理完需要调用一下 B 服务。过了一段时间 C 对数据也感性,A 就需要改代码,调用 B 服务,调用 C 服务。只要有服务需要,A 服务都要改动代码。很不方便。
有了消息队列后,A 只管发送一次消息,B 对消息感兴趣,只需要监听消息。C 感兴趣,C 也去监听消息。A 服务作为基础服务完全不需要有改动
异步消息
有些服务间调用是异步的,例如 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 服务还能及时的得到异步处理成功的消息
1.3 常见消息队列及比较
结论:
Kafka 在于分布式架构,RabbitMQ 基于 AMQP 协议来实现,RocketMQ / 思路来源于 kafka,改成了主从结构,在事务性可靠性方面做了优化。
广泛来说,电商、金融等对事务性要求很高的,可以考虑 RabbitMQ 和 RocketMQ,
对性能要求高的可考虑 Kafka
二 Rabbitmq 介绍安装
2.1 Rabbitmq 介绍
基于 AMQP 协议,erlang 语言开发,是部署最广泛的开源消息中间件, 是最受欢迎的开源消息中间件之一。
AMQP 协议 advanced message queuing protocol
在 2003 年时被提出,最早用于解决金融领不同平台之间的消息传递交互问题。
AMQP 是一种协议,是一个进程间传递异步消息的网络协议
# Broker: 消息中间件,AMQP实体
接收和分发消息的应用,RabbitMQ 的服务就是一个Message Broker
# Virtual host: 虚拟主机
出于多租户和安全因素设计的,把AMQP的基本组件划分到一个虚拟的分组中,类似于网络中的namespace概念
当多个不同的用户使用同一个RabbitMQ server提供的服务时,可以划分出多个vhost,每个用户在自己的vhost创建exchange/queue
# Connection: 连接
publisher/consumer和broker之间的TCP连接
断开连接的操作只会在client端进行,Broker不会断开连接,除非出现网络故障或broker服务出现问题
# 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中去。
常用的类型有:
'''
1【direct (point-to-point)-点对点模式】点对点模式Message中的routing key如果和Binding中的binding key一致,Direct exchange则将message发到对应的queue中
2【topic (publish-subscribe)-发布-订阅模式】根据routing key,及通配规则,Topic exchange将分发到目标queue中
3【fanout (multicast)-多播模式】每个发到Fanout类型Exchange的message都会分到所有绑定的queue上去。
'''
# Queue: 队列
消息最终被送到这里等待consumer取走
一个message可以被同时拷贝到多个queue中
# Binding: 绑定
exchange和queue之间的虚拟连接,binding中可以包含routing key
Binding信息被保存到exchange中的查询表中,用于message的分发依据
2.2 Rabbitmq 下载安装
官网:https://www.rabbitmq.com/
下载地址:https://www.rabbitmq.com/download.html
因为 Rabbitmq 是基于 erlang 语言开发,无论什么平台,都需要先安装 erlang
注意 rabbitmq 和 errlang 的版本对应关系
https://www.rabbitmq.com/which-erlang.html
2.2.1 Windows 下载安装
下载地址:https://www.rabbitmq.com/install-windows.html
erlang 下载地址:https://erlang.org/download/otp_versions_tree.html
两个软件一路无脑下一步即可,先安装 erlang,再按照 rabbitmq
2.2.2 Centos 7.5 上安装之 rpm 包安装
rabbitmq 下载地址:https://www.rabbitmq.com/install-rpm.html#downloads
erlang 下载地址:https://www.erlang.org/downloads
往下拉,找到 Solutions,点击,来到
https://www.erlang-solutions.com/downloads/
然后选择 Erlang OTP 的 CentOS 版本
# 1 安装上传下载软件
yum install -y lrzsz
# 2 把centos7文件夹下两个软件拖入
# 3 安装erlang
rpm -ivh esl-erlang_24.0-1_centos_7_amd64.rpm
# 如果报错误
错误:依赖检测失败:
libodbc.so.2()(64bit)
# 安装unixODBC
yum -y install unixODBC
# 查看安装的erlang
rpm -qa | grep erlang
# 卸载
rpm -e esl-erlang-23.3.1-1.x86_64
# 4 安装rabbitmq-server
rpm -ivh rabbitmq-server-3.8.16-1.el7.noarch.rpm
# 如果报错
错误:依赖检测失败:
socat 被 rabbitmq-server-3.8.16-1.el7.noarch 需要
# 安装socat
yum install socat
2.2.3 Docker 安装
# 安装好Docker,执行下面命令
docker pull rabbitmq:management
docker run -di --name Myrabbitmq -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=admin -p 15672:15672 -p 5672:5672 rabbitmq:management
# 浏览器访问:
http://10.0.0.103:15672
# 输入用户名:guest 密码:guest ,进入到管理控制台
2.3 配置 web 管理插件
2.3.2 centos7 配置
# 1 安装web管理插件
rabbitmq-plugins enable rabbitmq_management
# 2 新建配置文件(https://github.com/rabbitmq/rabbitmq-server/blob/master/deps/rabbit/docs/rabbitmq.conf.example)
cd /etc/rabbitmq
vi rabbitmq.conf
# 复制官方提供的配置文件
## 注意:把下面注释解开,才能从非本地访问web
## Uncomment the following line if you want to allow access to the guest user from anywhere on the network.
loopback_users.guest = false
# 3 启动RabbitMQ的服务
systemctl start rabbitmq-server
systemctl restart rabbitmq-server
systemctl stop rabbitmq-server
# 4 登录到web管理
http://10.0.0.100:15672/
# 5 用户名密码
用户名:guest
密码:guest
# 6 查看命令帮助
rabbitmqctl help
2.3.2 windows 配置
# 1 安装web管理插件
rabbitmq-service.bat enable rabbitmq_management
# 2 启动服务
rabbitmq-server.bat
# 3 访问web管理页面
http://127.0.0.1:15672/
# 4 使用用户登录
用户名:guest
密码:guest
2.4 用户设置
rabbitmqctl add_user lqz 123
# 设置用户为administrator角色
rabbitmqctl set_user_tags lqz administrator
# 设置权限
rabbitmqctl set_permissions -p "/" root ".*" ".*" ".*"
# 然后重启rabbiMQ服务
systemctl reatart rabbitmq-server
# 然后可以使用刚才的用户远程连接rabbitmq server了。
三 基本使用(生产者消费者模型)
对于 RabbitMQ 来说,生产和消费不再针对内存里的一个 Queue 对象,而是某台服务器上的 RabbitMQ Server 实现的消息队列。
安装 python 客户端
pip3 install pika
3.1 生产者
import pika
# 无密码
# connection = pika.BlockingConnection(pika.ConnectionParameters('127.0.0.1'))
# 有密码
credentials = pika.PlainCredentials("admin","admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('101.133.225.166',credentials=credentials))
channel = connection.channel()
# 声明一个队列(创建一个队列)
channel.queue_declare(queue='lqz')
channel.basic_publish(exchange='',
routing_key='lqz', # 消息队列名称
body='hello world')
connection.close()
3.2 消费者
import pika
credentials = pika.PlainCredentials("admin","admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('101.133.225.166',credentials=credentials))
channel = connection.channel()
# 声明一个队列(创建一个队列)
channel.queue_declare(queue='lqz')
def callback(ch, method, properties, body):
print("消费者接受到了任务: %r" % body)
channel.basic_consume(queue='lqz',on_message_callback=callback,auto_ack=True)
channel.start_consuming()
四 消息安全之 ack
4.1 生产者
import pika
# 无密码
# connection = pika.BlockingConnection(pika.ConnectionParameters('127.0.0.1'))
# 有密码
credentials = pika.PlainCredentials("admin","admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('101.133.225.166',credentials=credentials))
channel = connection.channel()
# 声明一个队列(创建一个队列)
channel.queue_declare(queue='lqz')
channel.basic_publish(exchange='',
routing_key='lqz', # 消息队列名称
body='hello world')
connection.close()
4.2 消费者
import pika
credentials = pika.PlainCredentials("admin","admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('101.133.225.166',credentials=credentials))
channel = connection.channel()
# 声明一个队列(创建一个队列)
channel.queue_declare(queue='lqz')
def callback(ch, method, properties, body):
print("消费者接受到了任务: %r" % body)
# 通知服务端,消息取走了,如果auto_ack=False,不加下面,消息会一直存在
# ch.basic_ack(delivery_tag=method.delivery_tag)
channel.basic_consume(queue='lqz',on_message_callback=callback,auto_ack=False)
channel.start_consuming()
五 消息安全之 durable 持久化
5.1 生产者
import pika
# 无密码
# connection = pika.BlockingConnection(pika.ConnectionParameters('127.0.0.1'))
# 有密码
credentials = pika.PlainCredentials("admin","admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('101.133.225.166',credentials=credentials))
channel = connection.channel()
# 声明一个队列(创建一个队列),durable=True支持持久化,队列必须是新的才可以
channel.queue_declare(queue='lqz1',durable=True)
channel.basic_publish(exchange='',
routing_key='lqz1', # 消息队列名称
body='111',
properties=pika.BasicProperties(
delivery_mode=2, # make message persistent,消息也持久化
)
)
connection.close()
5.2 消费者
import pika
credentials = pika.PlainCredentials("admin","admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('101.133.225.166',credentials=credentials))
channel = connection.channel()
# 声明一个队列(创建一个队列)
channel.queue_declare(queue='lqz1')
def callback(ch, method, properties, body):
print("消费者接受到了任务: %r" % body)
# 通知服务端,消息取走了,如果auto_ack=False,不加下面,消息会一直存在
# ch.basic_ack(delivery_tag=method.delivery_tag)
channel.basic_consume(queue='lqz1',on_message_callback=callback,auto_ack=False)
channel.start_consuming()
六 闲置消费
正常情况如果有多个消费者,是按照顺序第一个消息给第一个消费者,第二个消息给第二个消费者
但是可能第一个消息的消费者处理消息很耗时,一直没结束,就可以让第二个消费者优先获得闲置的消息
6.1 生产者
import pika
# 无密码
# connection = pika.BlockingConnection(pika.ConnectionParameters('127.0.0.1'))
# 有密码
credentials = pika.PlainCredentials("admin","admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('101.133.225.166',credentials=credentials))
channel = connection.channel()
# 声明一个队列(创建一个队列),durable=True支持持久化,队列必须是新的才可以
channel.queue_declare(queue='lqz123',durable=True)
channel.basic_publish(exchange='',
routing_key='lqz123', # 消息队列名称
body='111',
properties=pika.BasicProperties(
delivery_mode=2, # make message persistent,消息也持久化
)
)
connection.close()
6.2 消费者
import pika
credentials = pika.PlainCredentials("admin","admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('101.133.225.166',credentials=credentials))
channel = connection.channel()
# 声明一个队列(创建一个队列)
# channel.queue_declare(queue='lqz123')
def callback(ch, method, properties, body):
print("消费者接受到了任务: %r" % body)
# 通知服务端,消息取走了,如果auto_ack=False,不加下面,消息会一直存在
ch.basic_ack(delivery_tag=method.delivery_tag)
channel.basic_qos(prefetch_count=1) #####就只有这一句话 谁闲置谁获取,没必要按照顺序一个一个来
channel.basic_consume(queue='lqz123',on_message_callback=callback,auto_ack=False)
channel.start_consuming()
七 发布订阅
7.1 发布者
import pika
credentials = pika.PlainCredentials("admin","admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('101.133.225.166',credentials=credentials))
channel = connection.channel()
channel.exchange_declare(exchange='m1',exchange_type='fanout')
channel.basic_publish(exchange='m1',
routing_key='',
body='lqz nb')
connection.close()
7.2 订阅者 (启动几次订阅者会生成几个队列)
import pika
credentials = pika.PlainCredentials("admin","admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('101.133.225.166',credentials=credentials))
channel = connection.channel()
# exchange='m1',exchange(秘书)的名称
# exchange_type='fanout' , 秘书工作方式将消息发送给所有的队列
channel.exchange_declare(exchange='m1',exchange_type='fanout')
# 随机生成一个队列
result = channel.queue_declare(queue='',exclusive=True)
queue_name = result.method.queue
print(queue_name)
# 让exchange和queque进行绑定.
channel.queue_bind(exchange='m1',queue=queue_name)
def callback(ch, method, properties, body):
print("消费者接受到了任务: %r" % body)
channel.basic_consume(queue=queue_name,on_message_callback=callback,auto_ack=True)
channel.start_consuming()
八 发布订阅高级之 Routing(按关键字匹配)
8.1 发布者
import pika
credentials = pika.PlainCredentials("admin","admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('101.133.225.166',credentials=credentials))
channel = connection.channel()
channel.exchange_declare(exchange='m2',exchange_type='direct')
channel.basic_publish(exchange='m2',
routing_key='bnb', # 多个关键字,指定routing_key
body='lqz nb')
connection.close()
8.2 订阅者 1
import pika
credentials = pika.PlainCredentials("admin","admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('101.133.225.166',credentials=credentials))
channel = connection.channel()
# exchange='m1',exchange(秘书)的名称
# exchange_type='direct' , 秘书工作方式将消息发送给不同的关键字
channel.exchange_declare(exchange='m2',exchange_type='direct')
# 随机生成一个队列
result = channel.queue_declare(queue='',exclusive=True)
queue_name = result.method.queue
print(queue_name)
# 让exchange和queque进行绑定.
channel.queue_bind(exchange='m2',queue=queue_name,routing_key='nb')
channel.queue_bind(exchange='m2',queue=queue_name,routing_key='bnb')
def callback(ch, method, properties, body):
print("消费者接受到了任务: %r" % body)
channel.basic_consume(queue=queue_name,on_message_callback=callback,auto_ack=True)
channel.start_consuming()
8.3 订阅者 2
import pika
credentials = pika.PlainCredentials("admin","admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('101.133.225.166',credentials=credentials))
channel = connection.channel()
# exchange='m1',exchange(秘书)的名称
# exchange_type='direct' , 秘书工作方式将消息发送给不同的关键字
channel.exchange_declare(exchange='m2',exchange_type='direct')
# 随机生成一个队列
result = channel.queue_declare(queue='',exclusive=True)
queue_name = result.method.queue
print(queue_name)
# 让exchange和queque进行绑定.
channel.queue_bind(exchange='m2',queue=queue_name,routing_key='nb')
def callback(ch, method, properties, body):
print("消费者接受到了任务: %r" % body)
channel.basic_consume(queue=queue_name,on_message_callback=callback,auto_ack=True)
channel.start_consuming()
九 发布订阅高级之 Topic(按关键字模糊匹配)
9.1 发布者
import pika
credentials = pika.PlainCredentials("admin","admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('101.133.225.166',credentials=credentials))
channel = connection.channel()
channel.exchange_declare(exchange='m3',exchange_type='topic')
channel.basic_publish(exchange='m3',
# routing_key='lqz.handsome', #都能收到
routing_key='lqz.handsome.xx', #只有lqz.#能收到
body='lqz nb')
connection.close()
9.2 订阅者 1
- 只能加一个单词
可以加任意单词字符
import pika
credentials = pika.PlainCredentials("admin","admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('101.133.225.166',credentials=credentials))
channel = connection.channel()
# exchange='m1',exchange(秘书)的名称
# exchange_type='direct' , 秘书工作方式将消息发送给不同的关键字
channel.exchange_declare(exchange='m3',exchange_type='topic')
# 随机生成一个队列
result = channel.queue_declare(queue='',exclusive=True)
queue_name = result.method.queue
print(queue_name)
# 让exchange和queque进行绑定.
channel.queue_bind(exchange='m3',queue=queue_name,routing_key='lqz.#')
def callback(ch, method, properties, body):
print("消费者接受到了任务: %r" % body)
channel.basic_consume(queue=queue_name,on_message_callback=callback,auto_ack=True)
channel.start_consuming()
9.3 订阅者 2
import pika
credentials = pika.PlainCredentials("admin","admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('101.133.225.166',credentials=credentials))
channel = connection.channel()
# exchange='m1',exchange(秘书)的名称
# exchange_type='topic' , 模糊匹配
channel.exchange_declare(exchange='m3',exchange_type='topic')
# 随机生成一个队列
result = channel.queue_declare(queue='',exclusive=True)
queue_name = result.method.queue
print(queue_name)
# 让exchange和queque进行绑定.
channel.queue_bind(exchange='m3',queue=queue_name,routing_key='lqz.*')
def callback(ch, method, properties, body):
queue_name = result.method.queue # 发送的routing_key是什么
print("消费者接受到了任务: %r" % body)
channel.basic_consume(queue=queue_name,on_message_callback=callback,auto_ack=True)
channel.start_consuming()
十 基于 rabbitmq 实现 rpc
10.1 服务端
import pika
credentials = pika.PlainCredentials("admin","admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('101.133.225.166',credentials=credentials))
channel = connection.channel()
# 起翰监听任务队列
channel.queue_declare(queue='rpc_queue')
def on_request(ch, method, props, body):
n = int(body)
response = n + 100
# props.reply_to 要放结果的队列.
# props.correlation_id 任务
ch.basic_publish(exchange='',
routing_key=props.reply_to,
properties=pika.BasicProperties(correlation_id= props.correlation_id),
body=str(response))
ch.basic_ack(delivery_tag=method.delivery_tag)
channel.basic_qos(prefetch_count=1)
channel.basic_consume( queue='rpc_queue',on_message_callback=on_request,)
channel.start_consuming()
10.2 客户端
import pika
import uuid
class FibonacciRpcClient(object):
def __init__(self):
credentials = pika.PlainCredentials("admin", "admin")
self.connection = pika.BlockingConnection(pika.ConnectionParameters('101.133.225.166', credentials=credentials))
self.channel = self.connection.channel()
# 随机生成一个消息队列(用于接收结果)
result = self.channel.queue_declare(queue='',exclusive=True)
self.callback_queue = result.method.queue
# 监听消息队列中是否有值返回,如果有值则执行 on_response 函数(一旦有结果,则执行on_response)
self.channel.basic_consume(queue=self.callback_queue,on_message_callback=self.on_response, auto_ack=True)
def on_response(self, ch, method, props, body):
if self.corr_id == props.correlation_id:
self.response = body
def call(self, n):
self.response = None
self.corr_id = str(uuid.uuid4())
# 客户端 给 服务端 发送一个任务: 任务id = corr_id / 任务内容 = '30' / 用于接收结果的队列名称
self.channel.basic_publish(exchange='',
routing_key='rpc_queue', # 服务端接收任务的队列名称
properties=pika.BasicProperties(
reply_to = self.callback_queue, # 用于接收结果的队列
correlation_id = self.corr_id, # 任务ID
),
body=str(n))
while self.response is None:
self.connection.process_data_events()
return self.response
fibonacci_rpc = FibonacciRpcClient()
response = fibonacci_rpc.call(50)
print('返回结果:',response)
十一 集群搭建
11.1 普通集群 (副本集群)
All data/state required for the operation of a RabbitMQ broker is replicated across all nodes. An exception to this are message queues, which by default reside on one node, though they are visible and reachable from all nodes. To replicate queues across nodes in a cluster 默认情况下: RabbitMQ 代理操作所需的所有数据 / 状态都将跨所有节点复制。这方面的一个例外是消息队列,默认情况下,消息队列位于一个节点上,尽管它们可以从所有节点看到和访问
普通集群主节点必须可用,可以同步交换机和队列,但是队列中的消息只能看到,不能同步
核心解决问题: 当集群中某一时刻 master 节点宕机, 可以对 Quene 中信息, 进行备份
11.1.1 搭建
# 1 集群规划
mq1: 10.0.0.100 mq1 master 主节点
mq2: 10.0.0.101 mq2 repl1 副本节点
mq3: 10.0.0.103 mq3 repl2 副本节点
# 2 克隆三台机器主机名和ip映射(建立集群时仅支持按主机名创建,所以我们加好映射)
vim /etc/hosts
# 分别在三台机器上加入(直接通过主机名就可以ping通)
10.0.0.100 mq1
10.0.0.101 mq2
10.0.0.103 mq3
# 3 修改3个节点的主机名(分别在三台机器上)
hostname mq1
hostname mq2
hostname mq3
# 4 分别在三台机器上安装相同版本的rabbitmq-server,并启动(启动就会生成cookie)
启动查看管理web页面能看到,但是不是集群状态
查看集群状态也是孤立的
rabbitmqctl cluster_status
现在我们全停掉(3台机器都停掉)
# 5 三个机器安装rabbitmq,并同步cookie文件,在mq1上执行:(启动一遍,cookie才会有)
cat /var/lib/rabbitmq/.erlang.cookie
scp /var/lib/rabbitmq/.erlang.cookie root@mq2:/var/lib/rabbitmq/
scp /var/lib/rabbitmq/.erlang.cookie root@mq3:/var/lib/rabbitmq/
# 6 查看cookie是否一致:
mq1: cat /var/lib/rabbitmq/.erlang.cookie
mq2: cat /var/lib/rabbitmq/.erlang.cookie
mq3: cat /var/lib/rabbitmq/.erlang.cookie
# 7 后台启动rabbitmq所有节点执行如下命令,启动成功访问管理界面(三台机器都启动,老版本web页面看不到,新版本能看到):
rabbitmq-server -detached
# 8 在mq2和mq3上分别执行加入集群命令:
1.关闭 rabbitmqctl stop_app
2.加入集群 rabbitmqctl join_cluster rabbit@mq1 # 仅支持写主机名
3.启动服务 rabbitmqctl start_app
# 9 查看集群状态,任意节点执行:
rabbitmqctl cluster_status
# 10 如果出现如下显示,集群搭建成功:
Cluster status of node rabbit@mq1 ...
Basics
Cluster name: rabbit@mq1
Disk Nodes
rabbit@mq1
rabbit@mq2
rabbit@mq3
Running Nodes
rabbit@mq1
rabbit@mq2
rabbit@mq3
# 11 登录管理界面,展示如下状态:
注意:如果启动不了,删除/var/lib/rabbitmq/mnesia下的所有问题重新操作
11.1.2 测试集群
# 1 在主节点写操作,从节点是会跟着同步(只同步交换机和队列,队列内数据不同步)
# 2 比如我们在主节点新建一个交换机(exchange)
# 3 我们可以看到其他节点也会出现lqz这个交换机
# 4 我们新建queue,名字为test_queue,让test_queue和exchange绑定
# 5 我们可以看到,其他节点的lqz交换机也跟test_queue绑定了
# 1 队列的数据是不能同步的
# 2 我们发布一条消息
# 3 在queue中可以看到消息
# 1 现在我们挂掉从节点是可以的
# 在mq2上执行
rabbitmqctl stop_app
# 再启动(集群不受影响),再发布消息也是可以的,只要主节点活着就可以
rabbitmqctl start_app
# 5 挂掉主节点,整个服务就不能用了
# 在mq1上执行
rabbitmqctl stop_app
# 集群不能用了(两个从节点不能对外提供服务),如果消息是不持久化的消息,主节点挂了,消息就没了
# 发布消息可以发布到主,从节点;消费消息也可以从主从节点消费
rabbitmqctl start_app
11.2 镜像集群
This guide covers mirroring (queue contents replication) of classic queues -- 摘自官网
By default, contents of a queue within a RabbitMQ cluster are located on a single node (the node on which the queue was declared). This is in contrast to exchanges and bindings, which can always be considered to be on all nodes. Queues can optionally be made mirrored across multiple nodes. -- 摘自官网 镜像队列机制就是将队列在三个节点之间设置主从关系,消息会在三个节点之间进行自动同步,且如果其中一个节点不可用,并不会导致消息丢失或服务不可用的情况,提升 MQ 集群的整体高可用性。
通过镜像,把队列同步到任意一个节点,即便主挂了,从节点也可以提供服务
11.2.1 搭建
# 0.策略说明
rabbitmqctl set_policy [-p <vhost>] [--priority <priority>] [--apply-to <apply-to>] <name> <pattern> <definition>
-p Vhost: 可选参数,针对指定vhost下的queue进行设置
Name: policy的名称
Pattern: queue的匹配模式(正则表达式)
Definition:镜像定义,包括三个部分ha-mode, ha-params, ha-sync-mode
ha-mode:指明镜像队列的模式,有效值为 all/exactly/nodes
all:表示在集群中所有的节点上进行镜像
exactly:表示在指定个数的节点上进行镜像,节点的个数由ha-params指定
nodes:表示在指定的节点上进行镜像,节点名称通过ha-params指定
ha-params:ha-mode模式需要用到的参数
ha-sync-mode:进行队列中消息的同步方式,有效值为automatic(自动)和manual(手动)
priority:可选参数,policy的优先级
# 1.查看当前策略(可以在任意节点上执行)
rabbitmqctl list_policies
# 2.添加策略
rabbitmqctl set_policy ha-all '^lqz' '{"ha-mode":"all","ha-sync-mode":"automatic"}'
# 说明:策略正则表达式为 “^” 表示所有匹配所有队列名称 ^lqz:匹配lqz开头队列
rabbitmqctl set_policy ha-all '^' '{"ha-mode":"all","ha-sync-mode":"automatic"}'
# 对所有队列都做同步
# 3.删除策略
rabbitmqctl clear_policy ha-all
# 4.测试集群
停掉主节点,其他节点依然可以提供服务
此文部分图片及文字引用自互联网,如有侵权,请联系删除