本系列博客主要从实践理论两个角度带着大家一步一步认识了解熟悉RabbitMQ,总共分为基础篇、进阶篇、高级篇。基础篇主要介绍一些 MQ 的基础概念,RabbitMQ 的历史背景,以及同类型的一些技术,搭建运行环境并且编写一个简单的程序,进阶篇我们将使用 RabbitMQ 做更多的实验,以实践的方式深入学习,高级篇会讲解一些 RabbitMQ 的高级应用,如何搭建 RabbitMQ 的高可用基础架构以及 RabbitMQ 集成的一些插件,并且总结 RabbitMQ 的设计思想等。
MQ 让我们的请求和处理实现了分离,客户端的请求服务器端可以异步的执行,执行完成后返回结果,这种解耦极大的提升了服务器响应客户端请求的能力,请求的异步化降低了服务器同一时间需要处理的请求数,对于我们构建高性能服务有很大的帮助。
关键字解释:
0、AMQP协议:
AMQP:Advanced Message Queuing Protocol 高级消息队列协议,是一个异步消息传递所使用的应用层协议规范。
1、ErLang:
Erlang是一种通用的并行程序设计语言,它由乔·阿姆斯特朗(Joe Armstrong)在瑞典电信设备制造商爱立信所辖的计算机科学研究室开发,目的是创造一种可以应付大规模开发活动的程序设计语言和运行环境。Erlang于1987年发布正式版本,最早是爱立信拥有的私有软件,经过十年的发展,于1998年发表开放源代码版本。
Erlang是运作于虚拟机的解释型语言,但是现在也包含有乌普萨拉大学高性能Erlang计划(HiPE)[2]开发的原生代码编译器,自R11B-4版本开始,Erlang也支持脚本方式执行。在编程范型上,Erlang属于多重范型编程语言,涵盖函数式、并行及分布式。循序运行的Erlang是一个及早求值, 单次赋值和动态类型的函数式编程语言。
语言特色:
跨进程通信:
在并行程序设计语言中,可以使用spawn函数,将特定的函数设置为独立的进程,之后可以做跨进程通信。
函数式程序设计
由于Erlang早期以Prolog开发制成,受语言特性影响,即成为函数式语言。
快速失败
在运行时期发生的错误,会由错误位置提交消息,发生错误的进程立刻停止执行。借由进程通讯机制,可以自动传递错误、捕捉错误,使其他进程能够帮助处理错误。
2、生产者:
消息的生成者和发送者,是整个消息生命周期的起点,创建消息设置标签,然后与RabbitMQ代理服务器(Erlang节点)建立连接,并且发送消息到对应的服务器上。
3、消费者:
消息的接受者和使用者,接收来自RabbitMQ代理服务器的消息,解析请求并且根据内容给出响应,注意消费者只接收到整条消息的一部分:payload,消息路由的过程中,消息的标签没有随着有效载荷一起传递;消费者接收的每一条消息都需要确认,只有确认了以后 RabbitMQ 才能安全地将服务器上的数据删除,并且发送新的消息给消费者。
4、消息:
消息由两部分组成:有效载荷(payload)和标签(label),有效载荷是生产者需要传输的具体数据内容,标签是对数据的描述,相当于元数据,消息发送的过程中只有payload 进行传播,label 不会发送给消费者。
5、队列:
队列相当于邮箱服务器的一个具名邮箱,RabbitMQ服务器相当于是邮局,一个邮局有多个邮箱,一个邮箱对应多个接收人,接收人会随时查看邮箱的来信,接收人对应这里的消费者,消费者订阅队列,并且不断的从队列中接收消息进行处理,当一个队列上有多个订阅的消费者的时候,消息会以循环(round-robin)的方式分发给不同的消费者。
6、交换器:
类似于路由器,完成消息的分发,一个交换器可能绑定多个队列,多个交换器也可能绑定同一个队列,交换器根据路由键(routing key)将消息路由到对应的队列上。交换器的类型总共有四种:
7、绑定:
交换器(exchange) 与队列(queue) 之间存在一个绑定关系,这种绑定关系主要通过两个命令完成:basic.publish(body, exchange, routing_key), queue.bind(queue, exchange, routing_key)。
例如:basic.publish(“log”, “logs_exchange”, “error.log”), queue_bind(“msg_log_queue”, “logs_exchange”, “error.“),通过这样的绑定,logs_exchange 交换器上面所有 error. 的日志都会发送到 msg_log_queue 队列。
8、虚拟主机:
一台RabbimtMQ服务器上面可以创建多个虚拟主机 vhost,每个虚拟主机之间相互隔离,每个虚拟主机都拥有自己的交换器、队列、绑定。安装 RabbitMQ 后会生成默认的 vhost : “/”, 缺省的用户名密码是 guest/guest,RabbitMQ 的权限控制是以 vhost 为单位的,vhost 上给不同的用户设置了不同的权限。使用 vhost 可以保证通信架构的安全性和可移植性,在集群上创建 vhost 的时候,整个集群都会创建该 vhost.
那么如何创建呢:通过客户端工具 rabbitmqctl 完成创建。
rabbitmqctl add_vhost[vhost_name]: 创建 vhost
rabbitmqctl delete_vhost[vhost_name]:删除 vhost
rabbitmqctl list_vhosts:列出所有 vhost
9、持久化:
实现方式:
原理:
磁盘上创建一个持久化日志文件,消息如果被发送到非持久化队列,那么这条消息会被删除,持久化消息如果被消费了,持久化日志中的消息被标记为等待垃圾收集,服务器重启,持久化日志文件中的消息会重播,所以持久化日志文件中记录的消息都是持久化的消息,这部分消息在服务器重启后都可以进行恢复,所以一般会将一些重要的消息设置为持久化的,但是因为需要进行不断的磁盘读写操作,性能不是很好,会降低 RabbitMQ 的消息吞吐量,有可能性能下降 10 倍之多,而且在内建集群环境下工作的不好(因为内建集群数据不做冗余)
10、策略:
(1)持久化事务:AMQP 事务不同于数据库事务,多条命令形成一个事务,只有第一条命令执行成功后才执行其他的命令。同步的,性能很差。
(2)发送方确认模式(confirm 模式):所有信道上发布的消息都会被指派一个唯一的 ID 号(从 1 开始),一旦消息被投递给所有匹配的队列后,信道会发送一个发送方确认模式给生产者应用程序(包含消息的唯一 ID),这样生产者就知道消息已经发送成功了,如果消息和队列都是可持久化的,那么该确认消息会在队列将消息写入磁盘后才发出,发送方确认模式最大的优点就是异步的,生产者可以在等确认消息的同时发送下一条消息,如果收到的是 nack(未确认)消息,那么该消息重发。
11、黑洞: 路由消息找不到对应的队列,那么消息将进入“黑洞”。
12、死信: 被消费者拒绝并且没有重新入队的“垃圾(异常)消息”,存放到死信队列。
常用命令:
basic.get:向队列请求单条消息进行消费。
basic.ack:消费者显式地确认消息已收到。
basic.reject:消费过程中发生错误,当前服务器无法处理该消息,显式的拒绝继续发送,requeue 参数设置为 true, 则RabbitMQ 会将该消息分发给其它订阅的消费者。requeue 参数设置为 false,则 RabbitMQ 会将该消息从队列中移除,这部分消息进入“死信”(存放被拒绝且不能重新入队的消息)。
queue.declare:创建队列,一般需要指定名称,不指定会随机地分配一个,用于创建“临时匿名”队列。
basic.publish(body, exchange, routing_key):消息发送,从交换器发送到对应的队列,msg 消息通过默认交换器发送到对应的队列上。发送消息可以指定消息体、目标交换器以及对应交换器上的队列。
$channel -> basic.publish($msg, ‘’, ‘queue_name’)
exchange.declare: 创建交换器
queue.bind(queue, exchange, routing_key):交换器队列绑定
basic.consume(msg_consumer, queue, consumer_tag): 消费者订阅队列,一旦订阅,将持续不断的监控队列,消费(拒绝)完一条后继续接收下一条。
basic.cancel:取消订阅,消费者不再订阅该队列
start_consuming:消费者开始监听队列
stop_consuming:消费者停止监听队列
一般的消费者流程:
basic.consume(订阅)–> start_consuming(开始监听) –> basic.cancel (取消订阅)–> stop_consuming(停止监听)
(1)下载安装 RabbitMQ:rabbitmq-server-3.6.9.exe
(2)配置启动服务器
配置环境变量:
D:\Otp\erl8.3\bin 添加到环境变量 path 里面
启动服务器:
cd 到 rabbitmq 的 sbin 目录
rabbitmq-server.bat start:启动 rabbitmq 服务器:5672 rabbitmq, 15672 客户端插件, 25672 集群, 4369 epmd
查看启动状态:
rabbitmqctl.bat status : 查看 erlang 节点状态
rabbitmq-plugins.bat enable rabbitmq_management: 允许插件访问
访问路径:http://localhost:15672/
创建用户并授权
rabbitmqctl.bat add_user lijl85 test: 创建用户
rabbitmqctl.bat set_permissions “.” “.” “.*”: 授权,三个参数分别对应配置、读、写权限。
(3)客户端接入:
python 接入:使用 pip 安装皮卡丘(pika: RabbitMQ 的 python 实现)
/*
* 引入 pika
*/
import json, pika
# coding = utf-8
# broker_util.py:get_channel 可以获取代理服务器的信道
import pika
# 获取代理服务器信道
def get_channel(vhost, server, user, password):
AMQP_VHOST = vhost
AMQP_SERVER = server
AMQP_USER = user
AMQP_PASS = password
credentials_broker = pika.PlainCredentials(AMQP_USER, AMQP_PASS)
conn_params = pika.ConnectionParameters(credentials=credentials_broker, virtual_host=AMQP_VHOST, host=AMQP_SERVER)
connection = pika.BlockingConnection(conn_params)
return connection.channel()
# producer.py:生产者,发出 Hello World 消息
import json, pika
import common.broker_util as broker;
channel = broker.get_channel(vhost="/", server="localhost", user="lijl85", password="test")
channel.exchange_declare(exchange="hello_exchange", type="direct", auto_delete=False)
channel.queue_declare(queue="hello_queue", auto_delete=False)
channel.queue_bind(queue="hello_queue", exchange="hello_exchange", routing_key="hello")
msg = json.dumps({"data": "Hello RabbitMQ World!"})
msg_props = pika.BasicProperties()
msg_props.content_type = "application/json"
msg_props.durable = False # 非持久化消息
channel.basic_publish(properties=msg_props, body=msg, exchange="hello_exchange", routing_key="hello")
# consumer.py:消费者
import json
import common.broker_util as broker;
channel = broker.get_channel(vhost="/", server="localhost", user="lijl85", password="test")
channel.exchange_declare(exchange="hello_exchange", type="direct", auto_delete=False)
channel.queue_declare(queue="hello_queue", auto_delete=False)
channel.queue_bind(queue="hello_queue", exchange="hello_exchange", routing_key="hello")
def msg_handler(hello_channel, method, hello_header, body):
msg = json.loads(body)
print(msg.get("data"))
channel.basic_ack(delivery_tag=method.delivery_tag)
channel.basic_consume(consumer_callback=msg_handler, queue="hello_queue", no_ack=False, consumer_tag="hello_consumer")
channel.start_consuming()
broker_util 是单独封装的模块,用于获取与代理服务器的信道,需要传入当前使用的 vhost, server, user, password,用户名密码可以在 RabbitMQ Management Web UI 中设置,使用的时候先启动 consumer.py,然后执行 producer.py 就可以看到一条消息发送。
通过登录 http://localhost:15672 我们可以看到关于该 erlang 节点的各种信息以及消息发送的情况,RabbitMQ Web UI 比 rabbitmqctl 工具更强大的一点是它可以看到消息的分类:准备发送的消息 + 确认的消息 + 未确认的消息 + 总消息条数。直观而且友好。