python学习笔记-rabbitMQ

一、安装rabbitMQ

准备

下载erlang
https://www.erlang.org/downloads
下载RabbitMQ
https://github.com/rabbitmq/rabbitmq-server/releases/tag/v3.11.18

1、RabbitMQ 它依赖于Erlang,需要先安装Erlang (不要安装在有中文和空格的目录下)
版本要匹配,本次使用如下:

image

输入rabbitmqctl status检查是否安装成功

2、进入rabbitMQ目录的sbin目录,输入cmd输入命令,(安装网页插件)

rabbitmq-plugins enable rabbitmq_management

3、重启服务,双击rabbitmq-server.bat

打开浏览器,地址栏输入http://localhost:15672,进入管理界面的登录页
输入用户名和密码guest进入主界面

image

4、测试一下

安装python client:使用pip install pika,如遇以下报错,修改lib下sysconfig.py后成功安装

image

 发送方:

import pika

#连接队列服务器
connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
channel = connection.channel()

#创建队列。有就不管,没有就自动创建
channel.queue_declare(queue='hello')

#使用默认的交换机发送消息。exchange为空就使用默认的
channel.basic_publish(exchange='', routing_key='hello', body='Hello my World!')
print(" [x] Sent 'Hello World!'")
connection.close()
View Code

服务器收到消息

image

 接收方:

import pika

#连接队列服务器
connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
channel = connection.channel()

#创建队列。有就不管,没有就自动创建
channel.queue_declare(queue='hello')

#使用默认的交换机发送消息。exchange为空就使用默认的
channel.basic_publish(exchange='', routing_key='hello', body='Hello my World!')
print(" [x] Sent 'Hello World!'")
connection.close()
View Code

收到消息:

image

 看服务器,消息被消费后,对象就相应地移除

image

 二、rabbiitMQ,多个接收端消费消息

循环分发:
启动一个发送端往队列发消息,此时启动多个接收端。发送的消息会对接收端一个一个挨着发送消息。

默认情况下,多个接收端轮流消费消息。消费者收到消息后,正常处理完成,此时才通知队列可以自由删除。那么问题又来了,消费者挂掉了连确认消息都发不出,该怎么办?
还有就是server挂掉了怎么办?

注意: durable=True。这个就是当server挂了队列还存在。delivery_mode=2:server挂了消息还存在。若是保证消息不丢,这两个参数都要设置。

发送端:

import pika
import sys

connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
channel = connection.channel()

# durable:server挂了队列仍然存在
channel.queue_declare(queue='task_queue', durable=True)

# 使用默认的交换机发送消息。exchange为空就使用默认的。delivery_mode=2:使消息持久化。和队列名称绑定routing_key
message = ' '.join(sys.argv[1:]) or "Hello World!"
channel.basic_publish(exchange='',
routing_key='task_queue',
body=message,
properties=pika.BasicProperties(
delivery_mode=2,
))
print(" [x] Sent %r" % message)
connection.close()
View Code

运行并发消息

image

 接收端:

import pika
import time

connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
channel = connection.channel()

channel.queue_declare(queue='task_queue', durable=True)
print(' [*] Waiting for messages. To exit press CTRL+C')


def callback(ch, method, properties, body):
    print(" [x] Received %r" % body)
    time.sleep(body.count(b'.'))
    print(" [x] Done")
    # 手动对消息进行确认
    ch.basic_ack(delivery_tag=method.delivery_tag)


channel.basic_qos(prefetch_count=1)


# basic_consume:这个函数有no_ack参数。该参数默认为false。表示:需要对message进行确认。怎么理解:no设置成false,表示要确认
channel.basic_consume(queue='task_queue',on_message_callback=callback)
channel.start_consuming()
View Code

收到的消息

image

image

image

公平派遣:

我们已经知道如何保证消息不丢,那么问题又来了。有的消费干得快,有的干得慢。这样分发消息,有的累死有的没事干。这个问题如何解决?

在上面的接收端的
channel.basic_consume(callback, queue='task_queue')
代码前加:
channel.basic_qos(prefetch_count=1)
即可

 三、rabbitMQ,发布订阅模式

将同一个消息发给多个客户端。这就是发布订阅模式

 发送端:

import pika
import sys

connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
channel = connection.channel()

'''
原则上,消息,只能有交换机传到队列。就像我们家里面的交换机道理一样。
有多个设备连接到交换机,那么,这个交换机把消息发给那个设备呢,就是根据交换机的类型来定。类型有:direct\topic\headers\fanout
fanout:这个就是,所有的设备都能收到消息,就是广播。
此处定义一个名称为'logs'的'fanout'类型的exchange
'''

channel.exchange_declare(exchange='logs',
exchange_type='fanout')

# 将消息发送到名为log的exchange中
# 因为是fanout类型的exchange,所以无需指定routing_key

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()
View Code

image

 接收端:

import pika

connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
channel = connection.channel()

# 这里需要和发送端保持一致(习惯和要求)
channel.exchange_declare(exchange='logs',exchange_type='fanout')

'''
类似的,比如log,我们其实最想看的,当连接上的时刻到消费者退出,这段时间的日志有些消息,过期了的对我们并没有什么用
并且,一个终端,我们要收到队列的所有消息,比如:这个队列收到两个消息,一个终端收到一个。
我们现在要做的是:两个终端都要收到两个
那么,我们就只需做个临时队列。消费端断开后就自动删除
'''

result = channel.queue_declare('',exclusive=True)
# 取得队列名称
queue_name = result.method.queue

# 将队列和交换机绑定一起
channel.queue_bind(exchange='logs',queue=queue_name)

print(' [*] Waiting for logs. To exit press CTRL+C')

def callback(ch, method, properties, body):
    print(" [x] %r" % body)


# no_ack=True:此刻没必要回应了
channel.basic_consume(queue=queue_name,on_message_callback=callback,auto_ack=True)

channel.start_consuming()
View Code

image

image

image

 四、rabbitmq消息订阅发布

fanout :所有bind到此exchange的queue都可以接收消息
direct:通过routingKey和exchange确定的哪一组可以接收消息
topic:所有符合routingKey(此时可以是一个表达式)的routingKey所bind的queue可以接收消息

表达式符号说明:#代表一个或多个字符,*代表一个任何字符

注:使用RoutingKey为#,Exchange Type为topic的时候相当于使用fanout

headers:通过headers来决定把消息发送给那些queue

 

1、rabbitMQ系列-根据类型订阅

一些消息仍然发送给所有接收端。其中,某个接收端,只对其中某些消息感兴趣,它只想接收这一部分消息。如下图:C1,只对error感兴趣,C2对其他三种甚至对所有都感兴趣,我们该怎么搞呢?

image

 发送端:

import pika
import sys

connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
channel = connection.channel()

# 创建一个交换机:direct_logs 类型是:direct
channel.exchange_declare(exchange='direct_logs', exchange_type='direct')

severity = sys.argv[1] if len(sys.argv) > 1 else 'info'
message = ' '.join(sys.argv[2:]) or 'Hello World!'

# 向exchage按照设置的 routing_key=severity 发送message
channel.basic_publish(exchange='direct_logs',routing_key=severity,body=message)

print(" [x] Sent %r:%r" % (severity, message))
connection.close()
View Code

接收端:

import pika
import sys

connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
channel = connection.channel()

# 跟发送端一致
channel.exchange_declare(exchange='direct_logs',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)

'''
# 使用routing_key绑定交换机和队列。广播类型,无需使用这个
# direct类型:会对消息进行精确匹配
# 对个队列使用相同路由key是可以的
'''

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(" [x] %r:%r" % (method.routing_key, body))

channel.basic_consume(queue=queue_name,on_message_callback=callback,auto_ack=True)

channel.start_consuming()
View Code

如图,接收端订阅指定类型的消息。
这种模式和上一章,在发送端发消息之前,需要先将接收端启动起来。前面说了,过期消息不感兴趣,是不会接收的。

image

 2、rabbitMQ系列-根据主题分配消息

使用exchange_type='direct'进行消息传递。这样消息会完全匹配后发送到对应的接收端。现在我们想干这样一件事:
C1获取消息中包含:orange内容的消息,并且消息是由3个单词组成的。
C2获取消息中包含:rabbit内容的消息,并且也是3个单词组成,
C3同时,包含haha开头的消息,这个消息长度>2

发送端:

import pika
import sys

connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
channel = connection.channel()

#创建交换机和上一章一样。只是类型变为:exchange_type='topic'
channel.exchange_declare(exchange='topic_logs',exchange_type='topic')

routing_key = sys.argv[1] if len(sys.argv) > 2 else 'anonymous.info'
message = ' '.join(sys.argv[2:]) or 'Hello World!'
channel.basic_publish(exchange='topic_logs',routing_key=routing_key,body=message)
print(" [x] Sent %r:%r" % (routing_key, message))
connection.close()
View Code

接收端:

import pika
import sys

connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
channel = connection.channel()

#和发送端保持一致 注意:exchange_type='topic'
channel.exchange_declare(exchange='topic_logs',exchange_type='topic')

result = channel.queue_declare('',exclusive=True)
queue_name = result.method.queue

binding_keys = sys.argv[1:]
if not binding_keys:
    sys.stderr.write("Usage: %s [binding_key]...\n" % sys.argv[0])
    sys.exit(1)

for binding_key in binding_keys:
    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(queue=queue_name,on_message_callback=callback,auto_ack=True)

channel.start_consuming()
View Code

image

 

3、rabbitMQ系列-进行RPC调用

RPC:是远程过程调用。百度写了一大堆。此刻,我们简单点说:比如,我们在本地的代码中调用一个函数,那么这个函数不一定有返回值,但一定有返回。若是在分布式环境中,香我们前几章的例子,发送消息出去后,发送端是不清楚客户端处理完后的结果的。由于rabbitmq的响应机制,顶多能获取到客户端的处理状态,但并不能获取处理结果。那么,我们想像本地调用那样,需要客户端处理后返回结果该怎么办呢。就是如下图:

image

 client发送请求,同时告诉server处理完后要发送消息给:回调队列的ID:correlation_id=abc,并调用replay_to回调队列对应的回调函数。

客户端:

发消息也收消息

import pika

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(queue='rpc_queue',on_message_callback=on_request,)

print(" [x] Awaiting RPC requests")
channel.start_consuming()
View Code

服务端:

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

        # 这里:这个是消息发送方,当要执行回调的时候,它又是接收方
        # 使用callback_queue 实现消息接收。即是回调。注意:这里的回调
        # 不需要对消息进行确认。反复确认,没玩没了就成了死循环

        #这里设置回调
        self.channel.basic_consume(queue=self.callback_queue,on_message_callback=self.on_response, auto_ack=True)

    # 定义回调的响应函数。
    # 判断:若是当前的回调ID和响应的回调ID相同,即表示,是本次请求的回调
    # 原因:若是发起上百个请求,发送端总得知道回来的对应的是哪一个发送的
    def on_response(self, ch, method, props, body):
        if self.corr_id == props.correlation_id:
            self.response = body

    def call(self, n):
        # 设置响应和回调通道的ID
        self.response = None
        self.corr_id = str(uuid.uuid4())
        # properties中指定replay_to:表示回调要调用那个函数
        # 指定correlation_id:表示回调返回的请求ID是那个
        # body:是要交给接收端的参数
        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))

        # 监听回调
        while self.response is None:
            self.connection.process_data_events()   #检查队列里有没有新消息,但不会阻塞

        # 返回的结果是整数,这里进行强制转换
        return int(self.response)


fibonacci_rpc = FibonacciRpcClient()

print(" [x] Requesting fib(10)")
response = fibonacci_rpc.call(10)
print(" [.] Got %r" % response)
View Code

image

posted @ 2026-01-28 16:21  子不语332  阅读(2)  评论(0)    收藏  举报