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()
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()
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()
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()
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()
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()
消息路由
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()
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()
主题分发
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()
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()
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()
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,)
浙公网安备 33010602011771号