rabbitmq
线程Q:实现一个进程下不同线程间的数据交互
进程Q:不同进程的数据交互
rabbitmq:消息中间件 -消息队列,用于生产消费模型不同语言多个程序的交互
场景:异步
开发语言:erlang 爱立信公司
特点:队列可独立,EX交换机放到队列
一、常用命令
添加用户:rabbitmqctl add_user admin admin
添加权限:rabbitmqctl set_permissions -p "/" admin ".*" ".*" ".*"
修改用户角色rabbitmqctl set_user_tags admin administrator
启用插件:配置linux 端口 15672 网页管理 5672 AMQP端口
rabbitmq-plugins enable rabbitmq_management
重启
rabbitmq-server restart
开机
service rabbitmq-server start /etc/init.d/rabbitmq-server start|stop|restart|reload
关机
rabbitmqctl stop
安装:
https://blog.csdn.net/huxu981598436/article/details/55050528 下载rpm点击安装,额外的依赖环境
web管理
http://ip:15672
查看队列数
rabbitmqctl list_queues
客户端连接的时候需要配置认证参数
credentials = pika.PlainCredentials('alex', 'alex3714')
connection = pika.BlockingConnection(pika.ConnectionParameters(
'10.211.55.5',5672,'/',credentials))
channel = connection.channel()
# 超时时间
conn.add_timeout(5, lambda: channel.stop_consuming())
二、不同模式
a.一个生产者,多个消费者
在这种模式下,RabbitMQ会默认把p发的消息依次分发给各个消费者(c),跟负载均衡差不多
安全性:消费者拿消息时队列还没移除消息,如果该消费者没完成,队列会重新分配这个消费给下一个消费者
# 注:这时查队列数没有
消费者完成给队列答复
channel.basic_consume(callback,
queue='task_queue', # 消费者用queue,生产者用routing_key
no_ack=False # 得确认,True时不需确认队列就移除
)
def callback(ch, method, properties, body): # ch:通道实例,method:发布的参数,props:发布的自定义属性
ch.basic_ack(delivery_tag=method.delivery_tag) # 答复服务器确认
持久化:
不设置持久的情况下,重启服务器队列不存在
队列持久化:
channel.queue_declare(queue='hello', durable=True) # 队列名已存在时需重命名
生产消息持久化:
channel.basic_publish(exchange='',
routing_key='task_queue', #发到哪个queue上
body=message,
properties=pika.BasicProperties(
delivery_mode=2, # make message persistent
)
)
消息公平分发:
如果Rabbit只管按顺序把消息发到各个消费者身上,不考虑消费者负载的话,很可能出现,
一个机器配置不高的消费者那里堆积了很多消息处理不完,同时配置高的消费者却一直很轻松。
为解决此问题,可以在各个消费者端,配置perfetch=1,即消费者当前消息还没处理完的时候就不接收新消息。
channel.basic_qos(prefetch_count=1)
channel.basic_consume(callback,
queue='task_queue')
三、消息发布\订阅
前面都基本都是1对1的消息发送和接收,即消息只能发送到指定的queue里,
但如果想让消息被所有的Queue收到,类似广播的效果,这时候就要用到exchange了
Exchange在定义的时候是有类型的,以决定到底是哪些Queue符合条件,可以接收消息
- fanout: 所有bind到此exchange的queue都可以接收消息 # 广播
- direct: 通过routingKey和exchange决定的那个queue可以接收消息 # 组播
- topic:所有符合routingKey(此时可以是一个表达式)的routingKey所bind的queue可以接收消息
- headers: 通过headers 来决定把消息发给哪些queue
1、广播fanout
生产者声明:
connection = pika.BlockingConnection(pika.ConnectionParameters(
host='localhost'))
channel = connection.channel()
channel.exchange_declare(exchange='logs', #定义Exchange类型
exchange_type='fanout')
message = ' '.join(sys.argv[1:]) or "info: Hello World!" #sys.argv[]是一个列表,0表示py自身
channel.basic_publish(exchange='logs', #发布广播了
routing_key='', # 此时不用声明队列名
body=message)
connection.close()
消费者:
# 声明类型
channel.exchange_declare(exchange='logs', # 防备启动了没有声明,和声明queue一样
exchange_type='fanout')
#不指定queue名字,rabbit会分配一个新随机名字,exclusive=True排他,生成的queue重启会删除
result = channel.queue_declare(exclusive=True)
queue_name = result.method.queue
# 绑定队列
channel.queue_bind(exchange='logs',
queue=queue_name)
# 监听消息
channel.basic_consume(callback,
queue=queue_name,
no_ack=True)
场景:实时直播,每几秒切片,把内容分发到子服务器
2、组播
RabbitMQ还支持根据关键字发送,即:队列绑定关键字,发送者将数据根据关键字发送到消息exchange,
exchange根据 关键字 判定应该将数据发送至指定队列。
生产者:
severity = sys.argv[1] if len(sys.argv) > 1 else 'info' # 严重程度,级别 xxx.py info发布
message = ' '.join(sys.argv[2:]) or 'Hello World!'
channel.basic_publish(exchange='direct_logs',
routing_key=severity, # 这里
body=message)
消费者:
severities = sys.argv[1:] # python xxx.py info warning error这样传参
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) # 绑定该exchange下所有的routing_key
3、更细致的消息过滤
生产者和消费者的代码和组播一样,变了exchange类型。在于运行文件时输入的参数不同
表达式符号说明:#表示可以匹配0个或多个单词,*表示只能匹配一个单词
#注:使用RoutingKey为#,Exchange Type为topic的时候相当于使用fanout
# # 监听所有:
kern.* # 接收kern.xxxx,但不接收kern.xxxx.xxxx
*表示一个单词
四、RPC(Remote procedure call)
双向队列,远程过程调用,短连接
发布消息的时候加上返回队列、以及消息的唯一标识符(用于异步并发发布)
# 下面是等待消息返回,可以写成并发多个,不用等待接收而能干其他事情,这样类似异步,io多路复用
client端:
import pika
import uuid
class FibonacciRpcClient(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):
if self.corr_id == props.correlation_id:
self.response = body
def call(self, n):
self.response = None # 返回的结果
self.corr_id = str(uuid.uuid4()) # 唯一标识符
self.channel.basic_publish(exchange='',
routing_key='rpc_queue', # 得事前有这个队列
properties=pika.BasicProperties(
reply_to=self.callback_queue, # 要回复的已创建好的随机队列
correlation_id=self.corr_id, # 唯一标识符
),
body=str(n))
count = 0
while self.response is None: #这里能改为死循环,不断监听
self.connection.process_data_events() # 循环,如果没值,不断执行这个方法,直到有值后执行self.on_response
count +=1
print("check...",count)
return int(self.response)
fibonacci_rpc = FibonacciRpcClient()
print(" [x] Requesting fib(30)")
response = fibonacci_rpc.call(30) # call可以异步并发
print(" [.] Got %r" % response)
server端:
# _*_coding:utf-8_*_
__author__ = 'Alex Li'
import pika
import time
connection = pika.BlockingConnection(pika.ConnectionParameters(
host='localhost'))
channel = connection.channel()
channel.queue_declare(queue='rpc_queue')
def fib(n):
if n == 0:
return 0
elif n == 1:
return 1
else:
return fib(n - 1) + fib(n - 2)
def on_request(ch, method, props, body):
n = int(body)
print(" [.] fib(%s)" % n)
response = fib(n)
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)
channel.basic_qos(prefetch_count=1)
channel.basic_consume(on_request, queue='rpc_queue')
print(" [x] Awaiting RPC requests")
channel.start_consuming()