RabbitMQ 笔记一
来源:互联网 发布:淘客群管软件 编辑:程序博客网 时间:2024/06/03 14:39
前言
过往使用一些简单队列功能,直接上redis,包括pub/sub也都可以使用redis完成简单功能。不过既然RabbitMQ作为消息队列非常成熟的组件,还是值得学习使用。演示环境Centos7,MacOS,所有演示代码可去github上下载demo code,演示代码匀为python实现,所用版本python 3.5
概述
简介
Wiki中给出的介绍是:RabbitMQ是实现了高级消息队列协议(AMQP) 的开源消息代理软件(亦称面向消息的中间件)。
AMQP – Advanced Message Queuing Protocol,一个提供统一消息服务的消息队列协议,属于应用层协议。
RabbitMQ官网
RabbitMQ安装
CentOS 7
CentOS 7中可以直接通过yum源安装
# 依赖 erlang等,如果不能自动安装,手动yum安装下各个依赖yum install -y rabbitmq-serversystemctl startrabbitmq-server
手动安装可以去RabbitMQ官网下载
wget https://github.com/rabbitmq/rabbitmq-server/releases/download/rabbitmq_v3_6_12/rabbitmq-server-3.6.12-1.el7.noarch.rpm# 缺少依赖使用yum安装即可rpm -i rabbitmq-server-3.6.12-1.el7.noarch.rpm
docker启动RabbitMQ
docker hub上直接有RabbitMQ的镜像,可以直接使用docker来启动RabbitMQ服务。并且docker RabbitMQ镜像有很多种版本,选用带有RabbitMQ官方提供管理界面的RabbitMQ镜像启动,docker-compose.yml文件内容如下:
services: rbmq: image: "rabbitmq:3-management" container_name: test_rbmq ports: - "15672:15672" - "5672:5672" environment: - RABBITMQ_DEFAULT_USER=tester - RABBITMQ_DEFAULT_PASS=test_password restart: always
直接启动
# docker启动RabbitMQdocker-compose up -d
docker的相关内容可以参见之前的博文:docker 笔记一,docker 笔记二
服务启动后,可以直接访问界面 http://localhost:15672/#/
实际效果:
注意:
1. 截图中 Ports and contexts其中rabbitmq:3-management镜像一个监听了3个端口,分别是5672,15672,25672。其中5672是RabbitMQ服务所监听端口,直接在机器上安装的rabbitmq-server也是通过这个端口进行消息接收发送,15672端口接受的http协议,是控制界面的访问端口。另一个在演示中未用到,因此docker-compose中并未将其开放出来。
2. docker-compose中设置了RabbitMQ的用户名密码,因此使用时必须附带上认证信息才能进行消息交互,后续代码演示中会体现。手动安装的没有设置,可以直接访问。
个人建议使用docker方式启动RabbitMQ,因为控制界面能够直观帮助我们理解RabbitMQ很多基础理念。
基础使用
RabbitMQ架构
Google图库中找了一个比较贴切的RabbitMQ架构图:
使用RabbitMQ必须要明白几个概念:
- exchange:消息交换池,所有发送至RabbitMQ的消息都是发送至exchange中
- queue:消息队列,所有消息的消费都是从queue中取得
- binding:exchange中的消息,有binding确定路由关系,将池中消息送至相应的queue中
- connection:producer和consumer与RabbitMQ连接,是tcp的长连接
- channel:一个应用需要多个连接到RabbitMQ并非创建多个tcp长连接,而是一个tcp连接中多个channel(姑且理解为信道),共享一个tcp连接
详细的原文解释请参见官网
demo1
python调用RabbitMQ需要安装pika包,演示使用版本是0.11.0。
生产者 producer.py 代码段如下:
import pikaimport time# 用户名密码获取认证信息,附带该信息方可成功连接RabbitMQcredentials = pika.PlainCredentials('tester', 'test_password')connection = pika.BlockingConnection(pika.ConnectionParameters( 'localhost', credentials=credentials))# 一个connection中开启多个channelchannel = connection.channel()channel2 = connection.channel()# 声明队列channel.queue_declare(queue='first_queue')i = 0while i < 5: time.sleep(1) a = '{} message'.format(i) channel.basic_publish(exchange='', routing_key='first_queue', body=a) i += 1connection.close()
单独执行 producer.py,管理界面效果如下:
上图可以清晰看出各模块数据信息,点击队列名称可以看到队列详细信息。
消费者 consumer.py 代码段如下:
import pikaimport time# 消费函数def callback(ch, method, properties, body): time.sleep(2) print("consumer_1 [x] Received %r" % (body,)) # ch.basic_ack(delivery_tag=method.delivery_tag)credentials = pika.PlainCredentials('tester', 'test_password')connection = pika.BlockingConnection(pika.ConnectionParameters( 'localhost', credentials=credentials))channel = connection.channel()channel.queue_declare(queue='first_queue')# 为匹配队列指定消费的回调函数channel.basic_consume(callback, queue='first_queue', no_ack=True)print('Consumer_1 Waiting for messages. To exit press CTRL+C')channel.start_consuming()
分别启动demo code default工程中consumer1 和 consumer2,在启动producer产生消息,运行结果如下:
5条消息分别被consumer1 和 consumer2 认领消费。RabbitMQ使用一种循环分发的机制,顺序将到来的message分给每个consumer,当负载增加时,只需添加更多的consumer来完成拓展即可。
消息确认
上面的demo中在队列声明时指定了no_ack=True,也就是说当消息被consumer认领后,就认为该message已经被消费,此时若consumer异常退出未能正常消费该message,那么此条message就丢了。为此可以在消费端声明使用队列时默认使用需要ack的方式。
修改consumer代码,删除no_ack=True,并且在consumer2修改callback函数,添加1/0,中断consumer2。执行程序如下:
注意
1. 如果consumer2消费后没有发送确认,那么consumer2所有处理过的message在consumer2退出后会被重新发送给其他consumer
2. callbcck函数编写其参数时回调式默认传入的四个参数,可以进入 channel.basic_consume 方法中查看,源码注释中已经给出详细解释。
消息持久化
上文的ack确认可以保证消息被正常消费,可是RabbitQM 服务如果在消费尚未进行出现停止,此时已进入RabbitQM 队列中的消息会如何?维持上文produecr代码并执行完,进入Queue管理界面此时能看到first_queue队列中存有5条message。重启RabbitQM后登陆管理界面 first_queue已丢失。
RabbitQM中持久化首先是队列的持久化,调整producer中声明队列代码:
# 声明持久化队列channel.queue_declare(queue='first_queue', durable=True)
重复之前操作,此时队列first_queue在重启RabbitQM仍然存在,但是其中的消息没有了,已经做到了队列持久化。
在此调整producer中代码如下:
# 将发布的消息设置为持久化channel.basic_publish( exchange='', routing_key='first_queue', body=a, properties=pika.BasicProperties( delivery_mode=2, # make message persistent ))
再次重复操作,重启RabbitQM后队列,消息均维持原样。
注意:
delivery_mode=2是一个常量设置,在paki包的spec.py中定义了BasicProperties类,并且定义了
TRANSIENT_DELIVERY_MODE = 1
PERSISTENT_DELIVERY_MODE = 2
两个常量用以区分短暂消息和持久消息。在RabbitQM官网中规定了delivery_mode 1和2分别指定的消息类型。
路由算法
exchange有三种类型,官网中给出了相应的解释,分别是:
- direct:routing_key匹配,exchange将消息发送给所有routing_key匹配的队列中,但不是重复发送,一个消息只会发送给所有匹配routing_key队列中的一个,此种模式多用于负载均衡的消费形式
- fanout:广播模式,类似网络广播一样所有属于该exchange中的队列都能收到
- topic:通过队列设定的routing_key pattern,exchange 将消息发送给所有匹配的对列,只要匹配,队列就会收到消息,这是其与direct不同之处,topic多用于pub/sub的功能场景。
direct demo
demo code 工程中分别执行两个consumer1,和一个consumer2,之后执行producer,执行结果如下:
是否觉得仿佛与之前说明的direct 并不相同?两个consumer1都接收到消息。
producer代码如下:
import pikaimport timecredentials = pika.PlainCredentials('tester', 'test_password')connection = pika.BlockingConnection(pika.ConnectionParameters( 'localhost', credentials=credentials))channel = connection.channel()channel.exchange_declare(exchange='direct_exchange', exchange_type='direct')i = 0while i < 5: time.sleep(1) a = '{} message'.format(i) if i < 2: channel.basic_publish( exchange='direct_exchange', routing_key='mini', body=a) else: channel.basic_publish( exchange='direct_exchange', routing_key='max', body=a) i += 1connection.close()
consumer1中代码如下:
import pikaimport timedef callback(ch, method, properties, body): time.sleep(2) print("consumer [x] Received %r" % (body,)) ch.basic_ack(delivery_tag=method.delivery_tag)credentials = pika.PlainCredentials('tester', 'test_password')connection = pika.BlockingConnection(pika.ConnectionParameters( 'localhost', credentials=credentials))channel = connection.channel()channel.exchange_declare(exchange='direct_exchange', exchange_type='direct')result = channel.queue_declare(exclusive=True)queue_name = result.method.queuechannel.queue_bind( exchange='direct_exchange', routing_key='mini', queue=queue_name)channel.basic_consume(callback, queue=queue_name)print(' [*] Waiting for messages. To exit press CTRL+C')channel.start_consuming()
与之前的demo代码相比,producer端没有声明队列,而是将消息交exchange,而在consumer1中声明队列多了exclusive=True参数,表示队列跟consumer同步存在,consumer退出该队列也就随即消失。当启动多个consumer1时,实际产生了多个丢列,每个consumer都是消费自己队列中的,管理控制台显示:
因此可以修改consumer1文件:
# 固定队列名称,去除exclusive=True参数,否则无法启动多个channel.queue_declare(queue='mini_queue')channel.queue_bind( exchange='direct_exchange', routing_key='mini', queue='mini_queue')channel.basic_consume(callback, queue='mini_queue')print(' [*] Waiting for messages. To exit press CTRL+C')channel.start_consuming()
再次启动两个consumer1执行代码结果如下:
可以看出,两个consumer1是同时工作消费mini_queue队列中的消息,不会重复消费。
fanout demo
fanout是一种广播模式,所有处于广播模式exchange中的队列都会收到消息,类似上文方式,启动两个consumer1,一个consumer2,执行producer发送消息,结果如下:
此时 consumer1所用队列和consumer2所用队列均收到了5条message,并且consumer1启动了两个,同时消费其队列内容。具体代码内容请参见工程中示例,在管理控制界面,代码中所声明的 fanout_exchange 信息如下:
topic demo
主题形式的实用场景,诸如订阅微信公众号,参与群聊,都是此种形式的消息分发。topic类型的exchange中的routing_key是有一定规范写法的,两种特殊符号:
- ‘*’:表示任意一个单词
- ‘#’:表示0个或多个单词
producer代码如下:
import pikaimport timecredentials = pika.PlainCredentials('tester', 'test_password')connection = pika.BlockingConnection(pika.ConnectionParameters( 'localhost', credentials=credentials))channel = connection.channel()channel.exchange_declare(exchange='topic_exchange', exchange_type='topic')routing_mapping = { 0: 'tech.front', 1: 'tech.end.t1', 2: 'hr.g1.t1', 3: 'hr.g2.t1', 4: 'hr.g2.t2.',}i = 0while i < 5: time.sleep(1) a = '{} message'.format(i) routing = routing_mapping[i % 5] channel.basic_publish( exchange='topic_exchange', routing_key=routing, body=a) i += 1connection.close()
consumer1 代码如下:
import pikaimport timedef callback(ch, method, properties, body): time.sleep(2) print("consumer [x] Received %r" % (body,)) ch.basic_ack(delivery_tag=method.delivery_tag)credentials = pika.PlainCredentials('tester', 'test_password')connection = pika.BlockingConnection(pika.ConnectionParameters( 'localhost', credentials=credentials))channel = connection.channel()channel.exchange_declare(exchange='topic_exchange', exchange_type='topic')result = channel.queue_declare(exclusive=True)queue_name = result.method.queuechannel.queue_bind( exchange='topic_exchange', routing_key='*.*.t1', queue=queue_name)channel.basic_consume(callback, queue=queue_name)print('Consumer1 waiting for messages. To exit press CTRL+C')channel.start_consuming()
consumer1 订阅了所有 第三级是 t1的路径下的消息,consumer2的 routing_key=’tech.#’表示订阅所有 一级 tech 下的全部消息,producer根据mapping表发送消息,启动consumer1,consumer2执行producer,结果如下:
小结
至此RabbitMQ的基础框架,各种消息分发形式,python如何使用RabbitMQ通信基本介绍完毕,高阶功能再行规整。
- rabbitmq学习笔记一
- RabbitMQ 笔记一
- RabbitMQ学习笔记(一)
- rabbitMq使用笔记一:Window下安装使用RabbitMQ
- rabbitMq使用笔记一:Window下安装使用RabbitMQ
- Rabbitmq 学习笔记(一)简介
- rabbitmq-c++(SimpleAmqpClient) 笔记代码一
- RabbitMQ(一)
- RabbitMq(一)
- RabbitMQ笔记
- rabbitmq 笔记
- rabbitmq笔记
- rabbitmq笔记
- RabbitMQ 笔记
- RabbitMQ 笔记
- RabbitMQ 笔记
- RabbitMQ笔记
- rabbitMQ学习笔记(一) ubuntu12.4 与Windows 下rabbitMQ的安装
- IPU到底是个什么鬼?
- Spring源码解析——如何阅读源码
- JS基础——深浅拷贝
- SimpleDateFormat的坑
- STM8S 模拟I2C程序
- RabbitMQ 笔记一
- 第10周项目1-验证算法(1)层次遍历算法的验证
- 出栈顺序和卡特兰数的关系
- Kotlin系列课程——Kotlin初始化控件及界面跳转
- ThreadLocal应用场景以及源码分析
- 简单实现Tensorflow CNN图像训练
- 数据库修改预约数据后,软件无法刷新,需要退出pl/sql,
- oracle存储过程(procedure)
- tensorsor快速获取所有变量,和快速计算L2范数