消息队列--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()
posted @   Edmond辉仔  阅读(392)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示