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

posted @ 2017-07-28 09:51  zimsan  阅读(221)  评论(0)    收藏  举报