消息队列--Rabbitmq、RPC远程过程调用
1 消息队列(MQ)
https://www.liuqingzheng.top/python/其他/08-Rabbitmq从入门到精通/
https://www.yuque.com/nidhogg14/wssb4n/ig1kdy
# 消息队列介绍 Message Queue
消息队列(MQ)是一种应用程序对应用程序的通信方法 # 通常是不同主机之间
MQ是一直存在,不过随着微服务架构的流行,成了解决微服务之间问题的常用工具。
消息队列中间件是分布式系统中重要的组件,主要解决应用解耦,异步消息,流量削锋等问题
实现高性能,高可用,可伸缩和最终一致性架构。
MQ是消费-生产者模型的一个典型的代表,一端往消息队列中不断写入消息,而另一端则可以读取队列中的消息
这样发布者和使用者都不用知道对方的存在
# 简单理解为:把要传输的数据放在队列中
# 消息队列 解决的问题或好处
# 本质就是把传输的数据放进消息队列中,再服务主动去消息队列获取 或者 消息队列主动给服务发送
-应用解耦
eg: 订单系统=支付系统+库存系统+物流系统 任何一个子系统出了故障,都会造成下单操作异常
使用MQ解耦后,各系统的数据都放进MQ中,此时单一系统出现故障,但导致下单失败
-流量削峰
eg: 临时的峰值处理,加入到MQ做缓存后,将暂时不能同时的数据放进MQ队列里,等待排队执行。
-消息分发 发布订阅:观察者模式
eg: 原本b,c 都需要a产生的数据, a都得给b,c发一份。
引入MQ转发后,a只需要发给MQ,其他需要数据的 都去MQ获取。
-异步消息 celery就是对消息队列的封装
eg: A异步调用B,但B执行时间很长。
# 获取结果
以往两种方式:
A不断去B询问 # 轮询
A设置一个回调接口,B执行完就调用A的回调接口,告诉结果 # 异步回调
MQ后:
A调用B后,只需要监听B处理完成的消息
当B处理完成后,会发送一条消息给MQ,MQ会将此消息转发给A
# 常见的消息队列
目前使用较多的消息队列有RabbitMQ,Kafka,MetaMQ,RocketMQ、ActiveMQ
-rabbitmq: 吞吐量略小 可靠性高 # 有消息确认机制 可靠性要求高 用它
# eg: 生产订单 电商、金融等对事务性要求很高的 ---> RabbitMQ
-kafka : 吞吐量高 可靠性略差 # 注重高吞吐量 数据量特别大 且可靠性要求不高 用它
# eg: 日志 ---> Kafka
2 Rabbitmq
2.1 安装与基础
# 0 基本介绍
RabbitMQ是一个由Erlang语言开发的,基于AMQP协议的开源实现。
# 1 原生安装
# 下载centos源
wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.cloud.tencent.com/repo/centos7_base.repo
# 下载epel源
wget -O /etc/yum.repos.d/epel.repo http://mirrors.cloud.tencent.com/repo/epel-7.repo
# 清空yum缓存并且生成新的yum缓存
yum clean all
yum makecache
# 安装erlang
yum -y install erlang
# 安装RabbitMQ
yum -y install rabbitmq-server
# 2 docker拉取
docker pull rabbitmq:management # 自动开启了web管理界面
docker run -di --name rabbitmq -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=admin -p 15672:15672 -p 5672:5672 rabbitmq:management
# 3 python客户端安装
pip3 install pika
# 4 端口号
5672 : rabbitmq的默认端口
15672 : rabbitmq的web管理界面的端口
# 5 原生命令:
# 创建用户
rabbitmqctl add_user lqz 123
# 设置用户 为administrator角色
rabbitmqctl set_user_tags lqz administrator
# 用户分配权限 允许对所有的队列都有权限对何种资源具有配置、写、读的权限
rabbitmqctl set_permissions -p "/" lqz ".*" ".*" ".*"
# rabbitmq服务操作
systemctl start | stop | restart | status rabbitmq-server
2.2 组件解释
# AMQP协议是一个高级抽象层消息通信协议,RabbitMQ是基于AMQP协议的实现。
##### RabbitMQ的基础组件解释:
# 1.Server / Broker
RabbitMQ的服务器,接受客户端连接,实现AMQP消息队列和路由功能的进程
# 2.Virtual Host 虚拟主机
mini版的RabbitMQ服务器,类似于权限控制组
一个Virtual Host里面拥有自己的"交换机exchange、绑定Binding、队列Queue"
其中每个vhost服务一个应用程序
客户端连接RabbitMQ服务时须指定vHost,如果不指定默认连接的就是"/"
# 3.Exchange 交换机
接受生产者发送的消息,并根据Binding的路由规则将消息路由给服务器中的队列。
eg: 在RabbitMQ中,ExchangeType有direct、Fanout和Topic三种,不同类型的Exchange路由的行为是不一样的
# 4.Connection 连接
生产者和消费者,都需要和Broker 建立连接
其实就是一个位于客户端和Broker之间的TCP连接
# 5.Channel 信道
信道是建立在真实的TCP连接内的虚拟连接(图中白色的channel)
AMQP的命令都是通过信道发送出去的,每条信道都会被指派一个唯一ID
不论是发布消息、订阅队列还是接收消息都是通过信道完成的
一个Connection(TCP连接)下包含多个信道,实现共用TCP、减少TCP创建和销毁的开销
# 6.Routing key
Routing key是消息头的属性,生产者将消息发送到交换机时
会在消息头上携带一个key,这个key就是routing key,来指定这个消息的路由规则
# 7.Binding 绑定
绑定,可以理解成一个动词,它的作用就是把exchange和queue按照路由规则绑定起来。
# 8.Binding key
在绑定Exchange与Queue时,一般会指定一个binding key # 也是叫 routing key
生产者将消息发送给Exchange时,消息头上会携带一个routing key
当binding key与routing key相匹配时,消息将会被路由到对应的Queue中
2.3 简单模式
简单模式,也是默认模式 指的是 exchange= " " ,消息直接转发到 "Queue名字和Routing key相同" 的队列
2.3.1 基本使用
# 基本使用 就是生产者消费者模型
# 针对生产消费者模型
python的Queue也能实现,但存放在单一主机的内存里
对于RabbitMQ来说,生产和消费不再针对内存里的一个Queue对象
而是某台服务器上的RabbitMQ Server实现的消息队列
##### 生产者
import pika
# 拿到连接对象
# 无密码
connection = pika.BlockingConnection(pika.ConnectionParameters('101.133.225.166'))
# 有用户名密码
credentials = pika.PlainCredentials("admin","admin") # 用户凭证
connection = pika.BlockingConnection(pika.ConnectionParameters('101.133.225.166',credentials=credentials))
# 拿到channel信道对象
channel = connection.channel()
# 声明一个队列
channel.queue_declare(queue='hello') # 指定队列名字
# 生产者向队列中放一条消息
channel.basic_publish(exchange='',
routing_key='hello', # 指定哪个消息队列
body='lqz js nb')
print(" Sent 'Hello World!'")
# 关闭连接
connection.close()
##### 消费者
import pika
# connection = pika.BlockingConnection(pika.ConnectionParameters(host='101.133.225.166'))
credentials = pika.PlainCredentials("admin", "admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('101.133.225.166', credentials=credentials))
channel = connection.channel()
# 声明创建队列
channel.queue_declare(queue='hello')
# 确定回调函数
def callback(ch, method, properties, body):
print(" [x] Received %r" % body)
# 确定监听队列参数 auto_ack=True 默认为自动应答 consume v.消费
channel.basic_consume(queue='hello', on_message_callback=callback, auto_ack=True)
# 开始监听
channel.start_consuming()
##### 注:
1.消费者 和生产者 都需要 声明创建同一个队列 哪一端先声明,哪一端就创建
2.3.2 确认机制--应答参数
# 消息确认机制
自动应答:消费者收到消息后 自动应答确认 确认后,会将队列的数据删除 # 默认
手动应答:由消费者自己 手动回复确认应答
# 好处是 有可能消费者 消费/处理数据时 出错,但由于自动应答 收到消息就会自动删除
# 消费者调整参数
# 1.默认自动应答,修改为手动应答
auto_ack=False
# 2.消费者通知生产者,表明该消息已经被消费,否则消息一直存在,不会被删除
ch.basic_ack(delivery_tag=method.delivery_tag)
##### 生产者
import pika
credentials = pika.PlainCredentials("admin","admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('101.133.225.166',credentials=credentials))
channel = connection.channel()
channel.queue_declare(queue='lqz')
channel.basic_publish(exchange='',
routing_key='lqz',
body='lqz jssss nb')
print(" lqz jssss nb'")
connection.close()
##### 消费者
import pika
credentials = pika.PlainCredentials("admin", "admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('101.133.225.166', credentials=credentials))
channel = connection.channel()
channel.queue_declare(queue='lqz')
def callback(ch, method, properties, body):
print(" Received %r" % body)
# 真正的消息处理完了,发手动确认,再删除数据
# 告诉生产者,我已经取走了数据,否则数据一直存在 如果auto_ack=False时,不加下面,消息会一直存在
ch.basic_ack(delivery_tag=method.delivery_tag) # delivery n. 递送
# auto_ack=True 自动回复确认 队列收到确认,就会自动把消费过的消息删除
# auto_ack=False 自动应答改为手动应答 不会自动回复确认消息
channel.basic_consume(queue='lqz', on_message_callback=callback, auto_ack=False)
channel.start_consuming()
2.3.3 持久化--持久化参数
# 1.队列必须持久化
在声明队列时,开启队列持久化 队列必须是新建的 durable adj. 持久的
channel.queue_declare(queue='lqz_new', durable=True)
# 2.消息必须持久化
在发布消息的时候,添加properties 序列化参数 # 让消息也序列化
properties=pika.BasicProperties(delivery_mode=2)
##### 生产者
import pika
credentials = pika.PlainCredentials("admin","admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('101.133.225.166',credentials=credentials))
channel = connection.channel()
channel.queue_declare(queue='lqz_new',durable=True) # 开启队列持久化
channel.basic_publish(exchange='',
routing_key='lqz_new',
body='lqz jssss nb',
properties=pika.BasicProperties(delivery_mode=2) # 开启消息持久
)
print(" lqz jssss nb'")
connection.close()
##### 消费者
import pika
credentials = pika.PlainCredentials("admin", "admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('101.133.225.166', credentials=credentials))
channel = connection.channel()
channel.queue_declare(queue='lqz_new', durable=True) # 开启队列持久化
def callback(ch, method, properties, body):
print(" Received %r" % body)
ch.basic_ack(delivery_tag=method.delivery_tag)
channel.basic_consume(queue='lqz_new', on_message_callback=callback, auto_ack=False)
channel.start_consuming()
2.3.4 闲置消费--分发参数
# 消费者获取时: 谁闲置谁获取,没必要按照顺序一个一个来 prefetch v. 预先载入
channel.basic_qos(prefetch_count=1)
有两个消费者同时监听一个的队列
其中一个线程sleep2秒,另一个消费者线程sleep1秒,但是处理的消息是一样多
# 默认是轮询分发(round-robin) 不管谁忙,都不会多给消息,总是你一个我一个
# 公平分发(fair dispatch) 谁空闲 谁获取消息
1.必须关闭自动应答ack,改成手动应答
2.使用basic_qos(perfetch=1)
限制每次只发送不超过1条消息到同一个消费者,消费者必须手动反馈告知队列,才会发送下一个
##### 生产者
import pika
credentials = pika.PlainCredentials("admin","admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('101.133.225.166',credentials=credentials))
channel = connection.channel()
channel.queue_declare(queue='lqz')
channel.basic_publish(exchange='',
routing_key='lqz',
body='lqz jssss nb')
print(" lqz jssss nb'")
connection.close()
##### 消费者1
import pika
credentials = pika.PlainCredentials("admin", "admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('101.133.225.166', credentials=credentials))
channel = connection.channel()
channel.queue_declare(queue='lqz')
def callback(ch, method, properties, body):
import time
time.sleep(50)
print(" [x] Received %r" % body)
ch.basic_ack(delivery_tag=method.delivery_tag)
# 设置闲置消费
channel.basic_qos(prefetch_count=1)
channel.basic_consume(queue='lqz', on_message_callback=callback, auto_ack=False)
channel.start_consuming()
##### 消费者2
import pika
credentials = pika.PlainCredentials("admin", "admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('101.133.225.166', credentials=credentials))
channel = connection.channel()
channel.queue_declare(queue='lqz')
def callback(ch, method, properties, body):
print(" [x] Received %r" % body)
ch.basic_ack(delivery_tag=method.delivery_tag)
# 设置闲置消费
channel.basic_qos(prefetch_count=1)
channel.basic_consume(queue='lqz', on_message_callback=callback, auto_ack=False)
channel.start_consuming()
2.4 交换机模式之发布订阅
# Exchange交换机的路由类型: exchange_type的参数值
fanout : 广播模式 # 分发给所有与exchange绑定的队列 此模式下忽略routing_key
direct : 精准匹配 # 消息的Routing key与队列的 Routing key 相同
topic : 模糊匹配 # 消息的Routing key与队列的 Routing key 按某个格式相匹配
headers : 根据Headers头部匹配 # 不常用
# 发布订阅 和 简单消息队列 的区别:
简单消息队列中的数据被一个消费者 消费一次便会消失
发布订阅 可将消息发送给所有的订阅者
原理:
实现发布和订阅时,会为每一个订阅者都创建一个队列
而发布者发布消息时,可按照交换机的模式 选择将消息放置在多个不同的队列中
2.4.1 基础发布订阅
##### 发布者
import pika
credentials = pika.PlainCredentials("admin", "admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('101.133.225.166', credentials=credentials))
channel = connection.channel()
# 声明交换机exchange 匹配类型:fanout 发布给所有订阅者
channel.exchange_declare(exchange='logs', exchange_type='fanout')
# 向logs交换机插入数据 消息的routing_key为空
message = "info: Hello World!"
channel.basic_publish(exchange='logs', routing_key='', body=message)
print(" [x] Sent %r" % message)
connection.close()
##### 订阅者(启动多次,就是多个订阅者,会创建出多个队列,都绑定到了同一个exchange上)
import pika
credentials = pika.PlainCredentials("admin", "admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('101.133.225.166', credentials=credentials))
channel = connection.channel()
# 声明交换机
channel.exchange_declare(exchange='logs', exchange_type='fanout')
# 创建队列 (随机生成一个队列) exclusive adj. 独有的
result = channel.queue_declare(queue='', exclusive=True)
queue_name = result.method.queue
print(queue_name)
# 将指定队列绑定到交换机上
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)
channel.basic_consume(queue=queue_name, on_message_callback=callback, auto_ack=True)
channel.start_consuming()
2.4.2 Direct(按关键字精确匹配)
##### 发布者
import pika
credentials = pika.PlainCredentials("admin", "admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('101.133.225.166', credentials=credentials))
channel = connection.channel()
# 声明交换机 匹配类型:direct n.直接
channel.exchange_declare(exchange='lqz123', exchange_type='direct')
message = "info: asdfasdfasdfsadfasdf World!"
# 将消息发给 指定routing_key为'bnb' 的队列
channel.basic_publish(exchange='lqz123', routing_key='bnb', body=message)
print(" [x] Sent %r" % message)
connection.close()
##### 订阅者1
import pika
credentials = pika.PlainCredentials("admin", "admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('101.133.225.166', credentials=credentials))
channel = connection.channel()
# 声明交换机 匹配类型:direct n.直接
channel.exchange_declare(exchange='lqz123', exchange_type='direct')
# 创建队列(随机生成一个队列)
result = channel.queue_declare(queue='', exclusive=True)
queue_name = result.method.queue
print(queue_name)
# 将指定队列绑定到交换机上,并指定 该队列的routing_key 为 "nb"
channel.queue_bind(exchange='lqz123', queue=queue_name, routing_key='nb')
print(' [*] Waiting for logs. To exit press CTRL+C')
def callback(ch, method, properties, body):
print(" [x] %r" % body)
channel.basic_consume(queue=queue_name, on_message_callback=callback, auto_ack=True)
channel.start_consuming()
##### 订阅者2
import pika
credentials = pika.PlainCredentials("admin", "admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('101.133.225.166', credentials=credentials))
channel = connection.channel()
# 声明交换机 匹配类型:direct n.直接
channel.exchange_declare(exchange='lqz123', exchange_type='direct')
# 创建队列(随机生成一个队列)
result = channel.queue_declare(queue='', exclusive=True)
queue_name = result.method.queue
print(queue_name)
# 将指定队列绑定到交换机上,并指定 该队列的routing_key 为 "nb" 和 "bnb"
channel.queue_bind(exchange='lqz123', queue=queue_name, routing_key='nb')
channel.queue_bind(exchange='lqz123', queue=queue_name, routing_key='bnb')
print(' [*] Waiting for logs. To exit press CTRL+C')
def callback(ch, method, properties, body):
print(" [x] %r" % body)
channel.basic_consume(queue=queue_name, on_message_callback=callback, auto_ack=True)
channel.start_consuming()
2.4.3 Topic(按通配符模糊匹配)
# routing_key模糊匹配
routing_key变成了一个有“.”分隔的字符串,“.”将字符串分割成几个单词,每个单词代表一个条件;
"#" 表示后面可以跟任意字符
"*" 表示后面只能跟一个单词
##### 发布者
import pika
credentials = pika.PlainCredentials("admin", "admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('101.133.225.166', credentials=credentials))
channel = connection.channel()
# 声明交换机 匹配类型:topic n.主题
channel.exchange_declare(exchange='m3', exchange_type='topic')
message = "info: asdfasdfasdfsadfasdf World!"
# 将消息发给 指定routing_key为'lqz.dd' 的队列
channel.basic_publish(exchange='m3', routing_key='lqz.dd', body=message)
print(" [x] Sent %r" % message)
connection.close()
##### 订阅者1
import pika
credentials = pika.PlainCredentials("admin", "admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('101.133.225.166', credentials=credentials))
channel = connection.channel()
# 声明交换机 匹配类型:topic n.主题
channel.exchange_declare(exchange='m3', exchange_type='topic')
# 创建队列(随机生成一个队列)
result = channel.queue_declare(queue='', exclusive=True)
queue_name = result.method.queue
print(queue_name)
# 将指定队列绑定到交换机上,并指定 该队列的routing_key 为 "lqz.*"
channel.queue_bind(exchange='m3',queue=queue_name,routing_key='lqz.*')
print(' [*] Waiting for logs. To exit press CTRL+C')
def callback(ch, method, properties, body):
print(" [x] %r" % body)
channel.basic_consume(queue=queue_name, on_message_callback=callback, auto_ack=True)
channel.start_consuming()
##### 订阅者2
import pika
credentials = pika.PlainCredentials("admin", "admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('101.133.225.166', credentials=credentials))
channel = connection.channel()
# 声明交换机 匹配类型:topic n.主题
channel.exchange_declare(exchange='m3', exchange_type='topic')
# 创建队列(随机生成一个队列)
result = channel.queue_declare(queue='', exclusive=True)
queue_name = result.method.queue
print(queue_name)
# 将指定队列绑定到交换机上,并指定 该队列的routing_key 为 "lqz.#"
channel.queue_bind(exchange='m3', queue=queue_name,routing_key='lqz.#')
print(' [*] Waiting for logs. To exit press CTRL+C')
def callback(ch, method, properties, body):
print(" [x] %r" % body)
channel.basic_consume(queue=queue_name, on_message_callback=callback, auto_ack=True)
channel.start_consuming()
3 RPC
https://www.liuqingzheng.top/python/分布式与微服务/8-什么是RPC/
3.0 RPC介绍
# 微服务间通信
python 很少用来写微服务
微服务通常是 go 来编写,那微服务间的调用 基本都是 gRPC框架
# 两个服务之间的调用方式:
restful(http协议),rpc(远程过程调用)
# 不管用rpc或者restful来通信调用,涉及到同步,异步
# RPC介绍
RPC 远程过程调用 Remote Procedure Call
eg: 两台服务器A和B
一个应用部署在A服务器上,想要调用B服务器上应用提供的函数或方法
由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据
# 简述:像调用本地方法一样调用远程的程序
# RPC框架
gRPC:
谷歌出的 可以跨语言 # 主要是go来编写
传输数据格式只支持protobuf # 比json还节约空间
SimpleXMLRPCServer:
python 官方自带的
数据包大 速度慢 # 采用的http协议通信 且传输数据格式为XML
ZeroRPC:
第三方 底层使用ZeroMQ和MessagePack # pip install zerorpc
速度快 响应时间短 并发高 # 采用tcp协议 且传输数据格式为json 推荐
3.1 通过Rabbitmq实现rpc
# Rabbitmq实现rpc的流程:
1.客户端 发送 服务端的调用请求时,除调用参数外,
额外指定两个参数:回调的队列(reply_to) 和 关联id(correlation_id)
# 简单生产消费者模式 客户端生产数据(调用参数) ---> rpc_queue 队列---> 服务端消费数据
2.服务端 从 rpc_queue队列 获取调用参数后,进行函数调用
并将 调用结果 + 关联id 通过 回调队列 返回给客户端
# 简单生产消费者模式 服务端生产数据(调用结果) ---> 回调队列---> 客户端消费数据
# 具体处理事项:
1.服务端 生成rpc_queue队列 负责帮 服务端 接收客户端发来的 调用参数的数据
2.客户端 生成另外一个回调随机队列 服务端将调用后的结果 放进该队列 并发送给客户端
3.客户端 生成correlation_id (UUID) 发送给服务端 服务端会把这串字符作为验证再发给客户端
4.当服务端处理完,将调用结果发给客户端时会把调用与 关联id 一起通过 回调队列发回给客户端
5.客户端,会使用while循环 不断检测是否有数据,并以这种形式来实现阻塞等待数据,来监听服务端
6.客户端 获取调用结果时,触发回调函数 回调函数判断本机的UUID与服务端发回correlation_id是否匹配
由于服务端,可能有多个,且处理时间不等 所以需要判断,判断成功赋值数据,while循环就会捕获到,完成交互
##### 服务端
import pika
credentials = pika.PlainCredentials("admin", "admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('101.133.225.166', credentials=credentials))
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) # 调用本地的fib函数
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, auto_ack=False)s
print(" [x] Awaiting RPC requests")
channel.start_consuming()
##### 客户端
import pika
import uuid
class FibonacciRpcClient(object):
def __init__(self):
self.credentials = pika.PlainCredentials("admin", "admin")
self.connection = pika.BlockingConnection(pika.ConnectionParameters('101.133.225.166', credentials=self.credentials))
self.channel = self.connection.channel()
# 随机生成queue 回调队列
result = self.channel.queue_declare(queue='', exclusive=True)
self.callback_queue = result.method.queue
# 客户端 消费 服务端返回的函数结果
self.channel.basic_consume(
queue=self.callback_queue,
on_message_callback=self.on_response,
auto_ack=True)
# 对回调队列中的响应进行处理的函数
def on_response(self, ch, method, props, body):
# 根据 调用结果的关联id(correlation_id) 和 自身发起调用的参数 关联id 是否相等,再进行调用结果处理
if self.corr_id == props.correlation_id:
self.response = body
def call(self, n):
# 赋值变量,一个循环值
self.response = None
# 生成correlation_id 关联id,生成全局唯一标识ID,保证时间空间唯一性
self.corr_id = str(uuid.uuid4())
# 客户端 生产 调用客户端函数 需要的一系列参数
self.channel.basic_publish(
exchange='',
routing_key='rpc_queue', # 放到名为'rpc_queue'的队列
properties=pika.BasicProperties(
reply_to=self.callback_queue, # 指定返回结果存放的队列 调用结果 返回给self.callaback_queue这个队列中
correlation_id=self.corr_id, # 指定关联id correlation n. 关联
),
body=str(n) # 发的消息,必须传入字符串,不能传数字
)
# 调用结果没有数据 就循环接受
while self.response is None:
# 非阻塞版的start_consuming()
self.connection.process_data_events()
return int(self.response)
fibonacci_rpc = FibonacciRpcClient()
print(" [x] Requesting fib(30)")
response = fibonacci_rpc.call(10) # 外界看上去,就像调用本地的call()函数一样
print(" [.] Got %r" % response)
3.2 python中的rpc框架
https://www.liuqingzheng.top/python/其他/07-ZeroRPC和SimpleXMLRPCServer/
3.2.1 SimpleXMLRPCServer
##### 服务端
from xmlrpc.server import SimpleXMLRPCServer
class RPCServer(object):
def __init__(self):
super(RPCServer, self).__init__()
print(self)
self.send_data = {'server:'+str(i): i for i in range(100)}
self.recv_data = None
def getObj(self):
print('get data')
return self.send_data
def sendObj(self, data):
print('send data')
self.recv_data = data
print(self.recv_data)
# 服务端监听端口
server = SimpleXMLRPCServer(('localhost',4242), allow_none=True)
server.register_introspection_functions()
# 注册调用方法(函数地址或类对象)
server.register_instance(RPCServer())
# 服务端永久服务
server.serve_forever()
##### 客户端
import time
from xmlrpc.client import ServerProxy
def xmlrpc_client():
print('xmlrpc client')
# 连接服务端 (地址+端口)
c = ServerProxy('http://localhost:4242')
data = {'client:'+str(i): i for i in range(100)}
start = time.clock()
for i in range(50):
a=c.getObj() # 直接在客户端 调用 服务端的方法
print(a)
for i in range(50):
c.sendObj(data) # 直接在客户端 调用 服务端的方法
print('xmlrpc total time %s' % (time.clock() - start))
if __name__ == '__main__':
xmlrpc_client()
3.2.2 ZeroRPC实现rpc
##### 服务端
import zerorpc
class RPCServer(object):
def __init__(self):
super(RPCServer, self).__init__()
print(self)
self.send_data = {'server:'+str(i): i for i in range(100)}
self.recv_data = None
def getObj(self):
print('get data')
return self.send_data
def sendObj(self, data):
print('send data')
self.recv_data = data
print(self.recv_data)
# 注册调用方法(函数地址或类对象)
s = zerorpc.Server(RPCServer())
# 服务端监听端口 tcp协议通信
s.bind('tcp://0.0.0.0:4243')
s.run()
##### 客户端
import zerorpc
import time
def zerorpc_client():
print('zerorpc client')
# 实例客户端对象
c = zerorpc.Client()
# 客户端连接服务端
c.connect('tcp://127.0.0.1:4243')
data = {'client:'+str(i): i for i in range(100)}
start = time.clock()
for i in range(500):
a=c.getObj() # 直接在客户端 调用 服务端的方法
print(a)
for i in range(500):
c.sendObj(data) # 直接在客户端 调用 服务端的方法
print('total time %s' % (time.clock() - start))
if __name__ == '__main__':
zerorpc_client()
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)