简介
RabbitMQ:接受消息再传递消息,能够视为一个“邮局”。发送者和接受者经过队列来进行交互,队列的大小能够视为无限的,多个发送者能够发生给一个队列,多个接收者也能够从一个队列中接受消息。python
code
rabbitmq使用的协议是amqp,用于python的推荐客户端是pikaweb
pip install pika -i https://pypi.douban.com/simple/
生产者:send.py服务器
import pika # 创建一个链接 connection = pika.BlockingConnection(pika.ConnectionParameters( 'localhost')) # 链接本地的RabbitMQ服务器 channel = connection.channel() # 得到channel
这里连接的是本机的,若是想要链接其余机器上的服务器,只要填入地址或主机名便可。函数
接下来咱们开始发送消息了,注意要确保接受消息的队列是存在的,不然rabbitmq就丢弃掉该消息post
channel.queue_declare(queue='hello') # 在RabbitMQ中建立hello这个队列 channel.basic_publish(exchange='', # 使用默认的exchange来发送消息到队列 routing_key='hello', # 发送到该队列 hello 中 body='Hello World!') # 消息内容 connection.close() # 关闭 同时flush
RabbitMQ默认须要1GB的空闲磁盘空间,不然发送会失败。fetch
这时已在本地队列hello中存放了一个消息,若是使用 rabbitmqctl list_queues 可看到ui
hello 1
说明有一个hello队列 里面存放了一个消息spa
消费者:receive.pycode
import pika connection = pika.BlockingConnection(pika.ConnectionParameters( 'localhost')) channel = connection.channel()
仍是先连接到服务器,和以前发送时相同blog
channel.queue_declare(queue='hello') # 此处就是声明了 来确保该队列 hello 存在 能够屡次声明 这里主要是为了防止接受程序先运行时出错 def callback(ch, method, properties, body): # 用于接收到消息后的回调 print(" [x] Received %r" % body) channel.basic_consume(callback, queue='hello', # 收指定队列hello的消息 no_ack=True) #在处理完消息后不发送ack给服务器 channel.start_consuming() # 启动消息接受 这会进入一个死循环
工做队列(任务队列)
工做队列是用于分发耗时任务给多个工做进程的。不当即作那些耗费资源的任务(须要等待这些任务完成),而是安排这些任务以后执行。例如咱们把task做为message发送到队列里,启动工做进程来接受并最终执行,且可启动多个工做进程来工做。这适用于web应用,即不该在一个http请求的处理窗口内完成复杂任务。
channel.basic_publish(exchange='', routing_key='task_queue', body=message, properties=pika.BasicProperties( delivery_mode = 2, # 使得消息持久化 ))
分配消息的方式为 轮询 即每一个工做进程得到相同的消息数。
消息ack
若是消息分配给某个工做进程,可是该工做进程未处理完成就崩溃了,可能该消息就丢失了,由于rabbitmq一旦把一个消息分发给工做进程,它就把该消息删掉了。
为了预防消息丢失,rabbitmq提供了ack,即工做进程在收到消息并处理后,发送ack给rabbitmq,告知rabbitmq这时候能够把该消息从队列中删除了。若是工做进程挂掉 了,rabbitmq没有收到ack,那么会把该消息 从新分发给其余工做进程。不须要设置timeout,即便该任务须要很长时间也能够处理。
ack默认是开启的,以前咱们的工做进程显示指定了no_ack=True
channel.basic_consume(callback, queue='hello') # 会启用ack
带ack的callback:
def callback(ch, method, properties, body): print " [x] Received %r" % (body,) time.sleep( body.count('.') ) print " [x] Done" ch.basic_ack(delivery_tag = method.delivery_tag) # 发送ack
消息持久化
可是,有时RabbitMQ重启了,消息也会丢失。可在建立队列时设置持久化:
(队列的性质一旦肯定没法改变)
channel.queue_declare(queue='task_queue', durable=True)
同时在发送消息时也得设置该消息的持久化属性:
channel.basic_publish(exchange='', routing_key="task_queue", body=message, properties=pika.BasicProperties( delivery_mode = 2, # make message persistent ))
可是,若是在RabbitMQ刚接收到消息还没来得及存储,消息仍是会丢失。同时,RabbitMQ也不是在接受到每一个消息都进行存盘操做。若是还须要更完善的保证,须要使用publisher confirm。
公平的消息分发
轮询模式的消息分发可能并不公平,例如奇数的消息都是繁重任务的话,某些进程则会一直运行繁 重任务。即便某工做进程上有积压的消息未处理,如不少都没发ack,可是RabbitMQ仍是会按照顺序发消息给它。能够在接受进程中加设置:
channel.basic_qos(prefetch_count=1)
告知RabbitMQ,这样在一个工做进程没回发ack状况下是不会再分配消息给它。
群发
通常状况下,一条消息是发送给一个工做进程,而后完成,有时想把一条消息同时发送给多个进程:
exchange
发送者是否是直接发送消息到队列中的,事实上发生者根本不知道消息会发送到那个队列,发送者只能把消息发送到exchange里。exchange一方面收生产者的消息,另外一方面把他们推送到队列中。因此做为exchange,它须要知道当收到消息时它须要作什么,是应该把它加到一个特殊的队列中仍是放到不少的队列中,或者丢弃。exchange有direct、topic、headers、fanout等种类,而群发使用的即fanout。以前在发布消息时,exchange的值为 '' 即便用default exchange。
channel.exchange_declare(exchange='logs', type='fanout') # 该exchange会把消息发送给全部它知道的队列中
临时队列
result = channel.queue_declare() # 建立一个随机队列 result = channel.queue_declare(exclusive=True) # 建立一个随机队列,同时在没有接收者链接该队列后则销毁它 queue_name = result.method.queue
这样result.method.queue便是队列名称,在发送或接受时便可使用。
绑定exchange 和 队列
channel.queue_bind(exchange='logs', queue='hello')
logs在发送消息时给hello也发一份。
在发送消息是使用刚刚建立的 logs exchange
channel.basic_publish(exchange='logs', routing_key='', body=message)
路由
以前已经使用过bind,即创建exchange和queue的关系(该队列对来自该exchange的消息有兴趣),bind时可另外指定routing_key选项。
使用direct exchange
将对应routing key的消息发送到绑定相同routing key的队列中
channel.exchange_declare(exchange='direct_logs', type='direct')
发送函数,发布不一样severity的消息:
channel.basic_publish(exchange='direct_logs', routing_key=severity, body=message)
接受函数中绑定对应severity的:
channel.queue_bind(exchange='direct_logs', queue=queue_name, routing_key=severity)
使用topic exchange
以前使用的direct exchange 只能绑定一个routing key,可使用这种能够拿.隔开routing key的topic exchange,例如:
"stock.usd.nyse" "nyse.vmw"
和direct exchange同样,在接受者那边绑定的key与发送时指定的routing key相同便可,另外有些特殊的值:
* 表明1个单词 # 表明0个或多个单词
若是发送者发出的routing key都是3个部分的,如:celerity.colour.species。
Q1: *.orange.* 对应的是中间的colour都为orange的 Q2: *.*.rabbit 对应的是最后部分的species为rabbit的 lazy.# 对应的是第一部分是lazy的
qucik.orange.rabbit Q1 Q2均可接收到,quick.orange.fox 只有Q1能接受到,对于lazy.pink.rabbit虽然匹配到了Q2两次,可是只会发送一次。若是绑定时直接绑定#,则会收到全部的。
RPC
在远程机器上运行一个函数而后得到结果。
1、客户端启动 同时设置一个临时队列用于接受回调,绑定该队列
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.channel.basic_consume(self.on_response, no_ack=True, queue=self.callback_queue)
2、客户端发送rpc请求,同时附带reply_to对应回调队列,correlation_id设置为每一个请求的惟一id(虽说能够为每一次RPC请求都建立一个回调队列,可是这样效率不高,若是一个客户端只使用一个队列,则须要使用correlation_id来匹配是哪一个请求),以后阻塞在回调队列直到收到回复
注意:若是收到了非法的correlation_id直接丢弃便可,由于有这种状况--服务器已经发了响应可是还没发ack就挂了,等一会服务器重启了又会从新处理该任务,又发了一遍相应,可是这时那个请求已经被处理掉了
channel.basic_publish(exchange='', routing_key='rpc_queue', properties=pika.BasicProperties( reply_to = self.callback_queue, correlation_id = self.corr_id, ), body=str(n)) # 发出调用 while self.response is None: # 这边就至关于阻塞了 self.connection.process_data_events() # 查看回调队列 return int(self.response)
三、请求会发送到rpc_queue队列
四、RPC服务器从rpc_queue中取出,执行,发送回复
channel.basic_consume(on_request, queue='rpc_queue') # 绑定 等待请求 # 处理以后: ch.basic_publish(exchange='', routing_key=props.reply_to, properties=pika.BasicProperties(correlation_id = \ props.correlation_id), body=str(response)) # 发送回复到回调队列 ch.basic_ack(delivery_tag = method.delivery_tag) # 发送ack
五、客户端从回调队列中取出数据,检查correlation_id,执行相应操做
if self.corr_id == props.correlation_id: self.response = body