消息队列

消息队列中间件 (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()

RabbitMQ和RPC的更多相关文章

  1. RabbitMQ 实现RPC

    实现RPC 首先要弄明白,RPC是个什么东西. (RPC) Remote Procedure Call Protocol 远程过程调用协议 在一个大型的公司,系统由大大小小的服务构成,不同的团队维护不 ...

  2. RabbitMQ中RPC的实现及其通信机制

    RabbitMQ中RPC的实现:客户端发送请求消息,服务端回复响应消息,为了接受响应response,客户端需要发送一个回调队列的地址来接受响应,每条消息在发送的时候会带上一个唯一的correlati ...

  3. RabbitMQ(四):RPC的实现

    原文:RabbitMQ(四):RPC的实现 一.RPC RPC(Remote Procedure Call)—远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议. ...

  4. 基于RabbitMQ的Rpc框架

    参考文档:https://www.cnblogs.com/ericli-ericli/p/5917018.html 参考文档:RabbitMQ 实现RPC MQ的使用场景大概包括解耦,提高峰值处理能力 ...

  5. 什么是Rabbitmq消息队列? (安装Rabbitmq,通过Rabbitmq实现RPC全面了解,从入门到精通)

    目录 Rabbitmq 一: 消息队列介绍 1.介绍 2.MQ解决了什么问题 1.应用的解耦 2.流量削峰 3.消息分发(发布订阅: 观察者模式) 4.异步消息(celery就是对消息队列的封装) 3 ...

  6. RabbitMQ 笔记-RPC

    RabbitMQ中实现RPC的机制是: 客户端发送请求(消息)时,在消息的属性(MessageProperties,在AMQP协议中定义了14中properties,这些属性会随着消息一起发送)中设置 ...

  7. rabbitmq (五)RPC

    Remote Procedure Call or RPC(远程函数调用) 当我们需要在远程计算机上运行一个函数,并且等待结果的时候,我们用到RPC 在rabbitmq客户端使用call函数,发送RPC ...

  8. RabbitMQ、RPC、SaltStack "贡"具的使用

    消息队列 使用队列的场景 在程序系统中,例如外卖系统,订单系统,库存系统,优先级较高 发红包,发邮件,发短信,app消息推送等任务优先级很低,很适合交给消息队列去处理,以便于程序系统更快的处理其他请求 ...

  9. RabbitMQ除开RPC的五种消模型----原生API

    2.五种消息模型 RabbitMQ提供了6种消息模型,但是第6种其实是RPC,并不是MQ,因此不予学习.那么也就剩下5种. 但是其实3.4.5这三种都属于订阅模型,只不过进行路由的方式不同. 通过一个 ...

  10. rabbitmq之rpc

    环境:windows或者Linux,python3.6,rabbitmq3.5要求: 可以对指定机器异步的执行多个命令 例子: >>:run "df -h" --hos ...

随机推荐

  1. [转帖]linux 磁盘队列深度nr_requests 和 queue_depth

    linux 磁盘队列深度nr_requests 和 queue_depth nr_requests 和 queue_depth 修改配置值 nr_requests 和 queue_depth 区别 i ...

  2. HTTPS下tomcat与nginx的前端性能比较

    HTTPS下tomcat与nginx的前端性能比较 摘要 之前比较http的web服务器的性能. 发现nginx 比 tomcat 要好 50% 然后想到, https的情况下不知道两者有什么区别 所 ...

  3. Bash 脚本发送消息到企业微信的办法

    1. Study From https://www.cnblogs.com/elvi/p/11444388.html 2. 申请或者使用已经有的企业微信. 首先是获取一下企业id 方法如图: 3. 创 ...

  4. PG数据库恢复简单记录

    公司同事给了一个很小的数据 我这边进行备份和恢复操作 第一步 创建数据库 su - postgres #进入pg数据库的用户 psql #输入密码 登录 create user demo with p ...

  5. pytest-xdist分布式

    使用pytest框架运行每条case的时候,都是上一条运行结束才会运行下一条,要是有成千上百条case 且每条运行2s那就是2*总条数,会浪费大量的时间和人力.为了节约时间和人力成本,pytest提供 ...

  6. python批量上传文件到七牛云

    导航 引子 棘手的需求 化繁为简 实战案例 结语 参考 本文首发于智客工坊-<python批量上传文件到七牛云>,感谢您的阅读,预计阅读时长3min. 古之立大事者,不惟有超世之才,亦必有 ...

  7. 文心一言 VS 讯飞星火 VS chatgpt (187)-- 算法导论14.1 4题

    四.用go语言,写出一个递归过程 OS-KEY-RANK(T,k),以一棵顺序统计树T和一个关键字k作为输入,要求返回 k 在由 T 表示的动态集合中的秩.假设 T 的所有关键字都不相同. 文心一言, ...

  8. How to Use Github

    C:\Windows\System32\drivers\etc\hosts 在最后加上一句 20.205.243.166 github.com 从 https://ping.chinaz.com/ 来 ...

  9. MySQL【四】---案例实战{拆分多表、外键创建等}

    1.准备数据 数据准备 create database jing_dong charset = utf8mb4; 创建一个商品goods数据表: create table goods( id int ...

  10. Python 原生Socket实现端口扫描

    端口扫描,就是逐个对一段端口或指定的端口进行扫描.通过扫描结果可以知道一台计算机上都提供了哪些服务,Python中使用Socket即可实现对特定端口的探测,以及对C段的扫描. 扫描目标主机Banner ...