rabbitmq
Message Queue, 消息队列(MQ)
是一种应用程序对应用程序的通信方法。应用程序通过读写出入队列的消息(针对应用程序的数据)来通信,而无需专用连接来链接它们。消息系统通过将消息的发送和接收分离来实现异步和解耦。
rabbitMQ:
可靠性:包括持久性机制、投递确认、发布者证实和高可用性机制
灵活性:RabbitMQ为典型的路由逻辑提供了多种内置交换机类型。如果你有更复杂的路由需求,可以将这些交换机组合起来使用,你甚至可以实现自己的交换机类型,并且当做RabbitMQ的插件来使用。(exchange)
rabbitMQ是通过erlang语言开发。能支持linux,windows
rabbitMQ使用的模型为AMQP 0-9-1
工作过程如下图:消息(message)被发布者(publisher)发送给交换机(exchange),交换机常常被比喻成邮局或者邮箱。然后交换机将收到的消息根据路由规则分发给绑定的队列(queue)。最后AMQP代理会将消息投递给订阅了此队列的消费者,或者消费者按照需求自行获取。
发布者(publisher)发布消息时可以给消息指定各种消息属性(message meta-data)。有些属性有可能会被消息代理(brokers)使用,然而其他的属性则是完全不透明的,它们只能被接收消息的应用所使用。properties=pika.BasicProperties()
在某些情况下,例如当一个消息无法被成功路由时,消息或许会被返回给发布者并被丢弃。或者,如果消息代理执行了延期操作,消息会被放入一个所谓的死信队列中。此时,消息发布者可以选择某些参数来处理这些特殊情况。no_ack=False
默认轮询:
RabbitMQ会按顺序得把消息发送给每个消费者(consumer)。平均每个消费者都会收到同等数量得消息。这种发送消息得方式叫做——轮询(round-robin)
例子:
1 import pika 2 3 ##############生产者################# 4 5 #建立一个阻塞型连接,连接到rabbitmq服务器 6 7 conn = pika.BlockingConnection(pika.ConnectionParameters(host='localhost')) 8 9 #创建频道 10 11 chan = conn.channel() 12 13 #频道管理队列,在频道下创建队列haha 14 15 chan.queue_declare(queue='haha') 16 17 #exchange --它使我们能够确切地指定消息应该到哪个队列去。 18 19 #向队列插入数值 routing_key是队列名 body是要插入的内容,默认消费者轮流去队列的数据 20 21 chan.basic_publish( 22 23 exchange='', 24 25 routing_key='haha', 26 27 body = 'hello rabbitmq!' 28 29 ) 30 31 print('开始队列') 32 33 #缓冲区已经flush而且消息已经确认发送到了RabbitMQ中,关闭链接 34 35 conn.close() 36 37 38 ################消费者##################### 39 40 #连接rabbit 41 42 conn = pika.BlockingConnection(pika.ConnectionParameters(host='localhost')) 43 44 #创建管道 45 46 chan = conn.channel() 47 48 #如果生产者没有运行创建队列,那么消费者也许就找不到队列了。为了避免这个问题 49 50 #所有消费者也创建这个队列 51 52 chan.queue_declare(queue='haha') 53 54 #接收消息需要使用callback这个函数来接收,他会被pika库来调用 55 56 def callback(ch, method, properties, body): 57 58 print(" [x] Received %r" % body) 59 60 # 从队列取数据 callback是回调函数 如果拿到数据 那么将执行callback函数 61 62 chan.basic_consume(callback, 63 64 queue='haha', 65 66 no_ack=True) 67 68 print(' [*] 等待信息. To exit press CTRL+C') 69 70 #永远循环等待数据处理和callback处理的数据 71 72 chan.start_consuming()
消息确认
为了防止消息丢失,RabbitMQ提供了消息响应(acknowledgments)。消费者会通过一个ack(响应),告诉RabbitMQ已经收到并处理了某条消息,然后RabbitMQ就会释放并删除这条消息。
如果消费者(consumer)挂掉了,没有发送响应,RabbitMQ就会认为消息没有被完全处理,然后重新发送给其他消费者(consumer)。这样,及时工作者(workers)偶尔的挂掉,也不会丢失消息。
chan.basic_consume(callback, queue='haha', no_ack=False) #如果该消息处理中,生产者遇到情况(关闭通道,连接关闭或TCP连接丢失))挂掉了,那么出现错误,设置为Flase,RabbitMQ会重新将该任务添加到队列中,消费者会有ACK给生产者确认该消息消费完成。
消息持久化
这个 queue_declare 需要在 生产者(producer) 和消费方(consumer) 代码中都进行设置。不能持久化已经存在的队列
首先,为了不让队列消失,需要把队列声明为持久化(durable)
确保在RabbitMq重启之后queue_declare队列不会丢失。另外,我们需要把我们的消息也要设为持久化——将delivery_mode的属性设为2。
#############生产者############# #创建队列,使用durable方法 channel.queue_declare(queue='hello',durable=True) channel.basic_publish(exchange='', routing_key='hello', body='Hello World!', properties=pika.BasicProperties( delivery_mode=2, #标记我们的消息为持久化的 - 通过设置 delivery_mode 属性为 2 #这样必须设置,让消息实现持久化(消息持久化) )) #如果想让队列实现持久化那么加上durable=True(队列持久化
1 #######消费者############# 2 3 def callback(ch, method, properties, body): 4 5 print(" [x] Received %r" % body) 6 7 import time 8 9 time.sleep(10) 10 11 print 'ok' 12 13 ch.basic_ack(delivery_tag = method.delivery_tag) #主要使用此代码
消息公平分发:
消费者同时都保持有一个消息在消费()#保证按服务器资源来负载均衡
def callback(ch, method, properties, body): print(" [x] Received %r" % body) time.sleep(body.count(b'.')) print(" [x] Done") ch.basic_ack(delivery_tag=method.delivery_tag) channel.basic_qos(prefetch_count=1)#主要代码 channel.basic_consume(callback, queue='task_queue') channel.start_consuming()
rabbitmq的Publish\Subscribe(消息发布\订阅)
Exchange在定义的时候是有类型的,以决定到底是哪些Queue符合条件,可以接收消息
fanout: 所有bind到此exchange的queue都可以接收消息
direct: 通过routingKey和exchange决定的那个唯一的queue可以接收消息
topic:所有符合routingKey(此时可以是一个表达式)的routingKey所bind的queue可以接收消息
channel.exchange_declare(exchange='logs', type='fanout')
例子:fanout 广播(订阅的都收到)
1 import pika 2 import sys 3 connection = pika.BlockingConnection(pika.ConnectionParameters( 4 host='localhost')) 5 channel = connection.channel() 6 channel.exchange_declare(exchange='logs', 7 type='fanout') 8 message = ' '.join(sys.argv[1:]) or "info: Hello World!" 9 channel.basic_publish(exchange='logs', 10 routing_key='', 11 body=message) 12 print(" [x] Sent %r" % message) 13 connection.close()
1 import pika 2 connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost')) 3 channel = connection.channel() 4 channel.exchange_declare(exchange='logs',type='fanout') 5 # 不指定queue名字,rabbit会随机分配一个名字,exclusive=True会在使用此queue的消费者断开后,自动将queue删除 6 result = channel.queue_declare(exclusive=True) 7 queue_name = result.method.queue 8 channel.queue_bind(exchange='logs',queue=queue_name)#把你的queue绑定到exchange上 9 print(' [*] Waiting for logs. To exit press CTRL+C') 10 def callback(ch, method, properties, body): 11 print(" [x] %r" % body) 12 channel.basic_consume(callback, 13 queue=queue_name, 14 no_ack=True) 15 channel.start_consuming()
import pika connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost')) channel = connection.channel() channel.exchange_declare(exchange='logs',type='fanout') # 不指定queue名字,rabbit会随机分配一个名字,exclusive=True会在使用此queue的消费者断开后,自动将queue删除 result = channel.queue_declare(exclusive=True) queue_name = result.method.queue channel.queue_bind(exchange='logs',queue=queue_name)#把你的queue绑定到exchange上 print(' [*] Waiting for logs. To exit press CTRL+C') def callback(ch, method, properties, body): print(" [x] %r" % body) channel.basic_consume(callback, queue=queue_name, no_ack=True) channel.start_consuming()
direct例子,特定的消息订阅者可以收到(指定广播队列)
type='direct'
1 #消息推送端 调用方法 python 本文件名 error/info/warning 消息 2 import pika 3 import sys 4 connection = pika.BlockingConnection(pika.ConnectionParameters( 5 host='localhost')) 6 channel = connection.channel() 7 channel.exchange_declare(exchange='direct_logs', 8 type='direct') 9 severity = sys.argv[1] if len(sys.argv) > 1 else 'info' 10 message = ' '.join(sys.argv[2:]) or 'Hello World!' 11 channel.basic_publish(exchange='direct_logs', 12 routing_key=severity, 13 body=message) 14 print(" [x] Sent %r:%r" % (severity, message)) 15 connection.close()
1 订阅端 调用 python 本文件名 监测日志登记error/info/warning 2 import pika 3 import sys 4 connection = pika.BlockingConnection(pika.ConnectionParameters( 5 host='localhost')) 6 channel = connection.channel() 7 8 channel.exchange_declare(exchange='direct_logs', 9 type='direct') 10 result = channel.queue_declare(exclusive=True) 11 queue_name = result.method.queue 12 severities = sys.argv[1:] #绑定一个或者多个路由的key 13 if not severities:#没有绑定打印帮助 14 sys.stderr.write("Usage: %s [info] [warning] [error]\n" % sys.argv[0]) 15 sys.exit(1) 16 for severity in severities: 17 channel.queue_bind(exchange='direct_logs', 18 queue=queue_name, 19 routing_key=severity) 20 print(' [*] Waiting for logs. To exit press CTRL+C') 21 def callback(ch, method, properties, body): 22 print(" [x] %r:%r" % (method.routing_key, body)) 23 channel.basic_consume(callback, 24 queue=queue_name, 25 no_ack=True) 26 channel.start_consuming()
更细致的过滤:(关键字匹配)type='topic'
1 #消息推送端 调用 python 文件名 *.error/*.info/*.warning 消息 2 3 import pika 4 import sys 5 6 connection = pika.BlockingConnection(pika.ConnectionParameters( 7 host='localhost')) 8 channel = connection.channel() 9 10 channel.exchange_declare(exchange='topic_logs',#exchange名字随便定义 11 type='topic') 12 #anonymous.info表示*.info 13 routing_key = sys.argv[1] if len(sys.argv) > 1 else 'anonymous.info' 14 message = ' '.join(sys.argv[2:]) or 'Hello World!' 15 channel.basic_publish(exchange='topic_logs', 16 routing_key=routing_key, 17 body=message) 18 print(" [x] Sent %r:%r" % (routing_key, message)) 19 connection.close()
1 订阅端 调用 python 文件名 *.error/*.info/*.warning(监测日志等级) 2 3 import pika 4 import sys 5 connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost')) 6 channel = connection.channel() 7 channel.exchange_declare(exchange='topic_logs',type='topic') 8 result = channel.queue_declare(exclusive=True) 9 queue_name = result.method.queue 10 binding_keys = sys.argv[1:] 11 if not binding_keys: 12 sys.stderr.write("Usage: %s [binding_key]...\n" % sys.argv[0]) 13 sys.exit(1) 14 for binding_key in binding_keys: 15 channel.queue_bind(exchange='topic_logs', 16 queue=queue_name, 17 routing_key=binding_key) 18 print(' [*] Waiting for logs. To exit press CTRL+C') 19 def callback(ch, method, properties, body): 20 print(" [x] %r:%r" % (method.routing_key, body)) 21 channel.basic_consume(callback, 22 queue=queue_name, 23 no_ack=True) 24 channel.start_consuming()
RPC:客户端调用服务器端的命令(函数),服务器将命令结果返回给客户端
模拟RPC:
import pika
import time
#1 定义连接
connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
channel = connection.channel()
#2连接到queue(rpc_queue)
channel.queue_declare(queue='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):
#4 回调函数,接收到客户端的basic_publish下的properties=pika.BasicProperties(reply_to=self.callback_queue,correlation_id=self.corr_id,) 并赋值给props(所以服务器的props里面有客户端的两个属性), 客户端的body赋值给body
n = int(body)#30
print(" [.] fib(%s)" % n)
response = fib(n)#5 将30给fib函数处理并返回结果给response
#8 服务器端往随机队列reply_to,发送数据
ch.basic_publish(exchange='',
routing_key=props.reply_to,#6 客户端的传递过来的特性reply_to(callback_queue队列)
#7 客户端的传递过来的特性correlation_id(客户端的UUID)
properties=pika.BasicProperties(correlation_id= \
props.correlation_id),
body=str(response))#返回后处理数据fib(30)
ch.basic_ack(delivery_tag=method.delivery_tag)#持久化(保证客户端发送期间断开还能连接回来)
channel.basic_qos(prefetch_count=1)
channel.basic_consume(on_request, queue='rpc_queue') #3监听rpc_queue,接收到的值交个函数on_request处理
print(" [x] Awaiting RPC requests")
channel.start_consuming()
RPCserver
PRC CLIENT:
import pika
import uuid
class FibonacciRpcClient(object):
def __init__(self):
#初始化连接
self.connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
self.channel = self.connection.channel()
#定义一个随机名字的队列
result = self.channel.queue_declare(exclusive=True)
self.callback_queue = result.method.queue#随机名字队列名字赋值给self.callback_queue
# 回调方法callbakck self.on_response
self.channel.basic_consume(self.on_response, no_ack=True, #9 监听callback_queue队列
queue=self.callback_queue)
def on_response(self, ch, method, props, body):#回调方法callbakck
if self.corr_id == props.correlation_id:
self.response = body #10 将服务器返回的值给response
def call(self, n):
self.response = None
self.corr_id = str(uuid.uuid4())
self.channel.basic_publish(exchange='',
routing_key='rpc_queue',#client发向服务器的队列
properties=pika.BasicProperties(
#该properties函数里面定义的属性值会被服务器端的回调方法的props属性接收
#basic_publis的属性,携带两个变量reply_to为RPCserver回复client的队列名
# correlation_id(UUID随机数)代表每次RPC客户端发过去的命令都有各自的随机数,区分服务器发过来的信息
reply_to=self.callback_queue,
correlation_id=self.corr_id,
),
body=str(n))
#11 此时服务器端返回了response值
while self.response is None:
self.connection.process_data_events()#取队列的数据
return int(self.response) #12 结束并返回responseg给函数调用者
fibonacci_rpc = FibonacciRpcClient()
print(" [x] Requesting fib(30)")
response = fibonacci_rpc.call(30)
print(" [.] Got %r" % response)
RPCclient
重要:********中文档 很详细:rabbitmq参考文档:http://rabbitmq.mr-ping.com/tutorials_with_python/[3]Publish_Subscribe.html

浙公网安备 33010602011771号