Rabbitmq
基本信息
- 实现了高级消息队列协议(AMQP)的开源消息代理中间件
- 启动停止:
rabbitmqctl start_app
; rabbitmqctl stop
- 后台启动:
rabbitmq-server -detached
- 如果不启动RabbitMQ 管理插件,是无法通过Web访问的:
rabbitmq-plugins enable rabbitmq_management
- http://127.0.0.1:15672/ 查看消息队列 (account:guest;pwd:guest)
- RabbitMQ中所有的消息都要先通过交换机,空字符串表示使用默认的交换机
- rabbitmq服务挂了,那么消息队列里的消息将会全部丢失:声明队列为可持久化存储队列,并且在生产者将消息插入到消息队列时,设置消息持久化存储
channel.queue_declare(queue=self.qname,durable=True)
channel.basic_publish(exchange='', routing_key=qname, body=body, properties=pika.BasicProperties(delivery_mode=2))
- 如果在消费者获取到队列里的消息后,在回调函数的处理过程中,消费者突然出错或程序崩溃等异常,那么就会造成这条消息并未被实际正常的处理掉。
- 为了解决这个问题,我们只需在消费者
basic_consume(auto_ack=False)
; - 并在回调函数中设置手动应答即可
channel.basic_ack(delivery_tag=method.delivery_tag)
- 一个生产者和多个消费者,即多个消费者同时监听一个消息队列,这时候队列里的消息就是轮询分发(即如果消息队列里有100条信息,如果有2个消费者,那么每个就会收到50条信息),但是在某些情况下,不同的消费者处理任务的能力是不同的,这时还按照轮询的方式分发消息并不是很合理,那么只需要再配合手动应答的方式,设置消费者接收的消息没有处理完,队列就不要给我放送新的消息即可
channel.basic_qos(prefetch_count=1)
限制每次只发送不超过1条消息到同一个消费者,消费者必须手动反馈告知队列,才会发送下一个- 必须在
channel.basic_consume
之上,否则不生效
- 一些RabbitMQ客户端(Bunny,Java,.NET,Objective-C,Swift)提供了一种在网络故障后自动恢复连接的机制,而pika只能通过检测连接异常后再重新创建连接的方式
模板
import random
import time
import pika
from loguru import logger
from functools import wraps
from pika.exceptions import AMQPConnectionError, ChannelClosedByBroker, ConnectionClosedByBroker, ChannelWrongStateError
from apscheduler.schedulers.blocking import BlockingScheduler
from apscheduler.triggers.cron import CronTrigger
class RabbitmqPackage:
def __init__(self, user, pwd, host):
self.user, self.pwd = user, pwd
self.host = host
self.flow_control = "NORMAL"
def retry2(self):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
while True:
try:
return func(*args, **kwargs)
except (AMQPConnectionError, ChannelClosedByBroker, ConnectionClosedByBroker, ChannelWrongStateError) as e:
logger.error(e)
if self.flow_control == "NORMAL":
self.init_connection()
else:
print("主动强制断开链接")
return False
except Exception as e:
logger.error(e)
return False
time.sleep(random.randint(3, 5))
return wrapper
return decorator
def change_flow_control(self, status="STOP"):
self.flow_control = status
logger.info(f"将当前状态改为:{status}")
if self.flow_control == "STOP" and hasattr(self, "connection"):
self.channel.close()
self.connection.close()
print("链接通道关闭")
return True
def init_connection(self):
while True:
try:
print(f"开始链接到{self.host}")
credentials = pika.PlainCredentials(self.user, self.pwd)
connection = pika.BlockingConnection(
pika.ConnectionParameters(socket_timeout=10, host=self.host, port=5672, virtual_host='/',
credentials=credentials, heartbeat=20))
channel = connection.channel()
return connection, channel
except pika.exceptions.AMQPConnectionError:
logger.error("链接错误")
except Exception as e:
logger.error("其他错误")
time.sleep(5)
def send_msg(self, qname, body):
if not hasattr(self, "connection"):
self.connection, self.channel = self.init_connection()
if self.connection.is_open and self.channel.is_open:
self.channel.queue_declare(queue=qname,
durable=True)
self.channel.basic_publish(exchange='', routing_key=qname, body=body,
properties=pika.BasicProperties(delivery_mode=2))
else:
self.connection, self.channel = self.init_connection()
@staticmethod
def disconnect(connection: pika.BlockingConnection, channel: pika.adapters.blocking_connection.BlockingChannel):
channel.close()
connection.close()
def init_func(self, qname):
def get_name(func):
self.func = func
self.qname = qname
return get_name
def callback(self, channel, method, properties, body):
if hasattr(self, "func"):
status = self.func(body)
if status:
channel.basic_ack(delivery_tag=method.delivery_tag)
else:
channel.basic_nack(delivery_tag=method.delivery_tag, requeue=True)
else:
print("执行函数未定义")
return True
@property
def listen(self):
@self.retry2()
def inner_method():
self.connection, self.channel = self.init_connection()
if hasattr(self, "qname"):
self.channel.queue_declare(
queue=self.qname,
durable=True)
logger.info("开始启动监听")
self.channel.basic_qos(
prefetch_count=1)
self.channel.basic_consume(queue=self.qname,
auto_ack=False,
on_message_callback=self.callback
)
self.channel.start_consuming()
else:
print("队列名称未定义")
return inner_method
if __name__ == '__main__':
mq = RabbitmqPackage("guest", "guest", "127.0.0.1")
@mq.init_func(qname="hello2")
def task(body):
print(f"任务函数{body}")
time.sleep(5)
return True
def task_2():
mq.change_flow_control(status="NORMAL")
mq.listen()
scheduler = BlockingScheduler(timezone='Asia/Shanghai')
trigger1 = CronTrigger(second="15")
scheduler.add_job(task_2, trigger=trigger1, coalesce=True)
trigger2 = CronTrigger(second="45")
scheduler.add_job(mq.change_flow_control, trigger=trigger2, coalesce=True)
scheduler.start()
"""
AMQPConnectionError: 这个异常表示与AMQP(Advanced Message Queuing Protocol)服务器的连接问题。可能的原因包括网络问题、服务器不可用或者认证失败等
ChannelClosedByBroker:这个异常表示由broker(消息代理服务器,例如RabbitMQ)主动关闭了通道。可能的原因包括试图执行一个非法操作或者通道因某种原因被中断。
ConnectionClosedByBroker: 这个异常表示由broker主动关闭了连接。可能的原因包括broker因为某种原因(如配置、负载)决定关闭连接。
"""