目录
MQ
消息队列(Message Queue,简称MQ),从字面意思上看,本质是个队列,FIFO先入先出,只不过队列中存放的内容是message而已。其主要用途:不同进程Process/线程Thread之间通信。
使用消息队列的原因
当不同进程(process)之间传递消息时,如果两个进程之间耦合程度过高,一个进程的改动就会引发另一个进程的改动,为了隔离这两个进程,在两进程间抽离出一层(一个模块),用来两个进程之间传递的消息,这样单独修改某一个进程不会影响另一个;
为了实现标准化,格式规范化,对过多的消息进行排队,因此诞生了消息队列。
MQ框架非常之多,比较流行的有RabbitMq、ActiveMq、ZeroMq、kafka,以及阿里开源的RocketMQ。
RabbitMq基于Erlang,所以需要安装。
简单的rabbitMQ队列通信
由上图可知,数据是先发给exchange交换器,exchage再发给相应队列。pika模块是python对rabbitMQ的API接口。接收端有一个回调函数,一接收到数据就调用该函数。一条消息被一个消费者接收后,该消息就从队列删除。
import pika
# 连上rabbitMQ
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel() # 生成管道,在管道里跑不同的队列
# 声明queue
channel.queue_declare(queue='hello1')
# 向队列里发数据
channel.basic_publish(exchange='', routing_key ='hello1', body = 'HelloWorld!!')
print("Sent Message succeed")
connection.close()
import pika
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
channel.queue_declare(queue='hello1') # 声明队列,保证程序不出错
def callback(ch, method, properties, body):
print("\033[0;31;40m\t-->ch\033[0m" ,ch)
print("\033[0;32;40m\t-->method\033[0m", method)
print("\033[0;33;40m\t-->properties\033[0m", properties)
print("\033[0;34;40m\tReceived\033[0m", body)
# 一条消息被一个消费者接收后,该消息就从队列删除
channel.basic_consume('hello1',callback, auto_ack=False)
print('[*] Waiting for messages.To exit press CTRL+C')
channel.start_consuming()
如果把发送端和接收端分别比作生产者与消费者。生产者发送任务A,消费者接收任务A并处理,处理完后生产者将消息队列中的任务A删除。现在我们遇到了一个问题:如果消费者接收任务A,但在处理的过程中突然宕机了。而此时生产者将消息队列中的任务A删除。实际上任务A并未成功处理完,相当于丢失了任务/消息。为解决这个问题,应使消费者接收任务并成功处理完后发送一个ack到生产者!生产者收到ack后就明白任务A已被成功处理,这时才从消息队列中将任务A删除,如果没有收到ack,就需要把任务A发送给下一个消费者,直到任务A被成功处理。
消息持久化
消息持久化分为两步:
-
持久化队列。通过代码实现持久化hello队列:channel.queue_declare(queue='hello', durable=True)
-
持久化队列中的消息。通过代码实现:properties=pika.BasicProperties( delivery_mode = 2)
运行send创建一个消息
重启服务
运行receive可就收消息
import pika
# 连上rabbitMQ
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel() # 生成管道,在管道里跑不同的队列
# 声明queue
channel.queue_declare(queue='hello1',durable=True) #设置队列并且持久化
# 向队列里发数据 持久化第二步
channel.basic_publish(exchange='', routing_key ='hello1', body = 'HelloWorld!!',properties=pika.BasicProperties(delivery_mode=2))
print("Sent Message succeed")
connection.close()
import pika
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
channel.queue_declare(queue='hello1',durable=True) # 声明队列,保证程序不出错
def callback(ch, method, properties, body):
print("\033[0;31;40m\t-->ch\033[0m" ,ch)
print("\033[0;32;40m\t-->method\033[0m", method)
print("\033[0;33;40m\t-->properties\033[0m", properties)
print("\033[0;34;40m\tReceived\033[0m", body)
ch.basic_ack(delivery_tag=method.delivery_tag)
# 一条消息被一个消费者接收后,该消息就从队列删除
channel.basic_qos(prefetch_count=1) #一次处理一个队列
channel.basic_consume('hello1',callback, auto_ack=False)
print('Waiting for messages.To exit press CTRL+C')
channel.start_consuming()
RPC(Remote Procedure Call)
client端发的消息被server端接收后,server端会调用callback函数,执行任务后,还需要把相应的信息发送到client
import pika
import uuid
class FibonacciRpcClient(object):
def __init__(self):
self.connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
self.channel = self.connection.channel()
self.channel.queue_declare(queue='rpc_queue')
# 随机建立一个queue,为了监听返回的结果
result = self.channel.queue_declare('',exclusive=True)
print('client:随机监听队列已建立')
self.callback_queue = result.method.queue ##队列名
self.channel.basic_consume(self.callback_queue,self.on_response, # 一接收客户端发来的指令就调用回调函数on_response
auto_ack=True)
def on_response(self, ch, method, props, body): # 回调
# 每条指令执行的速度可能不一样,指令1比指令2先发送,但可能指令2的执行结果比指令1先返回到客户端,
# 此时如果没有下面的判断,客户端就会把指令2的结果误认为指令1执行的结果
if self.corr_id == props.correlation_id:
self.response = body
print('client:收到server端处理结果')
def call(self, n):
self.response = None ##指令执行后返回的消息
self.corr_id = str(uuid.uuid4()) ##可用来标识指令(顺序)
print('向client端发送消息'+str(n))
self.channel.basic_publish(exchange='',
routing_key='rpc_queue', # client发送指令,发到rpc_queue
properties=pika.BasicProperties(
reply_to=self.callback_queue, # 将指令执行结果返回到reply_to队列
correlation_id=self.corr_id,
),
body=str(n))
while self.response is None:
self.connection.process_data_events() # 去queue接收数据(不阻塞)
return int(self.response)
fibonacci_rpc = FibonacciRpcClient()
response = fibonacci_rpc.call(30)
print("client:Got %r" % response)
import pika
import time
connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
channel = connection.channel()
channel.queue_declare(queue='rpc_queue')
print('server:管道-rpc_queue-已建立。')
def fib(n):
if n == 0:
return 0
elif n == 1:
return 1
else:
return fib(n - 1) + fib(n - 2)
def on_request(ch, method, props, body):
n = int(body)
print("server:接受到"+str(n))
ch.basic_publish(exchange='', ##服务端发送返回的数据到props.reply_to队列(客户端发送指令时声明)
routing_key=props.reply_to, # correlation_id (随机数)每条指令都有随机独立的标识符
properties=pika.BasicProperties(correlation_id=props.correlation_id),
body=str(fib(n)))
print('server:回应client端')
ch.basic_ack(delivery_tag=method.delivery_tag) # 客户端持久化
channel.basic_qos(prefetch_count=1) # 公平分发
channel.basic_consume('rpc_queue',on_request) # 一接收到消息就调用on_request
print("server:等待client端响应")
channel.start_consuming()
启动server
启动client