window下启动与关闭
在安装目录下的sbin目录,打开终端;(也可以配置环境变量,就不用到sbin目录下)
- rabbitmqctl start_app; 开启rabbitmq-server
- rabbitmqctl status; 查看状态
- rabbitmqctl shutdown; 关闭服务
- rabbitmqctl --help 帮助信息
也可以到浏览器中打开UI管理界面,地址http://localhost:15672/
账号:guest
密码:guest
python客户端
使用python语言作为客户端,开发消息队列,演示消息队列的使用方法。
python作为客户端,连接rabbitmq服务,需要安装相应的包,这里使用pika。
pip install pika
生产者&消费者模式
- hello world模式
一个生产者、一个消费者。
# 生产者 producer.py
# __author__ = "laufing"
from pika import PlainCredentials, BlockingConnection, ConnectionParameters, BasicProperties
# 认证证书
credentials = PlainCredentials("guest", "guest")
# 建立TCP连接
connection = BlockingConnection(ConnectionParameters(host="localhost", port=5672, credentials=credentials, virtual_host="/"))
# 创建channel通道
channel = connection.channel()
# 声明交换机 (这里Hello World模式使用默认的交换机,不用声明)
# 声明 一个 队列
channel.queue_declare(queue="laufing")
# 发布消息
channel.basic_publish(exchange="", routing_key="laufing", properties=BasicProperties(correlation_id="q1-1"),
body="hello world")
# 关闭通道
channel.close()
# 关闭连接
connection.close()
下面是消费者代码:
# __author__ = "laufing"
from pika import PlainCredentials, BlockingConnection, ConnectionParameters, BasicProperties
# 认证证书
credentials = PlainCredentials("guest", "guest")
# 建立TCP连接
connection = BlockingConnection(ConnectionParameters(host="localhost", port=5672, credentials=credentials, virtual_host="/"))
# 创建channel通道
channel = connection.channel()
# 声明交换机 (这里Hello World模式使用默认的交换机,不用声明)
# 声明 一个 队列
channel.queue_declare(queue="laufing")
# 定义消息处理函数
def deal_func(ch, delivery, properties, body):
"""
:param ch: 消费者的通道channel
:param delivery: 转发信息
:param properties: 消息属性
:param body: 消息体
:return:
"""
print("channel:", ch, delivery)
print("接收到的消息:", body) # 字节串
# 消费消息
channel.basic_consume(queue="laufing", on_message_callback=deal_func, auto_ack=True) # 自动确认容易丢失消息
# 开启消费循环
channel.start_consuming() # 一直监听队列
- work queue模式
一个生产者、一个队列、多个消费者
只需在HelloWorld模式下启动多个消费者即可。
这里生产者改用多线程模式:
# __author__ = "laufing"
import time
import sys
from threading import Thread, Lock
from pika import PlainCredentials, BlockingConnection, ConnectionParameters
from pika.adapters.blocking_connection import BlockingChannel
def func(ch: BlockingChannel, msg: str) -> None:
# 声明队列
ch.queue_declare(queue="q1")
# 发布消息
ch.basic_publish(exchange="", routing_key="q1", body=msg)
ch.close()
# 多线程 建立多个channel ,公用一个TCP连接
def start_task(task_list: list) -> None:
ths = [Thread(target=func, args=(ch, msg)) for ch, msg in task_list]
for th in ths:
# 设置守护线程 主线程终止,子线程不管有没有执行完,都立即停止
th.setDaemon(True)
th.start() # 子线程开始执行
for th in ths:
th.join() # 主线程(代码执行完毕后)等待所有的子线程执行结束
if __name__ == '__main__':
# lock = Lock()
credentials = PlainCredentials("guest", "guest")
# 全局变量 TCP连接
conn = BlockingConnection(ConnectionParameters(host="localhost", port=5672, credentials=credentials,
virtual_host='/', heartbeat=0))
# 创建三个channel 逻辑通道
channels = [conn.channel() for i in range(3)]
task_list = ["jack", "tom", "lucy"]
start_task(zip(channels, task_list))
# 主线程退出时才关闭连接
try:
while True:
time.sleep(24*60*60)
except KeyboardInterrupt:
conn.close()
sys.exit(0)
以上生产者启动报错:
这是因为pika是线程不安全的
,三个子线程同时操作一个TCP连接的逻辑通道,即同时操作共享的数据,造成数据混乱。
解决
:
- 子线程加线程锁- Lock(); 可以解决,但子线程同步执行,无异步效果;
# 子线程中实现
lock.acquire()
.....
lock.release()
- 每个线程中创建TCP连接和channel (线程池 + 线程局部变量)
# __author__ = "laufing"
import time
import sys
from threading import local, current_thread
from concurrent.futures import ThreadPoolExecutor
from pika import PlainCredentials, BlockingConnection, ConnectionParameters
from pika.adapters.blocking_connection import BlockingChannel
def init():
# 每个线程中创建TCP连接 + channel,为避免频繁创建TCP连接,应避免频繁创建线程-->使用线程池
print("创建tcp连接...")
credentials = PlainCredentials("guest", "guest")
conn = BlockingConnection(ConnectionParameters(host="localhost", port=5672, credentials=credentials,
virtual_host='/', heartbeat=0))
return conn.channel()
def func(msg: str) -> None:
if not hasattr(local, "ch"):
print("当前线程:", current_thread().ident)
local.ch = init() # 子线程内部 创建TCP连接,并返回一个channel
print("local: ", local.__dict__.keys())
local.ch.queue_declare(queue="q1")
local.ch.basic_publish(exchange="", routing_key="q1", body=msg)
if __name__ == '__main__':
# 线程的局部变量 (为每个线程创建自己的变量)
local = local()
task_list = ["jack", "tom", "lucy", "laufing1", "laufing2", "laufing3"]
# 创建线程池
# 避免频繁的创建、销毁线程,这里只创建三个线程,循环利用
# 来执行所有的任务
pool = ThreadPoolExecutor(max_workers=3)
for msg in task_list:
pool.submit(func, msg) # 提交任务,同时执行三个任务; 其他任务依次等待
# 主线程退出时才关闭连接
try:
while True:
time.sleep(24*60*60)
except KeyboardInterrupt:
pool.shutdown() # 关闭线程池
sys.exit(0)
消费者不做修改,打开三个终端,开启三次即有三个消费者。
# __author__ = "laufing"
from pika import PlainCredentials, BlockingConnection, ConnectionParameters, BasicProperties
credentials = PlainCredentials("guest", "guest")
conn = BlockingConnection(ConnectionParameters(host="localhost", port=5672, credentials=credentials,
virtual_host='/', heartbeat=0))
channel = conn.channel()
channel.queue_declare(queue="q1")
# 消费消息的函数
def func(ch, delivery, prop, body):
"""
ch: 消费者的channel
delivery: 消息转发的信息
prop: 消息的属性
body: 消息体 字节串
"""
print(ch is channel)
print("消费的信息:", body)
# ch.basic_ack(delivery_tag=delivery.delivery_tag)
channel.basic_consume(queue="q1", on_message_callback=func, auto_ack=True)
channel.start_consuming() # 循环监听
默认Broker 会依次均匀分发消息给所有的消费者,即轮询;
这对消费慢的程序不公平,可以关闭自动确认,采用手动确认,确认完成后再发另一个消息。
发布订阅模式
-
fanout广播
- 生产者声明交换机及类型;
- 消费者声明各自的队列、交换机,绑定队列与交换机;
- 生产者发布消息给交换机,交换机广播消息到所有绑定该交换机的队列;
生产者:
# 声明交换机及类型;
channel.exchange_declare(exchange="e1", exchange_type="fanout")
# 发布消息
channel.basic_publish(exchange="e1", routing_key="", body=msg)
消费者1:
# __author__ = "laufing"
from pika import PlainCredentials, BlockingConnection, ConnectionParameters, BasicProperties
# 连接
credentials = PlainCredentials("guest", "guest")
conn = BlockingConnection(ConnectionParameters(host="localhost", port=5672, credentials=credentials,
virtual_host='/', heartbeat=0))
channel = conn.channel()
# 声明队列
channel.queue_declare(queue="q1")
# 声明交换机
channel.exchange_declare(exchange="e1", exchange_type="fanout")
# 队列绑定交换机
channel.queue_bind(queue="q1", exchange="e1", routing_key="")
# 定义消费程序
def func(ch, delivery, prop, body):
print(ch is channel)
print("消费的信息:", body)
# 确认每次接收一个消息
channel.basic_qos(prefetch_count=1)
channel.basic_consume(queue="q1", on_message_callback=func, auto_ack=True)
channel.start_consuming()
消费者2:
# 核心代码
# 声明队列
channel.queue_declare(queue="q2")
# 声明交换机
channel.exchange_declare(exchange="e1", exchange_type="fanout")
# 队列绑定交换机
channel.queue_bind(queue="q2", exchange="e1", routing_key="")
# 定义消费程序
def func(ch, delivery, prop, body):
print(ch is channel)
print("消费的信息:", body)
# 确认每次接收一个消息
channel.basic_qos(prefetch_count=1)
channel.basic_consume(queue="q2", on_message_callback=func, auto_ack=True)
channel.start_consuming()
测试结果:
- direct定向,根据routing_key定向到指定的队列
生产者:
# 声明交换机
channel.exchange_declare(exchange="e1", exchange_type="direct")
channel.basic_publish(exchange="e1", routing_key="error", body=msg) # 根据routing_key 定向转发
消费者error队列:
# error_consumer.py
# 声明队列
channel.queue_declare(queue="q1")
# 声明交换机
channel.exchange_declare(exchange="e1", exchange_type="direct")
# 队列绑定交换机
channel.queue_bind(queue="q1", exchange="e1", routing_key="error")
# 定义消费程序
def func(ch, delivery, prop, body):
print(ch is channel)
print("消费的信息:", body)
# 确认每次接收一个消息
channel.basic_qos(prefetch_count=1)
channel.basic_consume(queue="q1", on_message_callback=func, auto_ack=True)
channel.start_consuming()
消费者info队列:
# info_consumer.py
# 声明队列
channel.queue_declare(queue="q2")
# 声明交换机
channel.exchange_declare(exchange="e1", exchange_type="direct")
# 队列绑定交换机
channel.queue_bind(queue="q2", exchange="e1", routing_key="info")
# 定义消费程序
def func(ch, delivery, prop, body):
print(ch is channel)
print("消费的信息:", body)
# 确认每次接收一个消息
channel.basic_qos(prefetch_count=1)
channel.basic_consume(queue="q2", on_message_callback=func, auto_ack=True)
channel.start_consuming()
结果:
- topic 根据routing_key定向转发, 这里的routing_key 是正则匹配模式,*匹配一个单词,#匹配0个以上。
生产者:
生产者指定具体的routing_key。
# 声明交换机
channel.exchange_declare(exchange="e2", exchange_type="topic") #
channel.basic_publish(exchange="e2", routing_key="wang.laufing", body=msg) # 根据routing_key 定向转发
消费者1:
消费者端进行正则匹配。
# 声明队列
channel.queue_declare(queue="q2")
# 声明交换机
channel.exchange_declare(exchange="e2", exchange_type="topic")
# 队列绑定交换机
channel.queue_bind(queue="q2", exchange="e2", routing_key="*.jack") #
# 定义消费程序
def func(ch, delivery, prop, body):
print(ch is channel)
print("消费的信息:", body)
# 确认每次接收一个消息
channel.basic_qos(prefetch_count=1)
channel.basic_consume(queue="q2", on_message_callback=func, auto_ack=True)
channel.start_consuming()
消费者2:
# 声明队列
channel.queue_declare(queue="q1")
# 声明交换机
channel.exchange_declare(exchange="e2", exchange_type="topic") # 正则转发
# 队列绑定交换机
channel.queue_bind(queue="q1", exchange="e2", routing_key="*.laufing") # 消费者端 正则匹配
结果:
报错:改一个交换机的名字即可。
rpc模式
- 这里生产者、消费者一般叫做客户端、服务端;
- 客户端声明自己的接收响应的队列;
- 服务端声明接收请求的队列;
- 两者均使用默认的交换机;
生产者(客户端),发布的消息带有两个属性reply_to & correlation_id。
reply_to,表示服务器返回的响应要放入的队列;
correlation_id,表示返回的响应是对应哪个请求的。
# __author__ = "laufing"
import uuid
from pika import PlainCredentials, BlockingConnection, ConnectionParameters, BasicProperties
credential = PlainCredentials("guest", "guest")
connection = BlockingConnection(ConnectionParameters(host='localhost', port=5672,
credentials=credential, virtual_host="/"))
channel = connection.channel() # 创建逻辑通道
# 声明消费的队列(获取响应)
channel.queue_declare(queue="response")
# 发布一个消息(请求)
req_id = str(uuid.uuid4())
channel.basic_publish(exchange="", routing_key="request", properties=BasicProperties(reply_to="response",
correlation_id=req_id),
body="lauf")
response = None
def handle_response(ch, delivery, prop, body):
# 判断是否 是自己的响应
if prop.correlation_id == req_id:
print("接收响应:", body.decode())
global response
response = body
# 接收到响应 自主回复确认
channel.basic_ack(delivery_tag=delivery.delivery_tag)
# 接收响应
channel.basic_consume(queue='response', on_message_callback=handle_response, auto_ack=False)
while response is None:
# 没收到响应时, 一直处于保持通信状态
connection.process_data_events()
消费者(服务端)
# __author__ = "laufing"
import uuid
from pika import PlainCredentials, BlockingConnection, ConnectionParameters, BasicProperties
cre = PlainCredentials("guest", "guest")
connection = BlockingConnection(ConnectionParameters(host='localhost', port=5672, virtual_host="/", credentials=cre))
channel = connection.channel()
# 声明接收请求的queue
channel.queue_declare(queue='request')
def handle_request(ch, delivery, prop, body):
print("处理请求:", body)
# 返回响应
channel.basic_publish(exchange="", routing_key=prop.reply_to, properties=BasicProperties(correlation_id=prop.correlation_id),
body="你好" + body.decode())
# 返回响应后 回复确认
channel.basic_ack(delivery_tag=delivery.delivery_tag)
channel.basic_qos(prefetch_count=1) # 每次获取一个消息
channel.basic_consume(queue='request', on_message_callback=handle_request, auto_ack=False)
channel.start_consuming()
结果: