rabbitmq实现原理
P发出消息到C,通过exchanges(相应模式)计算发送到哪些queue,会确定生产者发送消息给哪个消费者,消息从exchanges出来排好队列发送给相应的消费者。
work queue模式
在这种模式下,RabbitMQ会默认把p发的消息依次分发给各个消费者c,跟负载均衡差不多
代码展示:
生产者:
import pika
import time
import sys
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel() #声明一个管道
channel.queue_declare(queue='wt_queue') #声明一个queue
message = ''.join(sys.argv[1:]) or "Hello World! %s" % time.time() #输入一个值,不输入默认hello
channel.basic_publish(exchange='',
routing_key='wt_queue',
body=message,
properties=pika.BasicProperties(
delivery_mode=2, #使消息持久化
))
print("[x] Sent %r" % message)
connection.close()
消费者:
import pika,time
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
def callback(ch, method, properties, body):
print("[x] Received %r" % body) #body:消息内容
time.sleep(10)
print("[x] Done")
print("method.delivery_tag",method.delivery_tag)
ch.basic_ack(delivery_tag=method.delivery_tag)
channel.basic_consume('wt_queue',
callback,
False #注释掉之后就会达到消息没有被接收,会一直发,直到消费者完全接收了,返回请求给生产者。
)
print('[*] 等待接受')
channel.start_consuming()
分析:运行生产者,开启多个消费者后发现,消费者会有一个随机接受消息的效果。(谁开启的最早,谁第一个接收到消息)
消息持久化
解决了消费者关闭后,消息不会丢失的问题,但那是在rabbitmq服务保持开启时,若服务突然断开,里面的队列未做持久化将会丢失。
channel.queue_declare(queue='wangteng',durable=True) #持久化
生产者,消费者都加入该队列持久化,遇到故障后,就不会丢失队列了
RabbitMQ不允许使用不同的参数重新定义现有队列,并且将向任何尝试这样做的程序返回错误。但是有一个快速的解决方法-让我们用不同的名称声明一个队列。
消息公平分发
如果Rabbit只管按顺序把消息发到各个消费者身上,不考虑消费者负载的话,很可能出现,一个机器配置不高的消费者那里堆积了很多消息处理不完,同时配置高的消费者却一直很轻松。为解决此问题,可以在各个消费者端,配置perfetch=1,意思就是告诉RabbitMQ在我这个消费者当前消息还没处理完的时候就不要再给我发新消息了。
在消费者端加
channel.basic_qos(prefetch_count=1) #公平分发机制
Publish\Subscribe(消息发布\订阅)
Exchange在定义的时候是有类型的,以决定到底是哪些Queue符合条件,可以接收消息
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
生产者:
import pika
import sys
import time
connection = pika.BlockingConnection(pika.ConnectionParameters(
host='localhost'))
channel = connection.channel()
channel.exchange_declare(exchange='logs',
exchange_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()
消费者:
import pika
connection = pika.BlockingConnection(pika.ConnectionParameters(
host='localhost'))
channel = connection.channel()
channel.exchange_declare(exchange='logs',
exchange_type='fanout')
result = channel.queue_declare('',exclusive=True) # 不指定queue名字,rabbit会随机分配一个名字,exclusive=True会在使用此queue的消费者断开后,自动将queue删除
queue_name = result.method.queue
channel.queue_bind(exchange='logs',
queue=queue_name)
print(' [*] 等待接受. To exit press CTRL+C')
def callback(ch, method, properties, body):
print(" [x] %r" % body)
channel.basic_consume(queue_name,
callback,
False)
channel.start_consuming()
分析:相当于广播,错过了就没有了(类似FM频道)
有选择的接收消息(exchange type=direct)
RabbitMQ还支持根据关键字发送,即:队列绑定关键字,发送者将数据根据关键字发送到消息exchange,exchange根据 关键字 判定应该将数据发送至指定队列。
publisher:
import pika
import sys
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
channel.exchange_declare(exchange='direct_logs',
exchange_type='direct') #有选择的接受消息
severity = sys.argv[1] if len(sys.argv) > 1 else 'info' #默认取脚本传入参数的值,如果长度大于1取前面的,否则取info
message = ''.join(sys.argv[2:]) or 'hello world!' #消息内容可以写好多
channel.basic_publish(exchange='direct_logs',
routing_key=severity,
body=message)
print("[x] Send %r:%r" % (severity,message))
connection.close()
subscriber:
import pika
import sys
connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
channel = connection.channel()
channel.exchange_declare(exchange='direct_log',
exchange_type='direct') #关键字
result = channel.queue_declare('',exclusive=True) #随机生成队列
queue_name = result.method.queue
severities = sys.argv[1:] #直接获取运行脚本时的参数
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.')
def callback(ch, method,properties,body):
print("[x] %r:%r" % (method.routing_key,body))
#print("method",method) #consumer_tag delivery_tag exchange routing_key里面存放了这些内容
#print("properties",properties)
#print(ch)
channel.basic_consume(queue_name,
callback,
False)
channel.start_consuming()
分析:生产者后面只能带一个参数,然后是内容,消费者后面可以带多个参数,消费者与生产者匹配后将会发送消息到匹配到的消费者端。