rabbitmq消息队列

一.回顾

1.1python中的queue消息队列

 1  ##在cmd中执行
 2  import queue
 3  q=queue.Queue()
 4  ##默认是先进先出
 5  q.put(111)
 6  q.put('yyy')
 7  q.get()
 8  >>111
 9  q.get()
10 >>'yyy'
11 q.qsize()
12 >>0

1.2同步与异步

同步执行:一个进程在执行某个任务时,另外一个进程必须等待其执行完毕,才能继续执行
异步执行:一个进程在执行某个任务时,另外一个进程无需等待其执行完毕,就可以继续执行,当有消息返回时,系统会通知后者进行处理,这样可以提高执行效率

举个例子,打电话时就是同步通信,发短息时就是异步通信。

同步:

  优点:保证任务及时处理

  缺点:排队问题

异步:

  优点:解决了排队问题

  缺点:不能保证任务及时处理

2.为什么用rabbitmq消息队列

rabbitmq作用:解耦,异步

python queue不能跨进程

3.消息队列在异步中的作用

3.1存储消息、数据

3.2保证消息顺秀

3.3保证数据的交付

二.RabbitMQ

1.历史

RabbitMQz是一个由erlang开发的AMQP的开源实现。

官网:http://www.rabbitmq.com

2.应用场景

RabbitMQ,或者AMQP解决了什么,或者说他的应用场景是什么?

对于一个大型的软件系统来说,他会有很多的组件或者模块.这些模块如何通信?

如果使用socket那么不同的模块的确可以部署到不同的机器上上,但是有很多问题需要解决.比如:

a.信息的发送者和接收者如何维持这个链接,如果一方链接中断,这期间的数据如何防止丢失?

b.如何降低发送者和接收者的耦合度?

c.如何保证接收者收到了完整的、正确的数据?

3.应用场景的系统架构

RabbitMQ Server:一种传输服务.他的角色是维护一条P到C的路线,保证数据能够按照指定的方式进行传输.但是这个保证也不是100%的保证,但是对于普通的应用来说这已经足够了.

Client A & B: Producer:数据发送方.一个Message有两个部分:payload(传输数据)和label(是exchange的名字,描述了payload,RabbitMQ通过这个label来决定把这个Message发给哪个Consumer).

Client 1 ,2 ,3:Consumer:数据的接收方.把queue比作是一个有名字的邮箱.当有Message到达某个邮箱后,RabbitMQ把他发送给他的某个订阅者Consumer;当然可能会把同一个Message发给很多的Consumer.在这个Message中,只有payload,lable已经被删掉了,Consumer不知道是谁发送的信息;就是协议本身不支持;如果payload包含了Producer信息就另当别论了.

对于一个数据从Producer到Consumer的正确传递,还有三个概念需要明确:exchanges,queues and bindings.

exchanges:生产者发布消息的地方

queues :消费者接收信息的地方,且消息也在队列中清除

bindings:设置如何将信息通过exchanges来分发到特定的queues 

 还有几个概念没有标明的,Connection(连接),Channel(通道).

Connection:就是一个TCP连接.Producer和Consumer都是通过TCP连接到RabbitMQ Server的.

Channel:虚拟连接.建立在TCP连接中,数据流动都是在Channel中进行的,也就是说,一般情况是程序起始建立TCP连接,第二步就是建立这个Channel.

4.进一步的细节阐明

4.1使用ack确认Message的正确传递

默认情况下,如果Message已经被某个Consumer正确的接收到了,那么该Message就会被从queue中移除.如果一个queue没有被任何Consumer订阅,那么如果这个queue有数据到达,这个数据会被储存,不会被丢弃;当有Consumer时这个数据就立即被发送到Consumer,这个数据就被正确接收后,从queue中删除;

每个Message都要被ack确认.可以显示的在程序中去ack,也可以自动的ack.如果数据没有被ack,那么RabbitMQ Server会把这个信息发送给下一个Consumer;如果这个app有bug,忘记了ack,RabbitMQ Server不会再发数据给他,因为Server认为这个Consumer处理能力有限;

而且ack的机制可以起到限流的作用。

4.2Reject a message

有两种方式:

第一种的Reject可以让RabbitMQ Server将该Message发送到下一个Consumer

第二种是从queue中立即删除该Message

4.3Creating a queue

Consumer和Procuder都可以通过 queue.declare 创建queue。对于某个Channel来说,Consumer不能declare一个queue,却订阅其他的queue。当然也可以创建私有的queue。这样只有app本身才可以使用这个queue。queue也可以自动删除,被标为auto-delete的queue在最后一个Consumer unsubscribe后就会被自动删除。那么如果是创建一个已经存在的queue呢?那么不会有任何的影响。需要注意的是没有任何的影响,也就是说第二次创建如果参数和第一次不一样,那么该操作虽然成功,但是queue的属性并不会被修改。

那么谁应该负责创建这个queue呢?是Consumer,还是Producer?

如果queue不存在,当然Consumer不会得到任何的Message。但是如果queue不存在,那么Producer Publish的Message会被丢弃。所以,还是为了数据不丢失,Consumer和Producer都try to create the queue!反正不管怎么样,这个接口都不会出问题。

 queue对load balance的处理是完美的。对于多个Consumer来说,RabbitMQ 使用循环的方式(round-robin)的方式均衡的发送给不同的Consumer。

4.4Exchanges

 

     有三种类型的Exchanges:direct, fanout,topic。 每个实现了不同的路由算法(routing algorithm)。

·        Direct exchange: 如果 routing key 匹配, 那么Message就会被传递到相应的queue中。其实在queue创建时,它会自动的以queue的名字作为routing key来绑定那个exchange。

·        Fanout exchange: 会向响应的queue广播。

·        Topic exchange: 对key进行模式匹配,比如ab*可以传递到所有ab*的queue.

4.5Virtual hosts

每个virtual host本质以上都是一个RabbitMQ Server,拥有它自己的queue,exchange和bing rule等.

5.实现

5.1 say hello 

设计图:

5.1.1环境配置

windows:

1 > easy_install pip
2 > pip install pika==0.9.8

5.1.2 Sending

 1 import pika
 2 ##建立TCP连接
 3 connection = pika.BlockingConnection(pika.ConnectionParameters(
 4     'localhost'))
 5 ##创建channel
 6 channel = connection.channel()
 7 ##创建queue
 8 channel.queue_declare(queue='hello')
 9 channel.basic_publish(
10     exchange='',
11     routing_key='hello',
12     body='hello world!'
13 )
14 print('[x] Sent "hello world!"')
15 connection.close()
send1
 1 import pika
 2 connection = pika.BlockingConnection(pika.ConnectionParameters(
 3     'localhost'))
 4 ##创建channel
 5 channel = connection.channel()
 6 ##创建queue
 7 channel.queue_declare(queue='hello')
 8 print('[*] Waiting for messages.To exit press CTRL+C')
 9 def callback(ch,method,proerties,body):
10     print('[x] Received %r'%(body,))
11 
12 channel.basic_consume(callback,
13                       queue='hello',
14                       no_ack=True,
15                       )
16 
17 channel.start_consuming()
receive1

5.2 任务分发机制

设计图

 1 #应用场景就是RabbitMQ Server会将queue的Message分发给不同的Consumer以处理计算密集型的任务
 2 import pika
 3 import sys
 4 ###创建TCP
 5 connection=pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
 6 ###创建连接
 7 channel=connection.channel()
 8 ##创建通道
 9 channel.queue_declare(queue='send2',durable=True)
10 # queue的持久化声名durable=True
11 message=''.join(sys.argv[1:])or 'hello world!'
12 channel.basic_publish(
13     exchange='',
14     routing_key='send2',
15     body=message,
16     properties=pika.BasicProperties(
17         delivery_mode=2,
18     ##message持久化,需要指定properties
19     )
20 )
21 print('[x] Sent %r'%(message))
22 connection.close()
send2
 1 import pika
 2 import time
 3 connection=pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
 4 channel=connection.channel()
 5 channel.queue_declare(queue='send2',durable=True)
 6 ###queue的持久化声名durable=True
 7 print('[*] Waiting for messages. To exit press CTRL+C')
 8 
 9 def callback(ch,method,properies,body):
10     print('[x] Received %r'%(body,))
11     time.sleep(1)
12     print('[x] Done')
13     ###将no-ack=Ture关闭,确认信息
14     ch.basic_ack(delivery_tag=method.delivery_tag)
15 
16 channel.basic_qos(prefetch_count=1)
17 ##RabbitMQ就会使得每个Consumer在同一个时间点最多处理一个Message
18 # 在接收到该Consumer的ack前,他它不会将新的Message分发给它
19 channel.basic_consume(callback,
20                       queue='send2')
21 channel.start_consuming()
receive2

5.3 分发到多consumer

设计图

广播模式

 1 ##广播模式
 2 
 3 import pika
 4 
 5 connection=pika.BlockingConnection(pika.ConnectionParameters('localhost'))
 6 channel=connection.channel()
 7 ##创建名字为logs的exchange,类型为广播模式
 8 channel.exchange_declare(
 9     exchange='logs',
10     type='fanout',
11 )
12 message='hello world!'
13 channel.basic_publish(
14     exchange='logs',
15     ##声名exchange
16     routing_key='',
17     ##连接通道queue
18     body=message
19 )
20 print('[x] Sent %r'%(message,))
21 connection.close()
View Code
 1 import pika
 2 
 3 connection=pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
 4 channel=connection.channel()
 5 channel.exchange_declare(
 6     exchange='logs',
 7     type='fanout',
 8 )
 9 ###一个新的,空的queue,在声名queue时不指定,RabbitMQ就会随机选择
10 ##当connection关闭时,queue被清除,可以添加exclusive参数
11 result=channel.queue_declare(exclusive=True)
12 # 通过result.method.queue可以获取queue名字
13 queue_name=result.method.queue
14 channel.queue_bind(exchange='logs',
15                    queue=queue_name)
16 print('[*] Waiting for logs. To exit press CTRL+C')
17 def callback(ch,method,properties,body):
18     print('[x] %r'%(body,))
19 channel.basic_consume(callback,
20                       queue=queue_name,
21                       no_ack=True)
22 
23 channel.start_consuming()
receive3

消息路由

 1 #消息路由
 2 import pika
 3 import sys
 4 connection=pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
 5 channel=connection.channel()
 6 channel.exchange_declare(
 7     exchange='direct_logs',
 8     type='direct'
 9 )
10 severity=sys.argv[1]if len(sys.argv)>1 else 'info'
11 message='hello world!'
12 channel.basic_publish(
13     exchange='direct_logs',
14     routing_key=severity,
15     body=message
16 )
17 print('[x] Sent %r:%r'%(severity,message))
18 connection.close()
send4
 1 import pika
 2 import sys
 3 connection=pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
 4 channel=connection.channel()
 5 channel.exchange_declare(
 6     exchange='direct_logs',
 7     type='direct'
 8 )
 9 result=channel.queue_declare(exclusive=True)
10 queue_name=result.method.queue
11 severities=sys.argv[1:]
12 if not severities:
13     print('Usage:%s[info][warning][error]'%(sys.argv[0]))
14     sys.exit(1)
15 # 用log的severity作为routing key,这样Consumer可以针对不同severity的log进行不同的处理
16 for severity in severities:
17     channel.queue_bind(
18         exchange='direct_logs',
19         queue=queue_name,
20         routing_key=severity
21     )
22 print('[*] Waiting for logs. To exit press CTRL+C')
23 
24 def callback(ch,method,properties,body):
25     print('[x]%r:%r'%(method.routing_key,body))
26 channel.basic_consume(
27     callback,
28     queue=queue_name,
29     no_ack=True
30 )
31 channel.start_consuming()
receive4

主题分发

 1 import pika
 2 import sys
 3 connection=pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
 4 channel=connection.channel()
 5 channel.exchange_declare(exchange='topic_logs',
 6                          type='topic')
 7 routing_key=sys.argv[1] if len(sys.argv)>1 else 'anonymous.info'
 8 message=''.join(sys.argv[2:]) or 'hello world!'
 9 channel.basic_publish(exchange='topic_logs',
10                       routing_key=routing_key,
11                       body=message)
12 print('[x] Sent %r:%r'%(routing_key,message))
13 connection.close()
send5
 1 import pika
 2 import sys
 3 connection=pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
 4 channel=connection.channel()
 5 channel.exchange_declare(exchange='topic_logs',
 6                          type='topic')
 7 result=channel.queue_declare(exclusive=True)
 8 queue_name=result.method.queue
 9 binding_keys=sys.argv[1:]
10 if not binding_keys:
11     print("Usage: %s [binding_key]..." % (sys.argv[0],)  )
12     sys.exit(1)
13 for binding_key in binding_keys:
14     channel.queue_bind(exchange='topic_logs',
15                        queue=queue_name,
16                        routing_key=binding_key)
17 
18 print(' [*] Waiting for logs. To exit press CTRL+C')
19 
20 
21 def callback(ch, method, properties, body):
22     print(" [x] %r:%r" % (method.routing_key, body,))
23 
24 
25 channel.basic_consume(callback,
26                       queue=queue_name,
27                       no_ack=True)
28 
29 channel.start_consuming()
receive5

5.4 适用于云计算集群的远程调用(Rpc)

设计图

工作流程:

当客户端启动时,它创建了匿名的exclusive callback queue.

客户端的RPC请求时将同时设置两个properties:reply_to设置为callback queue;coeewlarion_id设置为每个

request一个独一无二的值.

请求将被发送到an rec_queue queue.

RPC 端或者说server 一直在等待那个queue的请求.当请求到达时,它将通过在reply_to指定的queue回复

一个message给client.

clinet一直等待callback queue的数据.当message到达时,它将检查correlation_id的值,如果值和它request

发送时的一致那么就将返回响应.

 1 import pika  
 2   
 3 connection = pika.BlockingConnection(pika.ConnectionParameters(  
 4         host='localhost'))  
 5   
 6 channel = connection.channel()  
 7   
 8 channel.queue_declare(queue='rpc_queue')  
 9   
10 def fib(n):  
11     if n == 0:  
12         return 0  
13     elif n == 1:  
14         return 1  
15     else:  
16         return fib(n-1) + fib(n-2)  
17   
18 def on_request(ch, method, props, body):  
19     n = int(body)  
20   
21     print " [.] fib(%s)"  % (n,)  
22     response = fib(n)  
23   
24     ch.basic_publish(exchange='',  
25                      routing_key=props.reply_to,  
26                      properties=pika.BasicProperties(correlation_id = \  
27                                                      props.correlation_id),  
28                      body=str(response))  
29     ch.basic_ack(delivery_tag = method.delivery_tag)  
30   
31 channel.basic_qos(prefetch_count=1)  
32 channel.basic_consume(on_request, queue='rpc_queue')  
33   
34 print " [x] Awaiting RPC requests"  
35 channel.start_consuming()  
send6
 1 import pika  
 2 import uuid  
 3   
 4 class FibonacciRpcClient(object):  
 5     def __init__(self):  
 6         self.connection = pika.BlockingConnection(pika.ConnectionParameters(  
 7                 host='localhost'))  
 8   
 9         self.channel = self.connection.channel()  
10   
11         result = self.channel.queue_declare(exclusive=True)  
12         self.callback_queue = result.method.queue  
13   
14         self.channel.basic_consume(self.on_response, no_ack=True,  
15                                    queue=self.callback_queue)  
16   
17     def on_response(self, ch, method, props, body):  
18         if self.corr_id == props.correlation_id:  
19             self.response = body  
20   
21     def call(self, n):  
22         self.response = None  
23         self.corr_id = str(uuid.uuid4())  
24         self.channel.basic_publish(exchange='',  
25                                    routing_key='rpc_queue',  
26                                    properties=pika.BasicProperties(  
27                                          reply_to = self.callback_queue,  
28                                          correlation_id = self.corr_id,  
29                                          ),  
30                                    body=str(n))  
31         while self.response is None:  
32             self.connection.process_data_events()  
33         return int(self.response)  
34   
35 fibonacci_rpc = FibonacciRpcClient()  
36   
37 print " [x] Requesting fib(30)"  
38 response = fibonacci_rpc.call(30)  
39 print " [.] Got %r" % (response,)  
receive6

 

posted @ 2017-09-07 21:29  qianxiamo  阅读(209)  评论(0)    收藏  举报