RabbitMQ和RPC
消息队列
消息队列中间件 (Message Queue Middleware,简称 MQ) 是指利用高效可靠的消息传递机制进行与平台无关的数据交流,它可以在分布式环境下扩展进程间的数据通信,并基于数据通信来进行分布式系统的集成。
消息队列使用场景
- 项目解耦:不同的项目或模块可以使用消息中间件进行数据的传递,从而可以保证模块的相对独立性,实现解耦。
- 流量削峰:可以将突发的流量 (如秒杀数据) 写入消息中间件,然后由多个消费者进行异步处理。
- 弹性伸缩:可以通过对消息中间件进行横向扩展来提高系统的处理能力和吞吐量。
- 发布订阅:可以用于任意的发布订阅模式中。
- 异步处理:当我们不需要对数据进行立即处理,或者不关心数据的处理结果时,可以使用中间件进行异步处理。(celery就是对消息队列的封装)
- 冗余存储:消息中间件可以对数据进行持久化存储,直到你消费完成后再进行删除。
AMQP协议
AMQP (Advanced Message Queuing Protocol) 是一个提供统一消息服务的应用层通讯协议,为消息中间件提供统一的开发规范。不同客户端可以将消息投递到中间件上,或从上面获取消息;发送消息和接收消息的客户端可以采用不同的语言开发、不同的技术实现,但必须遵循相同的 AMQP 协议。AMQP 协议本身包括以下三层
- Module Layer:位于协议最高层,主要定义了一些供客户端调用的命令,客户端可以利用这些命令实现自己的业务逻辑。例如:可以使用 Queue.Declare 命令声明一个队列或者使用 Basic.Consume 订阅消费一个队列中的消息。
- Session Layer:位于中间层,主要负责将客户端的命令发送给服务器,再将服务端的应答返回给客户端,主要为客户端与服务器之间的通信提供可靠性同步机制和错误处理。
- Transport Layer:位于最底层,主要传输二进制数据流 ,提供帧的处理、信道复用、错误检测和数据表示等。
RabbitMQ
RabbitMQ 完全实现了 AMQP 协议,并基于相同的模型架构。
支持多种消息传递协议,易于部署,支持跨语言开发,可以通过集群来实现高可用性和高吞吐
rabbitmq和kafka比较
- rabbitmq:吞吐量小,消息确认,订单,对消息可靠性有要求,就用它
- kafka:吞吐量高,注重高吞吐量,不注重消息的可靠性,数据量特别大
电商、金融等对事务性要求很高的,可以考虑RabbitMQ
日志相关就用Kafka
基本概念
Publisher(发布者)
发布者 (或称为生产者) 负责生产消息并将其投递到指定的交换器上。
Message(消息)
消息由消息头和消息体组成。消息头用于存储与消息相关的元数据:如目标交换器的名字 (exchange_name) 、路由键 (RountingKey) 和其他可选配置 (properties) 信息。消息体为实际需要传递的数据。
Exchange(交换器)
交换器负责接收来自生产者的消息,并将将消息路由到一个或者多个队列中,如果路由不到,则返回给生产者或者直接丢弃,这取决于交换器的 mandatory 属性:
- 当 mandatory 为 true 时:如果交换器无法根据自身类型和路由键找到一个符合条件的队列,则会将该消息返回给生产者;
- 当 mandatory 为 false 时:如果交换器无法根据自身类型和路由键找到一个符合条件的队列,则会直接丢弃该消息。
BindingKey (绑定键)
交换器与队列通过 BindingKey 建立绑定关系。
Routingkey(路由键)
生产者将消息发给交换器的时候,一般会指定一个 RountingKey,用来指定这个消息的路由规则。当 RountingKey 与 BindingKey 基于交换器类型的规则相匹配时,消息被路由到对应的队列中。
Queue(消息队列)
用于存储路由过来的消息。多个消费者可以订阅同一个消息队列,此时队列会将收到的消息将以轮询 (round-robin) 的方式分发给所有消费者。即每条消息只会发送给一个消费者,不会出现一条消息被多个消费者重复消费的情况。
Consumer(消费者)
消费者订阅感兴趣的队列,并负责消费存储在队列中的消息。为了保证消息能够从队列可靠地到达消费者,RabbitMQ 提供了消息确认机制 (message acknowledgement),并通过 autoAck 参数来进行控制:
- 当 autoAck 为 true 时:此时消息发送出去 (写入TCP套接字) 后就认为消费成功,而不管消费者是否真正消费到这些消息。当 TCP 连接或 channel 因意外而关闭,或者消费者在消费过程之中意外宕机时,对应的消息就丢失。因此这种模式可以提高吞吐量,但会存在数据丢失的风险。
- 当 autoAck 为 false 时:需要用户在数据处理完成后进行手动确认,只有用户手动确认完成后,RabbitMQ 才认为这条消息已经被成功处理。这可以保证数据的可靠性投递,但会降低系统的吞吐量。
什么是死信队列
需求:订单超时未支付,自动取消该订单。那么通过RabbitMQ
实现的延时队列就是实现该需求的一种方式。
死信
顾名思义,就是死掉的信息,英文是Dead Letter。死信交换机(Dead-Letter-Exchange)
和普通交换机没有区别,都是可以接受信息并转发到与之绑定并能路由到的队列,区别在于死信交换机
是转发死信
的,而和该死信交换机
绑定的队列就是死信队列
。说的再通俗一点,死信交换机和死信队列其实都只是普通的交换机和队列,只不过接受、转发的信息是死信
,其他操作并没有区别。
当一条消息在队列中出现以下三种情况的时候,该消息就会变成一条死信。
- 消息被拒绝(
basic.reject / basic.nack
),并且requeue = false
- 消息过期,因为队列设置了
TTL(Time To Live)
时间。 - 消息被丢弃,因为超过了队列的长度限制。
当消息在一个队列中变成一个死信之后,如果配置了死信队列,它将被重新publish到死信交换机,死信交换机将死信投递到一个队列上,这个队列就是死信队列。
rabbitmq两种安装
原生安装
0.安装扩展epel源
1.yum -y install erlang
2.yum -y install rabbitmq-server
3.systemctl start rabbitmq-server
docker拉取(推荐)
1.拉取镜像(这里使用阿里云拉取)
dockerhub拉取
docker pull rabbitmq:management(management自动开启了web管理界面)
阿里云拉取
docker pull registry.cn-hangzhou.aliyuncs.com/hankewei/hkwimage:rabbitmq3.10.19-management
阿里云拉下来重命名
docker tag registry.cn-hangzhou.aliyuncs.com/hankewei/hkwimage:rabbitmq3.10.19-management rabbitmq:3.10.19-management
2.启动一个容器
docker run -di --name rabbitmq3.10.19 -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=root123456 -p 15672:15672 -p 5672:5672 rabbitmq:3.10.19-management
5672是rabbitmq的默认端口,15672是web管理界面的端口
3.像MySQL之类的,可以创建用户
3.1进入容器
docker exec -ti rabbitmq3.10.19 /bin/bash
3.2新增用户
rabbitmqctl add_user 用户名 密码
3.3查看权限相关命令
rabbitmqctl help set_permissions
4.分配权限
rabbitmqctl set_user_tags 用户名 administrator
rabbitmqctl set_permissions -p "/" 用户名 ".*" ".*" ".*"
Python中使用rabbitmq
安装: pip install pika
基本使用
settings.py
QUEUE_NAME = 'hello'
RABBITMQ_SERVER_HOST = '10.0.0.10'
RABBITMQ_SERVER_USER = 'admin'
RABBITMQ_SERVER_PASSWORD = 'root123456'
消费者
import pika
from settings import *
def main():
# 无密码的连接
# connection = pika.BlockingConnection(pika.ConnectionParameters(host='101.133.225.166'))
# 有密码的连接
credentials = pika.PlainCredentials(RABBITMQ_SERVER_USER, RABBITMQ_SERVER_PASSWORD)
connection = pika.BlockingConnection(pika.ConnectionParameters(RABBITMQ_SERVER_HOST, credentials=credentials))
channel = connection.channel()
channel.queue_declare(queue=QUEUE_NAME)
def callback(ch, method, properties, body):
print(" [x] Received %r" % body)
# on_message_callback回调函数,auto_ack自动确认
channel.basic_consume(queue=QUEUE_NAME, on_message_callback=callback, auto_ack=True)
channel.start_consuming()
if __name__ == '__main__':
# 消费者会夯在这等待生产者生产东西
main()
生产者
import pika
from settings import *
# 有密码的连接
credentials = pika.PlainCredentials(RABBITMQ_SERVER_USER, RABBITMQ_SERVER_PASSWORD)
connection = pika.BlockingConnection(pika.ConnectionParameters(RABBITMQ_SERVER_HOST, credentials=credentials))
# 拿到channel对象
channel = connection.channel()
# 声明一个队列
channel.queue_declare(queue=QUEUE_NAME) # 指定队列名字
# 生产者向队列中放一条消息
channel.basic_publish(exchange='',
routing_key=QUEUE_NAME, # 必须和消费者中的队列名字一样
body='this is body/content')
print("sent success")
# 关闭连接
connection.close()
持久化
主要更改代码
channel.queue_declare(queue=QUEUE_NAME, durable=True)
properties=pika.BasicProperties(
delivery_mode=2,
)
报错处理
406, "PRECONDITION_FAILED - inequivalent arg 'durable' for queue '持久化' in vhost '/': received 'true' but current is 'false'"
重新设置一个队列即可
settings.py
QUEUE_NAME = '持久化'
RABBITMQ_SERVER_HOST = '10.0.0.10'
RABBITMQ_SERVER_USER = 'admin'
RABBITMQ_SERVER_PASSWORD = 'root123456'
消费者
import pika
from settings import *
def main():
# 有密码的连接
credentials = pika.PlainCredentials(RABBITMQ_SERVER_USER, RABBITMQ_SERVER_PASSWORD)
connection = pika.BlockingConnection(pika.ConnectionParameters(RABBITMQ_SERVER_HOST, credentials=credentials))
channel = connection.channel()
channel.queue_declare(queue=QUEUE_NAME, durable=True)
def callback(ch, method, properties, body):
print(" [x] Received %r" % body)
# 真正的消息处理完了,再发确认
ch.basic_ack(delivery_tag=method.delivery_tag)
channel.basic_consume(queue=QUEUE_NAME, on_message_callback=callback, auto_ack=False)
channel.start_consuming()
if __name__ == '__main__':
# 消费者会夯在这等待生产者生产东西
main()
生产者
import pika
from settings import *
# 有密码的连接
credentials = pika.PlainCredentials(RABBITMQ_SERVER_USER, RABBITMQ_SERVER_PASSWORD)
connection = pika.BlockingConnection(pika.ConnectionParameters(RABBITMQ_SERVER_HOST, credentials=credentials))
# 拿到channel对象
channel = connection.channel()
# 声明一个队列
# durable=True开启持久化
channel.queue_declare(queue=QUEUE_NAME, durable=True) # 指定队列名字
# 生产者向队列中放一条消息
channel.basic_publish(
exchange='',
routing_key=QUEUE_NAME, # 必须和消费者中的队列名字一样
body='this is body/content',
properties=pika.BasicProperties(
delivery_mode=2,
)
)
print("sent success")
# 关闭连接
connection.close()
消息确认机制
主要更改代码
def callback(ch, method, properties, body):
print(" [x] Received %r" % body)
# 真正的消息处理完了,再发确认
ch.basic_ack(delivery_tag=method.delivery_tag)
channel.basic_consume(queue=QUEUE_NAME, on_message_callback=callback, auto_ack=False)
settings.py
QUEUE_NAME = '消息确认机制'
RABBITMQ_SERVER_HOST = '10.0.0.10'
RABBITMQ_SERVER_USER = 'admin'
RABBITMQ_SERVER_PASSWORD = 'root123456'
消费者
import pika
from settings import *
def main():
# 有密码的连接
credentials = pika.PlainCredentials(RABBITMQ_SERVER_USER, RABBITMQ_SERVER_PASSWORD)
connection = pika.BlockingConnection(pika.ConnectionParameters(RABBITMQ_SERVER_HOST, credentials=credentials))
channel = connection.channel()
channel.queue_declare(queue=QUEUE_NAME, durable=True)
def callback(ch, method, properties, body):
print(" [x] Received %r" % body)
# 真正的消息处理完了,再发确认
ch.basic_ack(delivery_tag=method.delivery_tag)
# auto_ack=True,队列收到确认,就会自动把消费过的消息删除
# channel.basic_consume(queue=QUEUE_NAME, on_message_callback=callback, auto_ack=True)
# 一般是auto_ack=False加上上面的ch.basic_ack(delivery_tag=method.delivery_tag)
# 等待消息处理完后,在发送确认消息
channel.basic_consume(queue=QUEUE_NAME, on_message_callback=callback, auto_ack=False)
channel.start_consuming()
if __name__ == '__main__':
# 消费者会夯在这等待生产者生产东西
main()
生产者
import pika
from settings import *
# 有密码的连接
credentials = pika.PlainCredentials(RABBITMQ_SERVER_USER, RABBITMQ_SERVER_PASSWORD)
connection = pika.BlockingConnection(pika.ConnectionParameters(RABBITMQ_SERVER_HOST, credentials=credentials))
# 拿到channel对象
channel = connection.channel()
# 声明一个队列
# durable=True开启持久化
channel.queue_declare(queue=QUEUE_NAME, durable=True) # 指定队列名字
# 生产者向队列中放一条消息
channel.basic_publish(
exchange='',
routing_key=QUEUE_NAME, # 必须和消费者中的队列名字一样
body='this is body/content',
properties=pika.BasicProperties(
delivery_mode=2,
)
)
print("sent success")
# 关闭连接
connection.close()
闲置消费
默认各个消费者间切换着来处理生产,但是如果一个生产占用了大量的时间,后面的生产再次过来时,如果有其他空闲的消费者,不会给他空闲的消费者,而是会夯在那
闲置消费就是为了让空闲的消费者来处理生产,而不是等待切换到自己才去处理生产
# 消费者里面配置一句话,谁闲置谁获取,没必要按照顺序一个一个来
channel.basic_qos(prefetch_count=1)
settings.py
QUEUE_NAME = '闲置消费'
RABBITMQ_SERVER_HOST = '10.0.0.10'
RABBITMQ_SERVER_USER = 'admin'
RABBITMQ_SERVER_PASSWORD = 'root123456'
消费者
import pika
from settings import *
def main():
credentials = pika.PlainCredentials(RABBITMQ_SERVER_USER, RABBITMQ_SERVER_PASSWORD)
connection = pika.BlockingConnection(pika.ConnectionParameters(RABBITMQ_SERVER_HOST, credentials=credentials))
channel = connection.channel()
channel.queue_declare(queue=QUEUE_NAME, durable=True)
def callback(ch, method, properties, body):
# 等待20秒,20秒内有其他的生产,会分配给另外的消费者
import time
time.sleep(20)
print(" [x] Received %r" % body)
ch.basic_ack(delivery_tag=method.delivery_tag)
# 配置一句话,谁闲置谁获取,没必要按照顺序一个一个来
channel.basic_qos(prefetch_count=1)
channel.basic_consume(queue=QUEUE_NAME, on_message_callback=callback, auto_ack=False)
channel.start_consuming()
if __name__ == '__main__':
main()
消费者2
import pika
from settings import *
def main():
credentials = pika.PlainCredentials(RABBITMQ_SERVER_USER, RABBITMQ_SERVER_PASSWORD)
connection = pika.BlockingConnection(pika.ConnectionParameters(RABBITMQ_SERVER_HOST, credentials=credentials))
channel = connection.channel()
channel.queue_declare(queue=QUEUE_NAME, durable=True)
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=QUEUE_NAME, on_message_callback=callback, auto_ack=False)
channel.start_consuming()
if __name__ == '__main__':
main()
生产者
import pika
from settings import *
credentials = pika.PlainCredentials(RABBITMQ_SERVER_USER, RABBITMQ_SERVER_PASSWORD)
connection = pika.BlockingConnection(pika.ConnectionParameters(RABBITMQ_SERVER_HOST, credentials=credentials))
channel = connection.channel()
channel.queue_declare(queue=QUEUE_NAME, durable=True)
channel.basic_publish(
exchange='',
routing_key=QUEUE_NAME,
body='this is body/content',
properties=pika.BasicProperties(
delivery_mode=2,
)
)
print("sent success")
connection.close()
发布订阅
主要代码更改
生产者
channel.exchange_declare(exchange=EXCHANGE_NAME, exchange_type='fanout')
不再使用QUEUE_NAME,而是使用EXCHANGE_NAME
QUEUE_NAME = ''
EXCHANGE_NAME = 'logs'
settings.py
QUEUE_NAME = ''
RABBITMQ_SERVER_HOST = '10.0.0.10'
RABBITMQ_SERVER_USER = 'admin'
RABBITMQ_SERVER_PASSWORD = 'root123456'
EXCHANGE_NAME = 'logs'
订阅者
import pika
from settings import *
def main():
credentials = pika.PlainCredentials(RABBITMQ_SERVER_USER, RABBITMQ_SERVER_PASSWORD)
connection = pika.BlockingConnection(pika.ConnectionParameters(RABBITMQ_SERVER_HOST, credentials=credentials))
channel = connection.channel()
channel.exchange_declare(exchange=EXCHANGE_NAME, exchange_type='fanout')
result = channel.queue_declare(queue='', exclusive=True)
queue_name = result.method.queue
print(queue_name)
channel.queue_bind(exchange=EXCHANGE_NAME, queue=queue_name)
def callback(ch, method, properties, body):
print(" [x] Received %r" % body)
channel.basic_consume(queue=queue_name, on_message_callback=callback, auto_ack=True)
channel.start_consuming()
if __name__ == '__main__':
main()
生产者
import pika
from settings import *
credentials = pika.PlainCredentials(RABBITMQ_SERVER_USER, RABBITMQ_SERVER_PASSWORD)
connection = pika.BlockingConnection(pika.ConnectionParameters(RABBITMQ_SERVER_HOST, credentials=credentials))
channel = connection.channel()
# 声明队列没有指定名字,指定了exchange
channel.exchange_declare(exchange=EXCHANGE_NAME, exchange_type='fanout')
message = "info: publish"
channel.basic_publish(
exchange=EXCHANGE_NAME,
routing_key=QUEUE_NAME,
body=message,
)
print("sent success")
connection.close()
发布订阅-按关键字匹配
主要代码更改
QUEUE_NAME要不一样,第二个设为空就行
用相同的EXCHANGE_NAME
ROUTING_KEY可以分为两个,生产者可以指定往那个里面送值
settings.py
QUEUE_NAME = '发布订阅-按关键字匹配'
QUEUE_NAME2 = ''
RABBITMQ_SERVER_HOST = '10.0.0.10'
RABBITMQ_SERVER_USER = 'admin'
RABBITMQ_SERVER_PASSWORD = 'root123456'
EXCHANGE_NAME = 'logs2'
ROUTING_KEY = 'hkw'
ROUTING_KEY2 = 'hkw2'
订阅者1
import pika
from settings import *
def main():
credentials = pika.PlainCredentials(RABBITMQ_SERVER_USER, RABBITMQ_SERVER_PASSWORD)
connection = pika.BlockingConnection(pika.ConnectionParameters(RABBITMQ_SERVER_HOST, credentials=credentials))
channel = connection.channel()
channel.exchange_declare(exchange=EXCHANGE_NAME, exchange_type='direct')
result = channel.queue_declare(queue=QUEUE_NAME, exclusive=True)
queue_name = result.method.queue
print(queue_name)
channel.queue_bind(exchange=EXCHANGE_NAME, queue=queue_name, routing_key=ROUTING_KEY)
def callback(ch, method, properties, body):
print(" [x] Received %r" % body)
channel.basic_consume(queue=queue_name, on_message_callback=callback, auto_ack=True)
channel.start_consuming()
if __name__ == '__main__':
main()
订阅者2
import pika
from settings import *
def main():
credentials = pika.PlainCredentials(RABBITMQ_SERVER_USER, RABBITMQ_SERVER_PASSWORD)
connection = pika.BlockingConnection(pika.ConnectionParameters(RABBITMQ_SERVER_HOST, credentials=credentials))
channel = connection.channel()
channel.exchange_declare(exchange=EXCHANGE_NAME, exchange_type='direct')
result = channel.queue_declare(queue=QUEUE_NAME2, exclusive=True)
queue_name = result.method.queue
print(queue_name)
channel.queue_bind(exchange=EXCHANGE_NAME, queue=queue_name, routing_key=ROUTING_KEY)
channel.queue_bind(exchange=EXCHANGE_NAME, queue=queue_name, routing_key=ROUTING_KEY2)
def callback(ch, method, properties, body):
print(" [x] Received %r" % body)
channel.basic_consume(queue=queue_name, on_message_callback=callback, auto_ack=True)
channel.start_consuming()
if __name__ == '__main__':
main()
生产者
import pika
from settings import *
credentials = pika.PlainCredentials(RABBITMQ_SERVER_USER, RABBITMQ_SERVER_PASSWORD)
connection = pika.BlockingConnection(pika.ConnectionParameters(RABBITMQ_SERVER_HOST, credentials=credentials))
channel = connection.channel()
# 声明队列没有指定名字,指定了exchange
channel.exchange_declare(exchange=EXCHANGE_NAME, exchange_type='direct')
message = "info: publish"
channel.basic_publish(
exchange=EXCHANGE_NAME,
routing_key=ROUTING_KEY,
# ROUTING_KEY两个订阅者都能收到
# ROUTING_KEY2只有订阅者2能收到
body=message,
)
print("sent success")
connection.close()
发布订阅-按模糊匹配
channel.exchange_declare(exchange=EXCHANGE_NAME, exchange_type='topic')
# 表示后面可以跟任意多个字符
* 表示后面只能跟一个单词
通过ROUTING_KEY的不同设置
QUEUE_NAME多个订阅者不能用同一个
settings.py
QUEUE_NAME = '发布订阅-按模糊匹配'
QUEUE_NAME2 = ''
RABBITMQ_SERVER_HOST = '10.0.0.10'
RABBITMQ_SERVER_USER = 'admin'
RABBITMQ_SERVER_PASSWORD = 'root123456'
EXCHANGE_NAME = 'mm'
ROUTING_KEY = 'python.go.docker' # 生产者
ROUTING_KEY2 = 'python.#' # 订阅者
ROUTING_KEY3 = 'python.go.*' # 订阅者
订阅者1
import pika
from settings import *
def main():
credentials = pika.PlainCredentials(RABBITMQ_SERVER_USER, RABBITMQ_SERVER_PASSWORD)
connection = pika.BlockingConnection(pika.ConnectionParameters(RABBITMQ_SERVER_HOST, credentials=credentials))
channel = connection.channel()
channel.exchange_declare(exchange=EXCHANGE_NAME, exchange_type='topic')
result = channel.queue_declare(queue=QUEUE_NAME, exclusive=True)
queue_name = result.method.queue
print(queue_name)
channel.queue_bind(exchange=EXCHANGE_NAME, queue=queue_name, routing_key=ROUTING_KEY2)
def callback(ch, method, properties, body):
print(" [x] Received %r" % body)
channel.basic_consume(queue=queue_name, on_message_callback=callback, auto_ack=True)
channel.start_consuming()
if __name__ == '__main__':
main()
订阅者2
import pika
from settings import *
def main():
credentials = pika.PlainCredentials(RABBITMQ_SERVER_USER, RABBITMQ_SERVER_PASSWORD)
connection = pika.BlockingConnection(pika.ConnectionParameters(RABBITMQ_SERVER_HOST, credentials=credentials))
channel = connection.channel()
channel.exchange_declare(exchange=EXCHANGE_NAME, exchange_type='topic')
result = channel.queue_declare(queue=QUEUE_NAME2, exclusive=True)
queue_name = result.method.queue
print(queue_name)
channel.queue_bind(exchange=EXCHANGE_NAME, queue=queue_name, routing_key=ROUTING_KEY3)
def callback(ch, method, properties, body):
print(" [x] Received %r" % body)
channel.basic_consume(queue=queue_name, on_message_callback=callback, auto_ack=True)
channel.start_consuming()
if __name__ == '__main__':
main()
生产者
import pika
from settings import *
credentials = pika.PlainCredentials(RABBITMQ_SERVER_USER, RABBITMQ_SERVER_PASSWORD)
connection = pika.BlockingConnection(pika.ConnectionParameters(RABBITMQ_SERVER_HOST, credentials=credentials))
channel = connection.channel()
# 声明队列没有指定名字,指定了exchange
channel.exchange_declare(exchange=EXCHANGE_NAME, exchange_type='topic')
message = "info: publish"
channel.basic_publish(
exchange=EXCHANGE_NAME,
routing_key=ROUTING_KEY,
body=message,
)
print("sent success")
connection.close()
RPC
RPC(远程过程调用)
gRPC(谷歌出的,跨语言)
通过rabbitmq实现rpc
settings.py
QUEUE_NAME = 'rabbitmq实现rpc1'
QUEUE_NAME_CLIENT = ''
RABBITMQ_SERVER_HOST = '10.0.0.10'
RABBITMQ_SERVER_USER = 'admin'
RABBITMQ_SERVER_PASSWORD = 'root123456'
EXCHANGE_NAME = 'rabbitmq实现rpc-exchage1'
EXCHANGE_NAME_CLIENT = ''
ROUTING_KEY = QUEUE_NAME
服务端
import pika
from settings import *
credentials = pika.PlainCredentials(RABBITMQ_SERVER_USER, RABBITMQ_SERVER_PASSWORD)
connection = pika.BlockingConnection(pika.ConnectionParameters(RABBITMQ_SERVER_HOST, credentials=credentials))
channel = connection.channel()
channel.queue_declare(queue=QUEUE_NAME)
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=EXCHANGE_NAME_CLIENT,
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=QUEUE_NAME, on_message_callback=on_request)
print(" [x] Awaiting RPC requests")
channel.start_consuming()
客户端
import pika
import uuid
from settings import *
class FibonacciRpcClient(object):
def __init__(self):
self.credentials = pika.PlainCredentials(RABBITMQ_SERVER_USER, RABBITMQ_SERVER_PASSWORD)
self.connection = pika.BlockingConnection(
pika.ConnectionParameters(RABBITMQ_SERVER_HOST, credentials=self.credentials)
)
self.channel = self.connection.channel()
result = self.channel.queue_declare(queue=QUEUE_NAME_CLIENT, 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):
if self.corr_id == props.correlation_id:
self.response = body
def call(self, n):
self.response = None
self.corr_id = str(uuid.uuid4())
self.channel.basic_publish(
exchange=EXCHANGE_NAME_CLIENT,
routing_key=ROUTING_KEY,
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(5)")
response = fibonacci_rpc.call(10) # 外界看上去,就像调用本地的call()函数一样
print(" [.] Got %r" % response)
python中的rpc框架
SimpleXMLRPCServer
服务端
from xmlrpc.server import SimpleXMLRPCServer
class RPCServer(object):
def __init__(self):
super(RPCServer, self).__init__()
print('self', 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)
# SimpleXMLRPCServer
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
# SimpleXMLRPCServer
def xmlrpc_client():
print('xmlrpc client')
c = ServerProxy('http://localhost:4242')
data = {'client:' + str(i): i for i in range(100)}
start = time.time()
for i in range(50):
a = c.getObj()
print(a)
for i in range(50):
c.sendObj(data)
print('xmlrpc total time %s' % (time.time() - start))
if __name__ == '__main__':
xmlrpc_client()
ZeroRPC实现rpc(速度快的一批)
pip install zerorpc
服务端
import zerorpc
class RPCServer(object):
def __init__(self):
super(RPCServer, self).__init__()
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())
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.time()
for i in range(500):
a = c.getObj()
print(a)
for i in range(500):
c.sendObj(data)
print('total time %s' % (time.time() - start))
if __name__ == '__main__':
zerorpc_client()