1 rabbitmq前提
1-1 什么是rabbitmq
实现发布/订阅,异步处理,或者工作队列
2-2 rabbitmq的安装
docker-compose.yml
version: "3"
services:
rabbitmq:
container_name: rabbitmq
ports:
- 15672:15672
- 5672:5672
restart: always
volumes:
- /etc/localtime:/etc/localtime
- /home/mycontainers/rabbitmq/rabbitmq:/var/lib/rabbitmq
environment:
- RABBITMQ_DEFAULT_USER=admin
- RABBITMQ_DEFAULT_PASS=admin
image: rabbitmq:3.8.34-management
docker命令
docker container ls -a # 查看docker所有的容器
docker image ls -a # 查看docker所有的镜像
docker-compose up -d # 使用docker-compose 部署项目,加d表示后台运行
docker system prune -f -a # 清理没有用的镜像
2 rabbitmq简单使用
2-1 rabbitm1初始
producer
#生产者
import pika
#链接rabbitmq
creadentials = pika.PlainCredentials(username='用户名',password='密码')
connection = pika.BlockingConnection(pika.ConnectionParameters(host='地址',port=端口))
channel = connection.channel()
#创建队列
channel.queue_declare(queue='ceshi')
#向指定队列插入数据 [exchange:交换机模式,简单模式为空 routing_key:指定队列 body:要插入的值]
channel.basic_publish(exchange='',routing_key='ceshi',body='hello world!')
print('[x]--')
consumer
#消费者
import pika
#创建链接
pika.PlainCredentials(username='用户名',password='密码')
connection = pika.BlockingConnection(pika.ConnectionParameters(host='地址',port=端口))
channel = connection.channel()
#创建队列
channel.queue_declare(queue='ceshi')
#确定回调函数
def callback(ch,method,properties,body):
print("[x]:",body)
#确定监听队列 [auto_ack:默认应答]
channel.basic_consume(queue='ceshi',auto_ack=True,on_message_callback=callback)
print('[*]:---')
#正式监听
channel.start_consuming()
2-2 参数说明
1.应答参数
消费者确定监听队列时的一个参数
auto_ack
True:默认应答:
从队列取走一条数据后队列里这条数据就不存在了,如果在数据处理过程中程序出现问题,会造成数据丢失现象
False:手动应答:
从队列取走一条数据后,这条数据还存在于队列中
ch.basic_ack(delivery_tag=method.delivery_tag)添加在回调函数最后
最后执行这条命令会告诉队列,我已经执行完成了,可以删除这条数据了
手动应答必然会影响效率,具体根据项目需求抉择:是追求数据安全还是效率
2.持久化
将数据保存到磁盘,防止程序运行中途rabbitmq服务异常导致数据丢失
生产者
创建队列时进行申明 durable=True
channel.queue_declare(queue='ceshi',durable=True)
插入数据时申明 properties=pika.BasicProperties(delivery_mode=2)
channel.basic_publish(exchange='',
routing_key='ceshi',
body='hello world!',
properties=pika.BasicProperties(
delivery_mode=2)
)
消费者
创建队列时进行申明 durable=True
channel.queue_declare(queue='ceshi2',durable=True)
3.分发参数
轮询分发
正常开启多个消费者都是轮询分发,比如队列有8个数据,每人4个
公平分发
处理快的获取到的数据便越多,,公平分发需要使用手动应答方式才可以
消费者添加 channel.basic_qos(prefetch_count=1)
3 AMQP
3-1 模型简介
AMQP 0-9-1的工作过程如下图:消息(message)被发布者(publisher)发送给交换机(exchange),交换机常常被比喻成邮局或者邮箱。然后交换机将收到的消息根据路由规则分发给绑定的队列(queue)。最后AMQP代理会将消息投递给订阅了此队列的消费者,或者消费者按照需求自行获取。
发布者(publisher)发布消息时可以给消息指定各种消息属性(message meta-data)。有些属性有可能会被消息代理(brokers)使用,然而其他的属性则是完全不透明的,它们只能被接收消息的应用所使用。
从安全角度考虑,网络是不可靠的,接收消息的应用也有可能在处理消息的时候失败。基于此原因,AMQP模块包含了一个消息确认(message acknowledgements)的概念:当一个消息从队列中投递给消费者后(consumer),消费者会通知一下消息代理(broker),这个可以是自动的也可以由处理消息的应用的开发者执行。当“消息确认”被启用的时候,消息代理不会完全将消息从队列中删除,直到它收到来自消费者的确认回执(acknowledgement)。
在某些情况下,例如当一个消息无法被成功路由时,消息或许会被返回给发布者并被丢弃。或者,如果消息代理执行了延期操作,消息会被放入一个所谓的死信队列中。此时,消息发布者可以选择某些参数来处理这些特殊情况。
队列,交换机和绑定统称为AMQP实体(AMQP entities)
3-2 交换机
交换机是用来发送消息的AMQP实体。交换机拿到一个消息之后将它路由给一个或零个队列。它使用哪种路由算法是由交换机类型和被称作绑定(bindings)的规则所决定的。AMQP 0-9-1的代理提供了四种交换机
| Name(交换机类型) | Default pre-declared names(预声明的默认名称) |
|---|---|
| Direct exchange(直连交换机) | (Empty string) and amq.direct |
| Fanout exchange(扇型交换机) | amq.fanout |
| Topic exchange(主题交换机) | amq.topic |
| Headers exchange(头交换机) | amq.match (and amq.headers in RabbitMQ) |
更多AMQP介绍请查看
rabbitmq.mr-ping.com/AMQP/AMQP_0…
4 rabbit中灵活使用交换机
4-1 扇形交换机(发布订阅模式)
producer.py
import pika
from rabbitmq.config import MQ_USER, MQ_PWD, MQ_HOST, MQ_PORT
# 创建链接
credentials = pika.PlainCredentials(MQ_USER, MQ_PWD)
connection = pika.BlockingConnection(pika.ConnectionParameters(MQ_HOST, MQ_PORT, '/', credentials=credentials))
channel = connection.channel()
# 创建一个扇形交换机
channel.exchange_declare(exchange='logs', exchange_type='fanout')
channel.basic_publish(exchange='logs', routing_key='', body='Hello World!')
print('[x]---')
connection.close()
consumer
# 消费者
import pika
from rabbitmq.config import MQ_USER, MQ_PWD, MQ_HOST, MQ_PORT
# 创建链接
credentials = pika.PlainCredentials(MQ_USER, MQ_PWD)
connection = pika.BlockingConnection(pika.ConnectionParameters(MQ_HOST, MQ_PORT, '/', credentials=credentials))
channel = connection.channel()
# 声明一个与生产者名称类型相同的交换机,避免先启动消费者-队列找不到交换机的情况
channel.exchange_declare(exchange='logs',
exchange_type='fanout') # fanout:发布订阅模式
# 创建队列 exclusive:系统会创建一个随机唯一的队列名
result = channel.queue_declare(queue='', exclusive=True)
queue_name = result.method.queue
print(queue_name)
# 将指定队列绑定到交换机上
channel.queue_bind(exchange='logs',
queue=queue_name)
# 确定回调函数
def callback(ch, method, properties, body):
print("[x]:", body)
# 确定监听队列 [auto_ack:默认应答]
channel.basic_consume(queue=queue_name,
auto_ack=True,
on_message_callback=callback)
print('[*]:---')
# 正式监听
channel.start_consuming()
4-2 直连交换机
producer
# 生产者
import pika
from rabbitmq.config import MQ_USER, MQ_PWD, MQ_HOST, MQ_PORT
# 链接rabbitmq
credentials = pika.PlainCredentials(MQ_USER, MQ_PWD)
connection = pika.BlockingConnection(pika.ConnectionParameters(MQ_HOST, MQ_PORT, '/', credentials=credentials))
channel = connection.channel()
# 声明一个名为logs类型为direct的交换机
channel.exchange_declare(exchange='fanout_queen',
exchange_type='direct') # direct:关键字模式
# 向logs交换机插入数据 hello world! routing_key为关键字
channel.basic_publish(exchange='fanout_queen',
routing_key='info',
body='hello world!'.encode())
print('[x]---')
connection.close()
consumer
# 生产者
import pika
from rabbitmq.config import MQ_USER, MQ_PWD, MQ_HOST, MQ_PORT
# 链接rabbitmq
credentials = pika.PlainCredentials(MQ_USER, MQ_PWD)
connection = pika.BlockingConnection(pika.ConnectionParameters(MQ_HOST, MQ_PORT, '/', credentials=credentials))
channel = connection.channel()
# 声明一个与生产者名称类型相同的交换机,避免先启动消费者-队列找不到交换机的情况
channel.exchange_declare(exchange='fanout_queen',
exchange_type='direct') # direct:关键字模式
# 创建队列 exclusive:系统会创建一个随机唯一的队列名
result = channel.queue_declare(queue='', exclusive=True)
queue_name = result.method.queue
print(queue_name)
# 将指定队列绑定到交换机上,routing_key:关键字,多个关键字绑定多个
channel.queue_bind(exchange='fanout_queen',
queue=queue_name,
routing_key='info')
channel.queue_bind(exchange='fanout_queen',
queue=queue_name,
routing_key='error')
# 确定回调函数
def callback(ch, method, properties, body):
if isinstance(body, bytes):
body = body.decode()
print("[x]:", body)
# 确定监听队列 [auto_ack:默认应答]
channel.basic_consume(queue=queue_name,
auto_ack=True,
on_message_callback=callback)
print('[*]:---')
# 正式监听
channel.start_consuming()
4-3 主题交换机(通配符模式)
producer
# 生产者
import pika
from rabbitmq.config import MQ_USER, MQ_PWD, MQ_HOST, MQ_PORT
# 链接rabbitmq
credentials = pika.PlainCredentials(MQ_USER, MQ_PWD)
connection = pika.BlockingConnection(pika.ConnectionParameters(MQ_HOST, MQ_PORT, '/', credentials=credentials))
channel = connection.channel()
# 声明一个名为logs类型为direct的交换机
channel.exchange_declare(exchange='topic_queen',
exchange_type='topic') # direct:关键字模式
# 向logs交换机插入数据 hello world! routing_key为关键字
channel.basic_publish(exchange='topic_queen',
routing_key='andy.yongge',
body='大家好啊'.encode())
print('[x]---')
connection.close()
consumer
# 生产者
import pika
from rabbitmq.config import MQ_USER, MQ_PWD, MQ_HOST, MQ_PORT
# 链接rabbitmq
credentials = pika.PlainCredentials(MQ_USER, MQ_PWD)
connection = pika.BlockingConnection(pika.ConnectionParameters(MQ_HOST, MQ_PORT, '/', credentials=credentials))
channel = connection.channel()
# 声明一个与生产者名称类型相同的交换机,避免先启动消费者-队列找不到交换机的情况
channel.exchange_declare(exchange='topic_queen',
exchange_type='topic') # direct:关键字模式
# 创建队列 exclusive:系统会创建一个随机唯一的队列名
result = channel.queue_declare(queue='', exclusive=True)
queue_name = result.method.queue
print(queue_name)
# 将指定队列绑定到交换机上,routing_key:关键字,多个关键字绑定多个
channel.queue_bind(exchange='topic_queen',
queue=queue_name,
routing_key='#.yongge')
# 确定回调函数
def callback(ch, method, properties, body):
if isinstance(body, bytes):
body = body.decode()
print("[x]:", body)
# 确定监听队列 [auto_ack:默认应答]
channel.basic_consume(queue=queue_name,
auto_ack=True,
on_message_callback=callback)
print('[*]:---')
# 正式监听
channel.start_consuming()