消息中间件—RabbitMQ
应用场景:异步
开发语言 :erlang
端口号:
4369 – erlang发现口
5672 --client端通信口
15672 – 管理界面ui端口
25672 – server间内部通信口
一对一的发送
#############声明send函数#######################################
import pika
credentials = pika.PlainCredentials('test','test')
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost',credentials=credentials))
channel = connection.channel() #声明队列向其发送消息
channel.queue_declare(queue='hello') #声明队列
channel.basic_publish(exechange='',
routing_key='hello',
body='Hello,World!')
print("Send 'Hello World!'")
connection.close()
#################################################################
#############声明receive函数#######################################
import pika
credentials = pika.PlainCredentials('test','test')
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost',credentials=credentials))
channel = connection.channel() #声明队列
channel.queue_declare(queue='hello') #声明队列向其发消息
def callback(ch, method, properties, body): #定义callback函数
print("[x] Received %r" % ch, method, properties, body)
channel.basic_consume(callback,
queue='hello',
no_ack=True)
print('[*] Waiting for messages.To exit press CTRL+C')
channel.start_consuming() #开始消费
#################################################################
声明一个队列,然后往其中发消息,然后我们准备两个消费者来接收,默认情况的接收顺序是依次公平接收。
rabbitmq循环调度,将消息循环发送给不同的消费者,如:消息1,3,5发送给消费者1;消息2,4,6发送给消费者2。
no_ack参数
ack参数是客户端在消费完消息之后向客户端返回的一个状态,默认我们要开启
消费者处理任务结束后,会给服务器一个反馈。服务器接收到反馈后就会删除掉任务,如果服务器没有接收到反馈,就会从其他地方拿到任务放回队列,让其它用户接收。
delivery_mode参数 —消息持久化
durable参数 —队列持久化
消息持久化,将消息写入硬盘中。 RabbitMQ不允许你重新定义一个已经存在、但属性不同的queue。需要标记消息为持久化的 - 要通过设置 delivery_mode 属性为 2来实现。
channel.queue_declare(queue='task_queue', durable=True) #创建一个新队列task_queue,设置队列持久化,注意不要跟已存在的队列重名,否则有报错
channel.basic_publish(exchange='',
14 routing_key='worker',#写明将消息发送给队列worker
15 body=message, #要发送的消息
16 properties=pika.BasicProperties(delivery_mode=2,)#设置消息持久化,将要发送的消息的属性标记为2,表示该消息要持久化
17 )
完整代码示例:
生产者:
import pika
import sys
username = 'wt' #指定远程rabbitmq的用户名密码
pwd = '111111'
user_pwd = pika.PlainCredentials(username, pwd)
s_conn = pika.BlockingConnection(pika.ConnectionParameters('192.168.1.240', credentials=user_pwd))#创建连接
channel = s_conn.channel() #在连接上创建一个频道
channel.queue_declare(queue='task_queue', durable=True) #创建一个新队列task_queue,设置队列持久化,注意不要跟已存在的队列重名,否则有报错
message = "Hello World"
channel.basic_publish(exchange='',
routing_key='worker',#写明将消息发送给队列worker
body=message, #要发送的消息
properties=pika.BasicProperties(delivery_mode=2,)#设置消息持久化,将要发送的消息的属性标记为2,表示该消息要持久化
)
print(" [生产者] Send %r " % message)
消费者:
import pika
import time
username = 'wt'#指定远程rabbitmq的用户名密码
pwd = '111111'
user_pwd = pika.PlainCredentials(username, pwd)
s_conn = pika.BlockingConnection(pika.ConnectionParameters('192.168.1.240', credentials=user_pwd))#创建连接
channel = s_conn.channel()#在连接上创建一个频道
channel.queue_declare(queue='task_queue', durable=True) #创建一个新队列task_queue,设置队列持久化,注意不要跟已存在的队列重名,否则有报错
def callback(ch, method, properties, body):
print(" [消费者] Received %r" % body)
time.sleep(1)
print(" [消费者] Done")
ch.basic_ack(delivery_tag=method.delivery_tag)# 接收到消息后会给rabbitmq发送一个确认
channel.basic_qos(prefetch_count=1) # 消费者给rabbitmq发送一个信息:在消费者处理完消息之前不要再给消费者发送消息
channel.basic_consume(callback,
queue='worker',
#这里就不用再写no_ack=False了
)
channel.start_consuming()
----------------------友好的分割线------------------------------
交换机
exchange:交换机。生产者不是将消息发送给队列,而是将消息发送给交换机,由交换机决定将消息发送给哪个队列。所以exchange必须准确知道消息是要送到哪个队列,还是要被丢弃。因此要在exchange中给exchange定义规则,所有的规则都是在exchange的类型中定义的。
exchange有4个类型:direct, topic, headers ,fanout
之前,我们并没有讲过exchange,但是我们仍然可以将消息发送到队列中。这是因为我们用的是默认exchange.也就是说之前写的:exchange='',空字符串表示默认的exchange。
之前的代码结构:
1 channel.basic_publish(exchange='',
2 routing_key='hello',
3 body=message)
fanout:广播类型,生产者将消息发送给所有消费者,如果某个消费者没有收到当前消息,就再也收不到了(消费者就像收音机)
生产者:(可以用作日志收集系统)
import pika
import sys
username = 'wt' #指定远程rabbitmq的用户名密码
pwd = '111111'
user_pwd = pika.PlainCredentials(username, pwd)
s_conn = pika.BlockingConnection(pika.ConnectionParameters('192.168.1.240', credentials=user_pwd))#创建连接
channel = s_conn.channel() #在连接上创建一个频道
channel.exchange_declare(exchange='logs',
type='fanout')#创建一个fanout(广播)类型的交换机exchange,名字为logs。
message = "info: Hello World!"
channel.basic_publish(exchange='logs',#指定交换机exchange为logs,这里只需要指定将消息发给交换机logs就可以了,不需要指定队列,因为生产者消息是发送给交换机的。
routing_key='',#在fanout类型中,绑定关键字routing_key必须忽略,写空即可
body=message)
print(" [x] Sent %r" % message)
connection.close()
消费者:
import pika
import sys
username = 'wt' #指定远程rabbitmq的用户名密码
pwd = '111111'
user_pwd = pika.PlainCredentials(username, pwd)
s_conn = pika.BlockingConnection(pika.ConnectionParameters('192.168.1.240', credentials=user_pwd))#创建连接
channel = s_conn.channel() #在连接上创建一个频道
channel.exchange_declare(exchange='logs',
type='fanout')#消费者需再次声明一个exchange 以及类型。
result = channel.queue_declare(exclusive=True)#创建一个队列,exclusive=True(唯一性)表示在消费者与rabbitmq断开连接时,该队列会自动删除掉。
queue_name = result.method.queue#因为rabbitmq要求新队列名必须是与现存队列名不同,所以为保证队列的名字是唯一的,method.queue方法会随机创建一个队列名字,如:‘amq.gen-JzTY20BRgKO-HjmUJj0wLg‘。
channel.queue_bind(exchange='logs',
queue=queue_name)#将交换机logs与接收消息的队列绑定。表示生产者将消息发给交换机logs,logs将消息发给随机队列queue,消费者在随机队列queue中取消息
print(' [消费者] Waiting for logs. To exit press CTRL+C')
def callback(ch, method, properties, body):
print(" [消费者] %r" % body)
channel.basic_consume(callback,#调用回调函数从queue中取消息
queue=queue_name,
no_ack=True)#设置为消费者不给rabbitmq回复确认。
channel.start_consuming()#循环等待接收消息。
这样,开启多个消费者后,会同时从生产者接收相同的消息。
direct:关键字类型。功能:交换机根据生产者消息中含有的不同的关键字将消息发送给不同的队列,消费者根据不同的关键字从不同的队列取消息
生产者:不用创建对列
import pika
import sys
username = 'wt' #指定远程rabbitmq的用户名密码
pwd = '111111'
user_pwd = pika.PlainCredentials(username, pwd)
s_conn = pika.BlockingConnection(pika.ConnectionParameters('192.168.1.240', credentials=user_pwd))#创建连接
channel = s_conn.channel() #在连接上创建一个频道
channel.exchange_declare(exchange='direct_logs',
type='direct')#创建一个交换机并声明exchange的类型为:关键字类型,表示该交换机会根据消息中不同的关键字将消息发送给不同的队列
severity = 'info'#severity这里只能为一个字符串,这里为‘info’表明本生产者只将下面的message发送到info队列中,消费者也只能从info队列中接收info消息
message = 'Hello World!'
channel.basic_publish(exchange='direct_logs',#指明用于发布消息的交换机、关键字
routing_key=severity,#绑定关键字,即将message与关键字info绑定,明确将消息发送到哪个关键字的队列中。
body=message)
print(" [生产者] Sent %r:%r" % (severity, message))
connection.close()
消费者:
import pika
import sys
username = 'wt' #指定远程rabbitmq的用户名密码
pwd = '111111'
user_pwd = pika.PlainCredentials(username, pwd)
s_conn = pika.BlockingConnection(pika.ConnectionParameters('192.168.1.240', credentials=user_pwd))#创建连接
channel = s_conn.channel() #在连接上创建一个频道
channel.exchange_declare(exchange='direct_logs',
type='direct')#创建交换机,命名为‘direct_logs’并声明exchange类型为关键字类型。
result = channel.queue_declare(exclusive=True)#创建随机队列,当消费者与rabbitmq断开连接时,这个队列将自动删除。
queue_name = result.method.queue#分配随机队列的名字。
severities = ['info','err']#可以接收绑定关键字info或err的消息,列表中也可以只有一个
if not severities:#判断如果输入有误,输出用法
sys.stderr.write("Usage: %s [info] [warning] [error]\n" % sys.argv[0])
sys.exit(1)
for severity in severities:
channel.queue_bind(exchange='direct_logs',#将交换机、队列、关键字绑定在一起,使消费者只能根据关键字从不同队列中取消息
queue=queue_name,
routing_key=severity)#该消费者绑定的关键字。
print(' [消费者] Waiting for logs. To exit press CTRL+C')
def callback(ch, method, properties, body):#定义回调函数,接收消息
print(" [消费者] %r:%r" % (method.routing_key, body))
channel.basic_consume(callback,
queue=queue_name,
no_ack=True)#消费者接收消息后,不给rabbimq回执确认。
channel.start_consuming()#循环等待消息接收。
topics:模糊匹配类型。比较常用
发送到一个 topics交换机的消息,它的 routing_key不能是任意的 – 它的routing_key必须是一个用小数点分割的单词列表。 这个字符可以是任何单词,但是通常是一些指定意义的字符。比如:“stock.usd.nyse",“nyse.vmw”,“quick.orange.rabbit”. 这里可以是你想要路由键的任意字符。最高限制为255字节。
生产者与消费者的routing_key必须在同一个表单中。 Topic交换的背后的逻辑类似直接交换(direct) – 包含特定关键字的消息将会分发到所有匹配的关键字队列中。然后有两个重要的特殊情况:
绑定键值:
(星*) 可代替一个单词
(井 # ) 可代替0个或多个单词
生产者:
import pika
import sys
username = 'wt' #指定远程rabbitmq的用户名密码
pwd = '111111'
user_pwd = pika.PlainCredentials(username, pwd)
s_conn = pika.BlockingConnection(pika.ConnectionParameters('192.168.1.240', credentials=user_pwd))#创建连接
channel = s_conn.channel() #在连接上创建一个频道
channel.exchange_declare(exchange='topic_logs',
type='topic') # 创建模糊匹配类型的exchange。。
routing_key = '[warn].kern'##这里关键字必须为点号隔开的单词,以便于消费者进行匹配。引申:这里可以做一个判断,判断产生的日志是什么级别,然后产生对应的routing_key,使程序可以发送多种级别的日志
message = 'Hello World!'
channel.basic_publish(exchange='topic_logs',#将交换机、关键字、消息进行绑定
routing_key=routing_key, # 绑定关键字,将队列变成[warn]日志的专属队列
body=message)
print(" [x] Sent %r:%r" % (routing_key, message))
s_conn.close()
消费者:
import pika
import sys
username = 'wt'#指定远程rabbitmq的用户名密码
pwd = '111111'
user_pwd = pika.PlainCredentials(username, pwd)
s_conn = pika.BlockingConnection(pika.ConnectionParameters('192.168.1.240', credentials=user_pwd))#创建连接
channel = s_conn.channel()#在连接上创建一个频道
channel.exchange_declare(exchange='topic_logs',
type='topic') # 声明exchange的类型为模糊匹配。
result = channel.queue_declare(exclusive=True) # 创建随机一个队列当消费者退出的时候,该队列被删除。
queue_name = result.method.queue # 创建一个随机队列名字。
binding_keys = ['[warn]', 'info.*']#绑定键。‘#’匹配所有字符,‘*’匹配一个单词。这里列表中可以为一个或多个条件,能通过列表中字符匹配到的消息,消费者都可以取到
if not binding_keys:
sys.stderr.write("Usage: %s [binding_key]...\n" % sys.argv[0])
sys.exit(1)
for binding_key in binding_keys:#通过循环绑定多个“交换机-队列-关键字”,只要消费者在rabbitmq中能匹配到与关键字相应的队列,就从那个队列里取消息
channel.queue_bind(exchange='topic_logs',
queue=queue_name,
routing_key=binding_key)
print(' [*] Waiting for logs. To exit press CTRL+C')
def callback(ch, method, properties, body):
print(" [x] %r:%r" % (method.routing_key, body))
channel.basic_consume(callback,
queue=queue_name,
no_ack=True)#不给rabbitmq发送确认
channel.start_consuming()#循环接收消息
远程过程调用(RPC)Remote procedure call
发送端发送消息出去后没有结果返回。如果只是单纯发送消息,当然没有问题了,但是在实际中,常常会需要接收端将收到的消息进行处理之后,返回给发送端。
处理方法描述:发送端在发送信息前,产生一个接收消息的临时队列,该队列用来接收返回的结果。其实在这里接收端、发送端的概念已经比较模糊了,因为发送端也同样要接收消息,接收端同样也要发送消息。
示例内容:假设有一个控制中心和一个计算节点,控制中心会将一个自然数N发送给计算节点,计算节点将N值加1后,返回给控制中心。这里用center.py模拟控制中心,compute.py模拟计算节点。
compute.py代码分析
#!/usr/bin/env python
#coding=utf8
import pika
#连接rabbitmq服务器
connection = pika.BlockingConnection(pika.ConnectionParameters(
host='localhost'))
channel = connection.channel()
#定义队列
channel.queue_declare(queue='compute_queue')
print ' [*] Waiting for n'
#将n值加1
def increase(n):
return n + 1
#定义接收到消息的处理方法
def request(ch, method, properties, body):
print " [.] increase(%s)" % (body,)
response = increase(int(body))
#将计算结果发送回控制中心
ch.basic_publish(exchange='',
routing_key=properties.reply_to,
body=str(response))
ch.basic_ack(delivery_tag = method.delivery_tag)
channel.basic_qos(prefetch_count=1)
channel.basic_consume(request, queue='compute_queue')
channel.start_consuming()
计算节点的代码比较简单,值得一提的是,原来的接收方法都是直接将消息打印出来,这边进行了加一的计算,并将结果发送回控制中心。
center.py代码分析
#!/usr/bin/env python
#coding=utf8
import pika
class Center(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.channel.basic_consume(self.on_response,
no_ack=True,
queue=self.callback_queue)
#定义接收到返回消息的处理方法
def on_response(self, ch, method, props, body):
self.response = body
def request(self, n):
self.response = None
#发送计算请求,并声明返回队列
self.channel.basic_publish(exchange='',
routing_key='compute_queue',
properties=pika.BasicProperties(
reply_to = self.callback_queue,
),
body=str(n))
#接收返回的数据
while self.response is None:
self.connection.process_data_events()
return int(self.response)
center = Center()
print " [x] Requesting increase(30)"
response = center.request(30)
print " [.] Got %r" % (response,)
上例代码定义了接收返回数据的队列和处理方法,并且在发送请求的时候将该队列赋值给reply_to,在计算节点代码中就是通过这个参数来获取返回队列的。