RabbitMQ队列
一、RabbitMQ队列安装
1.安装
#Centos7 安装 wget https://github.com/rabbitmq/rabbitmq-server/releases/download/rabbitmq_v3_6_10/rabbitmq-server-3.6.10-1.el7.noarch.rpm yum install epel-release -y yum install rabbitmq-server-3.6.10-1.el7.noarch.rpm rabbitmq-plugins enable rabbitmq_management cp /usr/share/doc/rabbitmq-server-3.6.10/rabbitmq.config.example /etc/rabbitmq/rabbitmq.config #在这里有可能会出现一个小问题,就是ip不对,需要去 vim /etc/hosts #修改一下ip就好了 systemctl restart rabbitmq-server #重启服务 systemctl status rabbitmq-server rabbitmqctl add_user admin 123456 #创建用户,密码 rabbitmqctl set_user_tags admin administrator #给admin用户分配administrator角色 sudo rabbitmqctl set_permissions -p / alex ".*" ".*" ".*" #给用户设定权限
rabbitmqctl list_queues #查看消息队列 轮询
2.安装pika模块
pip install pika or easy_install pika or 源码 https://pypi.python.org/pypi/pika
二、RabbitMQ队列
实现最简单的队列通信
client端
#客户端 import pika credentials = pika.PlainCredentials('root','123456')#连接的用户 parameters = pika.ConnectionParameters(host='192.168.100.129',credentials=credentials) #连接的参数设置 connection = pika.BlockingConnection(parameters)#闭塞连接 channel = connection.channel() #队列连接通道 #声明queue channel.queue_declare(queue='hello1',durable=True)#queue:队列的名称, #发消息 channel.basic_publish( exchange='', routing_key='hello1', #队列 body='HELLO WORLD',#内容 ) print("[x] Sent 'Hello World!'") connection.close()#关闭通信
server端
#服务端 import pika credentials = pika.PlainCredentials('root','123456')#连接的用户 parameters = pika.ConnectionParameters(host='192.168.100.129',credentials=credentials)#连接的配置 connection = pika.BlockingConnection(parameters) channel = connection.channel()#队列连接通道 #ch:就是通道的实例,channel def callback(ch,method,properties,body): print("[X] Received %r"% body) ch.basic_ack(delivery_tag=method.delivery_tag) channel.basic_consume( callback,#取到消息后,调用callback函数 queue='hello1',#队列名 ) print('[*] Waiting for messages. To exit press CTRL+C') channel.start_consuming()#阻塞模式,即数据传输
work queues
在这种模式下,RabbitMQ会默认把p发的消息依次分发给各个消费者(c),跟负载均衡差不多
生产者
#!/usr/bin/python # -*- coding: UTF-8 -*- import pika credentials = pika.PlainCredentials('root','123456')#连接的用户 parameters = pika.ConnectionParameters(host='192.168.100.129',credentials=credentials) #连接的参数设置 connection = pika.BlockingConnection(parameters)#闭塞连接 channel = connection.channel() #队列连接通道 #声明queue,队列持续化 channel.queue_declare(queue='hello1',durable=True)#queue:队列的名称,durable:队列持续化 #消息持续化 channel.basic_publish( exchange='', routing_key='hello1', #队列 properties=pika.BasicProperties( delivery_mode=2,#使消息持续,即当消息没有安全被接收执行,那么此消息还是会回到队列中,等待其他的消费者接收,直到接收完成。才消失 ), body="hello world3", ) #当linux重启,那么会使你的队列以及消息全都存在,关键字段durable=True,delivery_mode=2 print("[x] Sent 'Hello World1!'") connection.close()#关闭通信 #同一个队列中可以存放多条信息,等待服务端获取数据才会少 #如果开启了多个服务端。那么他们会一人接收一条数据的方式去执行。
消费者
#!/usr/bin/python # -*- coding: UTF-8 -*- #服务端 import pika,time credentials = pika.PlainCredentials('root','123456')#连接的用户 parameters = pika.ConnectionParameters(host='192.168.100.129',credentials=credentials)#连接的配置 connection = pika.BlockingConnection(parameters) channel = connection.channel()#队列连接通道 #ch:就是通道的实例,channel def callback(ch,method,properties,body): print("[X] Received %r"% body) time.sleep(20)#sleep。进行一个阻断的效果,当数据接收后,没完全接收,那么还会在队列中,等待其他人接收 print('ok') print("method.delivery_tag", method.delivery_tag) ch.basic_ack(delivery_tag=method.delivery_tag) #手动确认,消息持久性 channel.basic_consume( callback,#取到消息后,调用callback函数 queue='hello1',#队列名 # no_ack= True #默认是false ) print('[*] Waiting for messages. To exit press CTRL+C') channel.start_consuming()#阻塞模式,即数据传输
消息持久化
发送端
#!/usr/bin/python # -*- coding: UTF-8 -*- import pika,sys credentials = pika.PlainCredentials('root','123456')#连接的用户 parameters = pika.ConnectionParameters(host='192.168.100.129',credentials=credentials) #连接的参数设置 connection = pika.BlockingConnection(parameters)#闭塞连接 channel = connection.channel() #队列连接通道 #声明queue,队列持续化 channel.exchange_declare(exchange='logs',type='fanout') message = ''.join(sys.argv[1:]) or "info:Hello World!" #消息持续化 channel.basic_publish( exchange='logs', routing_key='', #队列 body=message, ) print(" [x] Sent %r" % message) connection.close()#关闭通信 #广播,P端发送一条消息,同时在线的C端都能收到消息,
接收端
#!/usr/bin/python # -*- coding: UTF-8 -*- import pika,time #连接 credentials = pika.PlainCredentials('root','123456')#连接的用户 parameters = pika.ConnectionParameters(host='192.168.100.129',credentials=credentials)#连接的配置 connection = pika.BlockingConnection(parameters) channel = connection.channel()#队列连接通道 channel.exchange_declare(exchange='logs',type='fanout') #不指定queue名字,rabbit会随机分配一个名字,exclusive=True会在使用此queue的消费者断开后,自动将queue删除 queue_obj = channel.queue_declare(exclusive=True) queue_name = queue_obj.method.queue print('queue name',queue_name,queue_obj) channel.queue_bind(exchange='logs',queue=queue_name)#绑定队列到Exchange print(' [*] Waiting for logs. To exit press CTRL+C') #ch:就是通道的实例,channel def callback(ch,method,properties,body): print("[X] Received %r"% body) channel.basic_consume( callback,#取到消息后,调用callback函数 queue=queue_name, no_ack= True #默认是false ) channel.start_consuming()#阻塞模式,即数据传输 #在线的C端,能够实时的接收到P端发送来的信息
消息公平分发
如果Rabbit只管按顺序把消息发到各个消费者身上,不考虑消费者负载的话,很可能出现,一个机器配置不高的消费者那里堆积了很多消息处理不完,同时配置高的消费者却一直很轻松。为解决此问题,可以在各个消费者端,配置perfetch=1,意思就是告诉RabbitMQ在我这个消费者当前消息还没处理完的时候就不要再给我发新消息了。
公平的接收端
#!/usr/bin/python # -*- coding: UTF-8 -*- import pika import time credentials = pika.PlainCredentials('root', '123456') parameters = pika.ConnectionParameters(host='192.168.100.129',credentials=credentials) connection = pika.BlockingConnection(parameters) channel = connection.channel() #队列连接通道 def callback(ch, method, properties, body): print(" [x] Received %r" % body) time.sleep(1) print('msg handle done...',body) ch.basic_ack(delivery_tag=method.delivery_tag) channel.basic_qos(prefetch_count=1) channel.basic_consume(callback, #取到消息后,调用callback 函数 queue='hello1',) #no_ack=True) #消息处理后,不向rabbit-server确认消息已消费完毕 print(' [*] Waiting for messages. To exit press CTRL+C') channel.start_consuming() #阻塞模式 #公平的消费者。 生产者使用的是producer-2 #谁先完成订单,就给谁先派单, #测试的模式的是,sleep,这样通过完成的时间快慢,就可以很明显的区分出来!
Publish\Subscribe(消息发布\订阅)
之前的例子都基本都是1对1的消息发送和接收,即消息只能发送到指定的queue里,但有些时候你想让你的消息被所有的Queue收到,类似广播的效果,这时候就要用到exchange了,
fanout: 所有bind到此exchange的queue都可以接收消息
direct: 通过routingKey和exchange决定的那个唯一的queue可以接收消息
topic:所有符合routingKey(此时可以是一个表达式)的routingKey所bind的queue可以接收消息
表达式符号说明:#代表一个或多个字符,*代表任何字符
例:#.a会匹配a.a,aa.a,aaa.a等
*.a会匹配a.a,b.a,c.a等
注:使用RoutingKey为#,Exchange Type为topic的时候相当于使用fanout
headers: 通过headers 来决定把消息发给哪些queue
fanout:在线的用户可实时接收订阅的数据
接收端
#!/usr/bin/python # -*- coding: UTF-8 -*- import pika,time #连接 credentials = pika.PlainCredentials('root','123456')#连接的用户 parameters = pika.ConnectionParameters(host='192.168.100.129',credentials=credentials)#连接的配置 connection = pika.BlockingConnection(parameters) channel = connection.channel()#队列连接通道 channel.exchange_declare(exchange='logs',type='fanout') #不指定queue名字,rabbit会随机分配一个名字,exclusive=True会在使用此queue的消费者断开后,自动将queue删除 queue_obj = channel.queue_declare(exclusive=True) queue_name = queue_obj.method.queue print('queue name',queue_name,queue_obj) channel.queue_bind(exchange='logs',queue=queue_name)#绑定队列到Exchange print(' [*] Waiting for logs. To exit press CTRL+C') #ch:就是通道的实例,channel def callback(ch,method,properties,body): print("[X] Received %r"% body) channel.basic_consume( callback,#取到消息后,调用callback函数 queue=queue_name, no_ack= True #默认是false ) channel.start_consuming()#阻塞模式,即数据传输 #在线的C端,能够实时的接收到P端发送来的信息
发送端
#!/usr/bin/python # -*- coding: UTF-8 -*- import pika,sys credentials = pika.PlainCredentials('root','123456')#连接的用户 parameters = pika.ConnectionParameters(host='192.168.100.129',credentials=credentials) #连接的参数设置 connection = pika.BlockingConnection(parameters)#闭塞连接 channel = connection.channel() #队列连接通道 #声明queue,队列持续化 channel.exchange_declare(exchange='logs',type='fanout') message = ''.join(sys.argv[1:]) or "info:Hello World!" #消息持续化 channel.basic_publish( exchange='logs', routing_key='', #队列 body=message, ) print(" [x] Sent %r" % message) connection.close()#关闭通信 #广播,P端发送一条消息,同时在线的C端都能收到消息,
direct:选择性的接收消息(info warning error)
RabbitMQ还支持根据关键字发送,即:队列绑定关键字,发送者将数据根据关键字发送到消息exchange,exchange根据 关键字 判定应该将数据发送至指定队列。
发送端
#!/usr/bin/python # -*- coding: UTF-8 -*- import pika,sys credentials = pika.PlainCredentials('root','123456')#连接的用户 parameters = pika.ConnectionParameters(host='192.168.100.129',credentials=credentials) #连接的参数设置 connection = pika.BlockingConnection(parameters)#闭塞连接 channel = connection.channel() #队列连接通道 # type='direct':组播类型 channel.exchange_declare(exchange='direct_log',type='direct') log_level = sys.argv[1] if len(sys.argv) > 1 else 'info' #严重程度 message = ''.join(sys.argv[1:]) or "info:Hello World!" #消息持续化 channel.basic_publish( exchange='direct_log', routing_key=log_level, #队列 body=message, ) print(" [x] Sent %r" % message) connection.close()#关闭通信 """ 启动三台客户端,运行不同的状态,info warning error 服务端运行其中的任意一个命令执行。 """
接收端
#!/usr/bin/python # -*- coding: UTF-8 -*- import pika,time,sys #连接 credentials = pika.PlainCredentials('root','123456')#连接的用户 parameters = pika.ConnectionParameters(host='192.168.100.129',credentials=credentials)#连接的配置 connection = pika.BlockingConnection(parameters) channel = connection.channel()#队列连接通道 #不指定queue名字,rabbit会随机分配一个名字,exclusive=True会在使用此queue的消费者断开后,自动将queue删除 queue_obj = channel.queue_declare(exclusive=True) queue_name = queue_obj.method.queue print('queue name',queue_name,queue_obj) logs_levels = sys.argv[1:]#组播的状态,info warning error if not logs_levels: sys.stderr.write("Usage: %s [info] [warning] [error]\n" % sys.argv[0]) sys.exit(1) for level in logs_levels: channel.queue_bind( exchange='direct_log', queue=queue_name, routing_key=level, #绑定队列到Exchange ) print(' [*] Waiting for logs. To exit press CTRL+C') #ch:就是通道的实例,channel def callback(ch,method,properties,body): print("[X] Received %r"% body) #发送消息 channel.basic_consume( callback,#取到消息后,调用callback函数 queue=queue_name, no_ack= True #默认是false ) channel.start_consuming()#阻塞模式,即数据传输 """ 启动三台客户端,运行不同的状态,info warning error 服务端运行其中的任意一个命令执行。 """
topic:更加详细的过滤
#:代表所有 *.:代表.前面匹配任意,且只能代表前面一个.的
发送端
#!/usr/bin/python # -*- coding: UTF-8 -*- import pika,sys credentials = pika.PlainCredentials('root','123456')#连接的用户 parameters = pika.ConnectionParameters(host='192.168.100.129',credentials=credentials) #连接的参数设置 connection = pika.BlockingConnection(parameters)#闭塞连接 channel = connection.channel() #队列连接通道 # type='topic':组播中的更详细的细分过滤 channel.exchange_declare(exchange='topic_log',type='topic') log_level = sys.argv[1] if len(sys.argv) > 1 else 'all.info' #严重程度 message = ''.join(sys.argv[1:]) or "info:Hello World!" #消息持续化 channel.basic_publish( exchange='topic_log', routing_key=log_level, #队列 body=message, ) print(" [x] Sent %r" % message) connection.close()#关闭通信 """ 可以细分的过滤组件。 客户端:通过 *.django.*:任意.django.任意 的字段去匹配 服务端:通过客户端的任意匹配规则去查询。 """
接收端
#!/usr/bin/python # -*- coding: UTF-8 -*- import pika,time,sys #连接 credentials = pika.PlainCredentials('root','123456')#连接的用户 parameters = pika.ConnectionParameters(host='192.168.100.129',credentials=credentials)#连接的配置 connection = pika.BlockingConnection(parameters) channel = connection.channel()#队列连接通道 #不指定queue名字,rabbit会随机分配一个名字,exclusive=True会在使用此queue的消费者断开后,自动将queue删除 queue_obj = channel.queue_declare(exclusive=True) queue_name = queue_obj.method.queue logs_levels = sys.argv[1:]#状态,info warning error if not logs_levels: sys.stderr.write("Usage: %s [info] [warning] [error]\n" % sys.argv[0]) sys.exit(1) for level in logs_levels: channel.queue_bind( exchange='topic_log', queue=queue_name, routing_key=level, #绑定队列到Exchange ) print(' [*] Waiting for logs. To exit press CTRL+C') #ch:就是通道的实例,channel def callback(ch,method,properties,body): print("[X] Received %r"% body) #发送消息 channel.basic_consume( callback,#取到消息后,调用callback函数 queue=queue_name, no_ack= True #默认是false ) channel.start_consuming()#阻塞模式,即数据传输 """ 可以细分的过滤组件。 客户端:通过 *.django.*:任意.django.任意 的字段去匹配 服务端:通过客户端的任意匹配规则去查询。 """
Remote procedure call (RPC)
fibonacci_rpc = FibonacciRpcClient() result = fibonacci_rpc.call(4) print("fib(4) is %r" % result)
ps.使用唯一标识来进行约定。返回的数据
rpc_client
#!/usr/bin/python # -*- coding: UTF-8 -*- """ 1.声明一队列,作为reply_to返回纤细结构的队列 2.发消息到队列,消息里带一个唯一标识符uid,reply_to 3.监听reply_to 的队列,直到有结果 """ import queue import pika import uuid class CMDRpcClient(object): def __init__(self): credentials = pika.PlainCredentials('root', '123456') parameters = pika.ConnectionParameters(host='192.168.100.129',credentials=credentials) self.connection = pika.BlockingConnection(parameters) self.channel = self.connection.channel() result = self.channel.queue_declare(exclusive=True) self.callback_queue = result.method.queue #命令的执行结果的queue #声明要监听callback_queue self.channel.basic_consume(self.on_response, no_ack=True, queue=self.callback_queue) def on_response(self, ch, method, props, body): """ 收到服务器端命令结果后执行这个函数 :param ch: :param method: :param props: :param body: :return: """ if self.corr_id == props.correlation_id: self.response = body.decode("gbk") #把执行结果赋值给Response def call(self, n): self.response = None self.corr_id = str(uuid.uuid4()) #唯一标识符号 self.channel.basic_publish(exchange='', routing_key='rpc_queue2', 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() #检测监听的队列里有没有新消息,如果有,收,如果没有,返回None #检测有没有要发送的新指令 return self.response cmd_rpc = CMDRpcClient() print(" [x] Requesting fib(30)") response = cmd_rpc.call('ipconfig') print(response)
rpc_server
#!/usr/bin/python # -*- coding: UTF-8 -*- """ 1.定义fib函数 2.声明接收指令的对列名rpc_queue 3.开始监听队列,收到消息后,调用fib函数 4.把fib执行结果,发送回客户端指定的reply_to队列 """ import subprocess import pika import time credentials = pika.PlainCredentials('root', '123456') parameters = pika.ConnectionParameters(host='192.168.100.129',credentials=credentials) connection = pika.BlockingConnection(parameters) channel = connection.channel() #队列连接通道 channel.queue_declare(queue='rpc_queue2') #执行客户端发送的值 def fib(n): if n == 0: return 0 elif n == 1: return 1 else: return fib(n-1)+fib(n-2) def run_cmd(cmd): cmd_obj = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE) result = cmd_obj.stdout.read() + cmd_obj.stderr.read() return result def on_request(ch,method,props,body): cmd = body.decode("utf-8") print(" [.] run (%s)" % cmd) response = run_cmd(cmd) ch.basic_publish(exchange='', routing_key=props.reply_to, #队列 properties=pika.BasicProperties(correlation_id = \ props.correlation_id), body=response) ch.basic_ack(delivery_tag = method.delivery_tag) channel.basic_consume(on_request, queue='rpc_queue2') print(" [x] Awaiting RPC requests") channel.start_consuming()