消息队列

消息队列中间件 (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. 周末拾遗 xsos 的学习与使用

    周末拾遗 xsos 的学习与使用 摘要 周末陪儿子上跆拳道课. 自己一个人傻乎乎的开着笔记本想着学习点东西. 上午看到了一个sosreport的工具. 本来想学习一下. 发现xsos 应该是更好的一个 ...

  2. [转帖]docker使用buildx构建多平台(x86,arm64)构架镜像

    https://blog.csdn.net/atzqtzq/article/details/128583331 配置文件激活buildx docker目前使用版本为Server Version: 20 ...

  3. [转帖]简单理解Linux的Memory Overcommit

    https://zhuanlan.zhihu.com/p/551677956 Memory Overcommit的意思是操作系统承诺给进程的内存大小超过了实际可用的内存.一个保守的操作系统不会允许me ...

  4. Mysql localhost 无法登录 root用户的处理过程

    问题说明: 前段时间同事修改密码, 但是发现修改了密码之后,外面可以连接root用户, 但是本地无法连接了. 怀疑是密码修改存在问题,需要重新进行处理 这里进行简单的描述. 问题现象 使用 mysql ...

  5. js获取字符串最后几位字符数

    截取字符串 为什要截取字符串呢??? 因为有些时候,我们需要判断某一个字符串中是不是,含有特定的字符 substring(a)从起始位置开始(包含a这个位置),一直到字符串的末尾(截取字符串最后6个) ...

  6. CANVAS ----- 鼠标移动画圆

    1.增加鼠标移动事件 $('#canvas').mousemove(function (e) { draw(event); }); 2.获取鼠标在canvas上的坐标 function getCanv ...

  7. 强大的AWS lambda

    AWS强大的lambda 自从几年前换工作后,我所参与的项目一直都是基于AWS云服务的架构,我慢慢对serverless的相关基础建设有了一定了解和实践经验.其中lambda是我心中最强大的serve ...

  8. 深度学习应用篇-自然语言处理-命名实体识别[9]:BiLSTM+CRF实现命名实体识别、实体、关系、属性抽取实战项目合集(含智能标注)

    深度学习应用篇-自然语言处理-命名实体识别[9]:BiLSTM+CRF实现命名实体识别.实体.关系.属性抽取实战项目合集(含智能标注) 1.命名实体识别介绍 命名实体识别(Named Entity R ...

  9. Linux系统NTP配置同步修改硬件时钟

    前言: 硬件时钟:即BIOS时间,就是CMOS设置时看到的时间,存储在主板BIOS里,关机及断电后由主板电池供电维持时间的守时.    系统时钟:linux系统Kernel时间,由CPU守时,关机及断 ...

  10. System V|共享内存基本通信框架搭建|【超详细的代码解释和注释】

    前言 那么这里博主先安利一下一些干货满满的专栏啦! 手撕数据结构https://blog.csdn.net/yu_cblog/category_11490888.html?spm=1001.2014. ...