简单模式-生产与消费

producer生产者

import pika
import json
 
credentials = pika.PlainCredentials('guest', 'guest')
conn = pika.BlockingConnection(
    pika.ConnectionParameters(host='your robbitmq server', port=5672, virtual_host='/', credentials=credentials))
 
channel = conn.channel()
result = channel.queue_declare(queue='zy_test')
 
for i in range(100):
    message = json.dumps({'OrderId': f'1000{i}'})
    channel.basic_publish(exchange='', routing_key='zy_test', body=message)
    print(message)
 
conn.close()

consumer消费者

import pika
import json
 
credentials = pika.PlainCredentials('guest', 'guest')
conn = pika.BlockingConnection(
    pika.ConnectionParameters(host='your robbitmq server', port=5672, virtual_host='/', credentials=credentials))
 
channel = conn.channel()
# durable 持久化 消息不丢失
channel.queue_declare(queue='zy_test', durable=True)
# 负载均衡:使用basicQos( prefetchCount = 1)方法,来限制RabbitMQ只发不超过1条的消息给同一个消费者。当消息处理完毕后,有了反馈,才会进行第二次发送。
channel.basic_qos(prefetch_count=1)
 
def callback(ch, method, properties, body):
    ch.basic_ack(delivery_tag=method.delivery_tag)
    print(body.decode())
 
channel.basic_consume(queue='zy_test', on_message_callback=callback)
channel.start_consuming()

发布与订阅

发布与订阅要借助交换机(EXchange)的原理来实现:
image.png

EXchange一共有四种工作模式:fanout,direct,topic,headers(不常用)

1、fanout模式

fanout模式下,传递到exchange的消息将会转发到所有与其绑定的queue上。通俗理解为:发送给与exchange绑定的所有queue

  • 不需要指定routing_key,即使指定了也是无效的
  • 需要提前将exchange和queue绑定,多对多关系:一个exchange可以绑定多个queue,一个queue可以绑定多个exchange
  • 需要先启动订阅者,此模式下的队列是consumer随机生成的,发布者仅仅发消息到exchange,由exchange转发消息至queue。

发布者:

import json
from time import sleep
 
import pika
 
credentials = pika.PlainCredentials('guest', 'guest')
conn = pika.BlockingConnection(
    pika.ConnectionParameters(host='your robbitmq server', port=5672, virtual_host='/', credentials=credentials))
 
channel = conn.channel()
# 声明exchange,由exchange指定消息在哪个队列传递,如不存在,则创建。durable = True 代表exchange持久化存储,False 非持久化存储
channel.exchange_declare(exchange='zy_test', exchange_type='fanout', durable=True)
 
for i in range(100):
    message = json.dumps({'OrderId': f'20000{i}'})
    # 向队列插入数值 routing_key是队列名。delivery_mode = 2 声明消息在队列中持久化,delivery_mod = 1 消息非持久化。routing_key 不需要配置
    channel.basic_publish(
        exchange='zy_test',
        routing_key='',
        body=message,
        properties=pika.BasicProperties(delivery_mode=2)
    )
    print(f'Sent: {message}')
    sleep(1)
conn.close()

订阅者:

import pika
 
credentials = pika.PlainCredentials('guest', 'guest')
conn = pika.BlockingConnection(
    pika.ConnectionParameters(host='your robbitmq server', port=5672, virtual_host='/', credentials=credentials))
 
channel = conn.channel()
channel.exchange_declare(exchange='logs', exchange_type='fanout')
# 创建临时队列,队列名传空字符,consumer关闭后,队列自动删除
result = channel.queue_declare(queue='', exclusive=True)
# 绑定exchange和队列queue,exchange 使我们能够确切地指定消息应该到哪个队列去
channel.queue_bind(exchange='zy_test', queue=result.method.queue)
 
 
# 回调函数,处理消息内容
def callback(ch, method, properties, body):
    ch.basic_ack(delivery_tag=method.delivery_tag)
    print(body.decode())
 
 
# auto_ack设置成 False,在调用callback函数时,未收到确认标识,消息会重回队列。True,无论调用callback成功与否,消息都被消费掉
channel.basic_consume(result.method.queue, callback, auto_ack=False)
channel.start_consuming()

2、direct模式

direct模式原理:消息发送至exchange,exchange根据路由键(routing_key)转达到对应的queue上;通俗的理解为:根据routing_key过滤。

  • 可以使用默认exchange='', 也可以自定义exchange
  • direct模式不需要将exchange和queue,routing_key,queue进行绑定,当然可以绑定;
  • 传递或接收消息时 需要指定 routing_key
  • 需要先启动 订阅者,此模式下的consumer是随机生成的,发布者仅仅发布消息到exchange,由exchange转发消息至queue。如果后启动订阅者,则会丢失启动前的消息数据。

发布者:

import json
from time import sleep
 
import pika
 
credentials = pika.PlainCredentials('guest', 'guest')
conn = pika.BlockingConnection(
    pika.ConnectionParameters(host='your robbitmq server', port=5672, virtual_host='/', credentials=credentials))
 
channel = conn.channel()
channel.exchange_declare(exchange='zy_test_d', durable=True, exchange_type='direct')
for i in range(100):
    message = json.dumps({'OrderId': f'30000{i}'})
    # 向队列插入数值 routing_key是队列名。delivery_mode = 2 声明消息在队列中持久化,delivery_mod = 1 消息非持久化。routing_key 不需要配置
    channel.basic_publish(
        exchange='zy_test_d',
        routing_key='OrderId',
        body=message,
        properties=pika.BasicProperties(delivery_mode=2)
    )
    print(f'Sent: {message}')
    sleep(1)
conn.close()

订阅者:

import pika
 
credentials = pika.PlainCredentials('guest', 'guest')
conn = pika.BlockingConnection(
    pika.ConnectionParameters(host='your robbitmq server', port=5672, virtual_host='/', credentials=credentials))
 
channel = conn.channel()
channel.exchange_declare(exchange='zy_test_d', exchange_type='direct', durable=True)
# 创建临时队列,队列名传空字符,consumer关闭后,队列自动删除
result = channel.queue_declare(queue='', exclusive=True)
# 绑定exchange和队列queue,exchange 使我们能够确切地指定消息应该到哪个队列去
channel.queue_bind(exchange='zy_test_d', queue=result.method.queue, routing_key='OrderId')
 
 
# 回调函数,处理消息内容
def callback(ch, method, properties, body):
    ch.basic_ack(delivery_tag=method.delivery_tag)
    print(body.decode())
 
 
# auto_ack设置成 False,在调用callback函数时,未收到确认标识,消息会重回队列。True,无论调用callback成功与否,消息都被消费掉
channel.basic_consume(result.method.queue, callback, auto_ack=False)
channel.start_consuming()

3、topic模式

topic模式和第二种模式差不多,exchange 也是通过 路由键 routing_key 来转发消息到指定的 queue 。 _不同点是 routing_key 使用正则表达式支持模糊匹配_,但匹配规则又与常规的正则表达式不同,比如“#”是匹配全部,“*”是匹配一个词。
举例:routing_key =“#orderid#”,意思是将消息转发至所有 routing_key 包含 “orderid” 字符的队列中。代码和模式二 类似,就不贴出来了。

基于rabbitMQ的RPC

Callback queue 回调队列

一个客户端向服务器发送请求,服务器端处理请求后,将其处理结果保存在一个存储体中。而客户端为了获得处理结果,那么客户在向服务器发送请求时,同时发送一个回调队列地址 reply_to

Correlation id 关联标识

一个客户端可能会发送多个请求给服务器,当服务器处理完后,客户端无法辨别在回调队列中的响应具体和那个请求时对应的。为了处理这种情况,客户端在发送每个请求时,同时会附带一个独有correlation_id属性,这样客户端在回调队列中根据correlation_id字段的值就可以分辨此响应属于哪个请求。

客户端发送请求

客户端在发送RPC请求到RPC请求队列时,客户端至少发送带有reply_to以及correlation_id两个属性的信息

服务端工作流

等待接受客户端发来RPC请求,当请求出现的时候,服务器从RPC请求队列中取出请求,进行处理后,将响应发送到reply_to指定的回调队列中

客户端接受处理结果

客户端等待回调队列中出现响应,当响应出现时,它会根据响应中correlation_id字段的值,将其返回给对应的应用

server端

import pika
 
 
def fib(n):
    if n == 0:
        return 0
    elif n == 1:
        return 1
    else:
        return fib(n - 1) + fib(n - 2)
 
 
# 处理RPC请求队列中数据,并回调
def on_request(ch, method, properties, body):
    n = int(body)
    print(f'[recv] n={n}')
    response = fib(n)
 
    ch.basic_publish(exchange='',
                     routing_key=properties.reply_to,
                     properties=pika.BasicProperties(correlation_id=properties.correlation_id),
                     body=str(response)
                     )
    ch.basic_ack(delivery_tag=method.delivery_tag)
 
# 鉴权
credentials = pika.PlainCredentials('guest', 'guest')
# 建立链接
conn = pika.BlockingConnection(
    pika.ConnectionParameters(host='your rabbitmq host', port=5672, virtual_host='/', credentials=credentials))
# 建立会话
channel = conn.channel()
# 声明RPC请求队列
channel.queue_declare(queue='rpc_queue')
# 负载均衡
channel.basic_qos(prefetch_count=1)
# 消费客户端的消息,并请求回调方法on_request
channel.basic_consume(queue='rpc_queue', on_message_callback=on_request)
channel.start_consuming()

client端

import uuid
import pika
 
 
class fibRpcClient:
    def __init__(self):
        self.__credentials = pika.PlainCredentials('guest', 'guest')
        self.conn = pika.BlockingConnection(pika.ConnectionParameters(
            host='your rabbitmq server', port=5672, virtual_host='/', credentials=self.__credentials
        ))
        # 定义回调消息会话
        self.channel = self.conn.channel()
        result = self.channel.queue_declare(queue='rpc_back', exclusive=True)
        self.callback_queue = result.method.queue
        self.correlation_id = str(uuid.uuid4())
        self.response = None
        # 收到回调消息,开始消费
        self.channel.basic_consume(
            on_message_callback=self.on_response,
            queue=self.callback_queue,
            auto_ack=False
        )
 
    # 收到消息后 数据处理方法
    def on_response(self, ch, method, properties, body):
        if self.correlation_id == properties.correlation_id:
            self.response = body
 
    def call(self, n):
        # 发送消息
        self.channel.basic_publish(
            exchange='',
            routing_key='rpc_queue',
            body=str(n),
            properties=pika.BasicProperties(
                reply_to=self.callback_queue,
                correlation_id=self.correlation_id
            )
        )
        print(f'[send] n={n}')
        # 没有收到相应,就一直非阻塞式的start_consumer
        while self.response is None:
            self.conn.process_data_events()
        print(f'[recv] result is {int(self.response)}')
 
if __name__ == '__main__':
    fib = fibRpcClient()
    fib.call(17)

持久化

MQ默认建立的是临时 queue 和 exchange,如果不声明持久化,一旦 rabbitmq 挂掉,queue、exchange 将会全部丢失。所以我们一般在创建 queue 或者 exchange 的时候会声明 持久化。

1、queue 声明持久化

声明消息队列,消息将在这个队列传递,如不存在,则创建。
durable = True 代表消息队列持久化存储,False 非持久化存储

result = channel.queue_declare(queue = 'python-test',durable = True)

2、exchange 声明持久化

声明exchange,由exchange指定消息在哪个队列传递,如不存在,则创建。
durable = True 代表exchange持久化存储,False 非持久化存储

channel.exchange_declare(exchange = 'python-test', durable = True)

3、消息持久化

虽然exchange 和 queue 都申明了持久化,但如果消息只存在内存里,rabbitmq 重启后,内存里的东西还是会丢失。所以必须声明消息也是持久化,从内存转存到硬盘。

向队列插入数值 routing_key是队列名。
delivery_mode = 2 声明消息在队列中持久化,delivery_mod = 1 消息非持久化 

channel.basic_publish(exchange = '',routing_key = 'python-test',body = message, properties=pika.BasicProperties(delivery_mode = 2))

4、acknowledgement消息不丢失

消费者(consumer)调用callback函数时,会存在处理消息失败的风险,如果处理失败,则消息丢失。但是也可以选择消费者处理失败时,将消息回退给 rabbitmq ,重新再被消费者消费,这个时候需要设置确认标识。

auto_ack设置成 False,在调用callback函数时,未收到确认标识,消息会重回队列。
True,无论调用callback成功与否,消息都被消费掉 

channel.basic_consume(callback,queue = 'python-test', auto_ack= False)

Sunflower
171 声望8 粉丝

Keep Hungery!