rabbitmq
一 消息队列介绍
1.1 介绍
消息队列就是基础数据结构中的“先进先出”的一种数据机构。想一下,生活中买东西,需要排队,先排的人先买消费,就是典型的“先进先出”
message queue:消息队列 简称mq
1.2 六种消息模型
第一种:简单模式 Simple
第二种:工作模式 Work
第三种:发布订阅模式
第四种:路由模式
第五种:主题Topic模式
第六种:rpc
1.3 消息队列解决什么问题?
MQ是一直存在,不过随着微服务架构的流行,成了解决微服务之间问题的常用工具。
应用解耦
- 单体应用 ---> 分布式应用
- 把一个大功能拆分成小功能,功能直接数据使用mq交互
以电商应用为例,应用中有订单系统、库存系统、物流系统、支付系统。用户创建订单后,如果耦合调用库存系统、物流系统、支付系统,任何一个子系统出了故障,都会造成下单操作异常。
当转变成基于消息队列的方式后,系统间调用的问题会减少很多,比如物流系统因为发生故障,需要几分钟来修复。在这几分钟的时间里,物流系统要处理的内存被缓存在消息队列中,用户的下单操作可以正常完成。当物流系统恢复后,继续处理订单信息即可,中单用户感受不到物流系统的故障。提升系统的可用性
流量削峰
- celery异步
- 秒杀场景:比如某一时刻并发秒杀--不是同步处理-->而是把秒杀任务放到消息队列中,一点点消费
举个例子,如果订单系统最多能处理一万次订单,这个处理能力应付正常时段的下单时绰绰有余,正常时段我们下单一秒后就能返回结果。但是在高峰期,如果有两万次下单操作系统是处理不了的,只能限制订单超过一万后不允许用户下单。
使用消息队列做缓冲,我们可以取消这个限制,把一秒内下的订单分散成一段时间来处理,这事有些用户可能在下单十几秒后才能收到下单成功的操作,但是比不能下单的体验要好。
消息分发
- 发布订阅:订阅者订阅了消息,只要发布者发布,订阅者就能收到
多个服务队数据感兴趣,只需要监听同一类消息即可处理。
例如A产生数据,B对数据感兴趣。如果没有消息的队列A每次处理完需要调用一下B服务。过了一段时间C对数据也感性,A就需要改代码,调用B服务,调用C服务。只要有服务需要,A服务都要改动代码。很不方便。
有了消息队列后,A只管发送一次消息,B对消息感兴趣,只需要监听消息。C感兴趣,C也去监听消息。A服务作为基础服务完全不需要有改动
异步消息
- 如果使用resful调用--->同步调用
- 发送了调用请求后,继续干自己的活,等另一个服务数据准备好,会放到mq中,再去mq中取
有些服务间调用是异步的,例如A调用B,B需要花费很长时间执行,但是A需要知道B什么时候可以执行完,以前一般有两种方式,A过一段时间去调用B的查询api查询。或者A提供一个callback api,B执行完之后调用api通知A服务。这两种方式都不是很优雅。(但是这种情况适用于A服务之后的流程不会用到B中处理的数据的结果,如果A必须有B的结果之后才能执行下一步,就只能等着B处理结果完成)
使用消息总线,可以很方便解决这个问题,A调用B服务后,只需要监听B处理完成的消息,当B处理完成后,会发送一条消息给MQ,MQ会将此消息转发给A服务。
这样A服务既不用循环调用B的查询api,也不用提供callback api。同样B服务也不用做这些操作。A服务还能及时的得到异步处理成功的消息。
常见消息队列比较
# rabbitmq和kafka
-编程语言不同:erlang,java
-对客户端支持都一样,都支持
-处理数据能力(吞吐量):rabbitmq低于kafak(处理大数据类的性能高十万级)
-可靠性:rabbitmq更高
-图形化界面:rabbitmq有自己的管理界面,kafak第三方的
二 rabbitmq安装
官网:https://www.rabbitmq.com/getstarted.html
2.1 安装
# 1 centos 安装
# 安装配置epel源
# 安装erlang
yum -y install erlang
# 安装RabbitMQ
yum -y install rabbitmq-server
# 2 win
-官网先下载erlang,版本跟rabbimq版本有对应关系
-rabbimq 官网下载安装包:https://github.com/rabbitmq/rabbitmq-server/releases
# 创建用户
上面两种方式启动后,会启动一个服务。访问本地的15672端口,也会打开它的图形化界面。但是它上面没有用户,需要自己创建用户,使用命令来创建用户。
rabbitmqctl add_user lqz 123
# 设置用户为administrator角色
rabbitmqctl set_user_tags lqz administrator
# 设置权限
rabbitmqctl set_permissions -p "/" root ".*" ".*" ".*"
# 然后重启rabbiMQ服务
systemctl reatart rabbitmq-server
# 然后可以使用刚才的用户远程连接rabbitmq server了。
# 3 docker安装
docker pull rabbitmq:management
# 设置用户名和密码,做端口映射:一个端口是web界面的端口,另一个端口是服务端口
docker run -di --name Myrabbitmq -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=admin -p 15672:15672 -p 5672:5672 rabbitmq:management
2.2 我们使用docker安装
# 3 docker拉取镜像
docker pull rabbitmq:management
# 启动成容器
# -e加环境变量:设置用户名和密码
# -p端口映射;15672 端口是web界面的端口(它自己的图形化界面),5672端口是rabbitmq服务端口
docker run -di --name Myrabbitmq -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=admin -p 15672:15672 -p 5672:5672 rabbitmq:management
# web 管理页面的端口是 15672
# 服务端口是5672,使用python连接时候,指定这个端口。此时python就是一个客户端,rabbitmq是服务端
上面都是运维来搭建的,我们要做的是,服务已经搭建好了,我们要连接rabbitmq服务,使用它。
三 基于queue实现生产者消费者
import queue # 线程队列
import threading
# 生成一个队列
message = queue.Queue(10)
def producer(i):
while True:
# 往队列中放值
message.put(i)
print('生产者生产了:%s' % i)
def consumer(i):
while True:
# 从队列中取值
msg = message.get()
print('消费者消费了:%s'%msg)
for i in range(12): # 12个生产者
t = threading.Thread(target=producer, args=(i,))
t.start()
for i in range(10): # 10个消费者
t = threading.Thread(target=consumer, args=(i,))
t.start()
四 rabbitmq基本使用
# 使用rabbitmq实现生成者消费者模型
对于RabbitMQ来说,生产和消费不再针对内存里的一个Queue对象,而是某台服务器上的RabbitMQ Server实现的消息队列。
# 安装模块:pip3 install pika
# https://www.rabbitmq.com/getstarted.html 不同语言的示例代码
应用场景:可以用来处理异步任务。例如在一个web应用程序中,将耗时的任务委托给后台处理器。
生产者
import pika
## 第一步:连接服务
# 无密码
# connection = pika.BlockingConnection(pika.ConnectionParameters('127.0.0.1', port=5672))
# 有密码
credentials = pika.PlainCredentials("admin","admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('47.94.149.117',port=5672,credentials=credentials)) # credentials指定用户和密码
## 第二步:得到一个channel,连接对象
channel = connection.channel()
## 第三步:声明一个队列(创建一个队列),队列名字叫lqz
channel.queue_declare(queue='lqz')
channel.basic_publish(exchange='', # 生产者
routing_key='lqz', # 消息队列名称
body='hello world') # 消息内容
## 第四步:队列关闭
connection.close()
消费者
import pika
credentials = pika.PlainCredentials("admin","admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('47.94.149.117',port=5672,credentials=credentials))
channel = connection.channel()
# 声明一个队列(创建一个队列),消费者也要声明队列,因为不清楚是客户端先启还是服务端先启,如果是客户端先启动,没有消息队列会报错
# 客户端这边的是如果有就直接使用这个消息队列,如果没有就创建出一个lqz的消息队列
channel.queue_declare(queue='lqz')
def callback(ch, method, properties, body): # 四个参数为标准格式
print("消费者接受到了任务: %r" % body)
channel.basic_consume( # 消费消息
queue='lqz', # 指定队列名
on_message_callback=callback, # 如果收到消息,就调用callback函数来处理消息
auto_ack=True
)
channel.start_consuming() # 开始消费消息,如果没有消息会一直阻塞者
五 消息安全ack
消息队列中,第一个任务被执行了,就会在消息队列中删除这条消息,但是如果在执行过程中如果出错了,这个任务就不会被执行了,这种情况不安全。
# 所以应该任务执行完成后,再给消息队列中发个信息,说任务执行完了,可以删除这个任务了。而不是一取到就删除这个任务
# 使用
## 消费者中
# 回调函数中执行任务结束后必须通知队列任务执行结束了
def callback(ch, method, properties, body):
print("消费者接受到了任务: %r" % body)
# 通知服务端,消息取走了,如果auto_ack=False,不加下面这句话,消息会一直存在
ch.basic_ack(delivery_tag=method.delivery_tag)
# autoAck设置为true时,消息队列可以不用在意消息消费者是否处理完消息,一直发送全部消息
channel.basic_consume(queue='lqz',on_message_callback=callback,auto_ack=False)
send
import pika
# 有密码
credentials = pika.PlainCredentials("admin","admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('8.130.125.9',credentials=credentials))
channel = connection.channel()
# 声明一个队列(创建一个队列)
channel.queue_declare(queue='lqz')
channel.basic_publish(exchange='',
routing_key='lqz', # 消息队列名称
body='hello world111')
connection.close()
receive
import pika
credentials = pika.PlainCredentials("admin","admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('8.130.125.9',credentials=credentials))
channel = connection.channel()
# 声明一个队列(创建一个队列)
channel.queue_declare(queue='lqz')
def callback(ch, method, properties, body):
print("消费者接受到了任务: %r" % body)
# 通知服务端,消息取走了,如果auto_ack=False,不加下面这句话,消息会一直存在
ch.basic_ack(delivery_tag=method.delivery_tag)
# autoAck设置为true时,消息队列可以不用在意消息消费者是否处理完消息,一直发送全部消息。
# 想要设置消息安全,需要设置auto_ack=False
channel.basic_consume(queue='lqz',on_message_callback=callback,auto_ack=False)
channel.start_consuming()
六 持久化
生产者把消息发送到消息队列中后,如果rabbitmq挂掉了,或者是中间rabbitmq重启了,那队列中保存的消息还存在吗?# 默认情况下是不存在的,不安全,会丢失数据
# 生产者
# 在声明队列的时候,就需要做持久化,加durable=True参数
channel.queue_declare(queue='lqz1',durable=True) # durable=True支持持久化,队列必须是新的才可以
# 消息也要持久化
channel.basic_publish(exchange='',
routing_key='lqz1',
body='hello world111',
properties=pika.BasicProperties(
delivery_mode=2, # 消息持久化
)
)
生产者
import pika
# 有密码
credentials = pika.PlainCredentials("admin","admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('8.130.125.9',credentials=credentials))
channel = connection.channel()
# 声明一个队列(创建一个队列),durable=True支持持久化,队列必须是新的才可以
channel.queue_declare(queue='lqz1',durable=True)
channel.basic_publish(exchange='',
routing_key='lqz1', # 消息队列名称
body='hello world111',
properties=pika.BasicProperties(
delivery_mode=2, # make message persistent,消息也持久化
)
)
connection.close()
# 重启服务
docker stop Myrabbitmq
docker start Myrabbitmq
# 或者
docker restart Myrabbitmq
消费者
import pika
credentials = pika.PlainCredentials("admin","admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('8.130.125.9',credentials=credentials))
channel = connection.channel()
# 声明一个队列(创建一个队列),持久化
channel.queue_declare(queue='lqz1',durable=True)
def callback(ch, method, properties, body):
print("消费者接受到了任务: %r" % body)
# 通知服务端,消息取走了
ch.basic_ack(delivery_tag=method.delivery_tag)
channel.basic_consume(
queue='lqz1',
on_message_callback=callback,
auto_ack=False
)
channel.start_consuming()
七 闲置消费
正常情况如果有多个消费者,是按照顺序第一个消息给第一个消费者,第二个消息给第二个消费者
但是可能第一个消息的消费者处理消息很耗时,一直没结束,就可以让第二个消费者优先获得闲置的消息
# 使用
生产者不用变化
# 消费者中加上这句话,先启动哪个,哪个就是第一个消费者
channel.basic_qos(prefetch_count=1) # 就只有这一句话 谁闲置谁获取,没必要按照顺序一个一个来
生产者
import pika
# 有密码
credentials = pika.PlainCredentials("admin","admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('8.130.125.9',credentials=credentials))
channel = connection.channel()
# 声明一个队列(创建一个队列),durable=True支持持久化,队列必须是新的才可以
channel.queue_declare(queue='lqz1',durable=True)
channel.basic_publish(exchange='',
routing_key='lqz1',
body='hello 888',
properties=pika.BasicProperties(
delivery_mode=2, # make message persistent,消息也持久化
)
)
connection.close()
消费者
import pika
import time
credentials = pika.PlainCredentials("admin","admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('8.130.125.9',credentials=credentials))
channel = connection.channel()
# 声明一个队列(创建一个队列)
channel.queue_declare(queue='lqz1',durable=True)
def callback(ch, method, properties, body):
time.sleep(10)
print("消费者接受到了任务: %r" % body)
# 消息安全ack
ch.basic_ack(delivery_tag=method.delivery_tag)
# 闲置消费
channel.basic_qos(prefetch_count=1) # 就加上这一句话 谁闲置谁获取,没必要按照顺序一个一个来
channel.basic_consume(
queue='lqz1',
on_message_callback=callback,
auto_ack=False)
channel.start_consuming()
八 发布订阅-订阅与发布模式fanout
发布订阅是extange(路由)生产者生产一个消息,客户端1和客户端2都可以收到同样的这个消息
# 使用
### 发布者
# 声明一个路由exchange叫m1,路由模式是fanout模式
channel.exchange_declare(exchange='m1',exchange_type='fanout')
# 发布消息
channel.basic_publish(exchange='m1',
routing_key='', # 不需要写发给哪个队列
body='lqz nb')
### 订阅者
channel.exchange_declare(exchange='m1',exchange_type='fanout')
# 随机生成一个队列,会自动生成一个随即名字的队列
result = channel.queue_declare(queue='',exclusive=True)
queue_name = result.method.queue
print(queue_name)
# 让exchange和queque进行绑定.
channel.queue_bind(exchange='m1',queue=queue_name)
channel.basic_consume(
queue=queue_name,
on_message_callback=callback,
auto_ack=True)
# 应用场景:
例如:中国气象局提供“天气预报”送入交换机,网易、新浪、百度、搜狐等门户接入通过队列绑定到该交换机,自动获取气象局推送的气象数据。
例如:买了视频网站的会员,只要网站上映新的电影,就会给你发送通知。
发布者
import pika
credentials = pika.PlainCredentials("admin","admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('101.133.225.166',credentials=credentials))
channel = connection.channel()
# 之前的方式一直是声明一个队列queue_declare
# 声明一个路由exchange叫m1,路由模式是fanout模式
channel.exchange_declare(exchange='m1',exchange_type='fanout')
# 发布消息
channel.basic_publish(exchange='m1',
routing_key='', # 不需要写发给哪个队列
body='lqz nb')
connection.close()
订阅者
- 开启几个py文件,就是几个订阅者,就会生成几个队列
import pika
credentials = pika.PlainCredentials("admin","admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('101.133.225.166',credentials=credentials))
channel = connection.channel()
# exchange='m1',exchange(秘书)的名称
# exchange_type='fanout' , 秘书工作方式将消息发送给所有的队列
channel.exchange_declare(exchange='m1',exchange_type='fanout')
# 随机生成一个队列,会自动生成一个随即名字的队列
result = channel.queue_declare(queue='',exclusive=True)
queue_name = result.method.queue
print(queue_name)
# 让exchange和queque进行绑定.
channel.queue_bind(exchange='m1',queue=queue_name)
def callback(ch, method, properties, body):
print("消费者接受到了任务: %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()
# exchange='m1',exchange(秘书)的名称
# exchange_type='fanout' , 秘书工作方式将消息发送给所有的队列
channel.exchange_declare(exchange='m1',exchange_type='fanout')
# 随机生成一个队列
result = channel.queue_declare(queue='',exclusive=True)
queue_name = result.method.queue
print(queue_name)
# 让exchange和queque进行绑定.
channel.queue_bind(exchange='m1',queue=queue_name)
def callback(ch, method, properties, body):
print("消费者接受到了任务: %r" % body)
channel.basic_consume(queue=queue_name,on_message_callback=callback,auto_ack=True)
channel.start_consuming()
九 路由模式routing(按关键字匹配)
# 按关键字匹配,direct
# 生产者
# 声明一个exchange叫m2,exchange_type是'direct'
channel.exchange_declare(exchange='m2',exchange_type='direct')
channel.basic_publish(exchange='m2',
routing_key='bnb', # 要指定消费者需要监听的key是什么
body='lqz bnb')
# 消费者
# 让exchange和queque进行绑定,绑定中写关键字参数routing_key
channel.queue_bind(exchange='m2',queue=queue_name,routing_key='nb')
# 应用场景:
对于不同级别日志来说,对于 error 级别的日志信息可能是我们需要特别关注的,会被单单独的消费者进行处理,此时交换机分发消息是有条件的进行分发,这个就是根据 Routing Key 进行不同的消息分发。
路由模式是一种精准的匹配,只有设置了 Routing Key 消息才能进行分发。
生产者
import pika
credentials = pika.PlainCredentials("admin","admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('8.130.125.9',credentials=credentials))
channel = connection.channel()
# 声明一个exchange叫m2,路由模式是direct模式,可以按关键字匹配
channel.exchange_declare(exchange='m2',exchange_type='direct')
# 发布消息
channel.basic_publish(exchange='m2',
routing_key='bnb',
body='lqz bnb')
connection.close()
消费者1
import pika
credentials = pika.PlainCredentials("admin","admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('8.130.125.9',credentials=credentials))
channel = connection.channel()
# exchange='m2',exchange(秘书)的名称
# exchange_type='direct' , 秘书工作方式将消息发送给所有的队列
channel.exchange_declare(exchange='m2',exchange_type='direct')
# 随机生成一个队列,自动生成一个随机名字的队列
result = channel.queue_declare(queue='',exclusive=True)
queue_name = result.method.queue
print(queue_name) # 随机队列名字是什么
# 让exchange和queque进行绑定,绑定中写关键字参数routing_key
# 只有消息是'nb'或'bnb'的消费者1才会去消费
channel.queue_bind(exchange='m2',queue=queue_name,routing_key='nb')
channel.queue_bind(exchange='m2',queue=queue_name,routing_key='bnb') # 可以监听多个:写两条
def callback(ch, method, properties, body):
print("消费者接受到了任务: %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('8.130.125.9',credentials=credentials))
channel = connection.channel()
# exchange='m2',exchange(秘书)的名称
# exchange_type='direct' , 秘书工作方式将消息发送给所有的队列
channel.exchange_declare(exchange='m2',exchange_type='direct')
# 随机生成一个队列,自动生成一个随机名字的队列
result = channel.queue_declare(queue='',exclusive=True)
queue_name = result.method.queue
print(queue_name) # 随机队列名字是什么
# 让exchange和queque进行绑定,绑定中写关键字参数routing_key
channel.queue_bind(exchange='m2',queue=queue_name,routing_key='bnb')
channel.queue_bind(exchange='m2',queue=queue_name,routing_key='nb') #
def callback(ch, method, properties, body):
print("消费者接受到了任务: %r" % body)
channel.basic_consume(queue=queue_name,on_message_callback=callback,auto_ack=True)
channel.start_consuming()
十 主题模式topic(按关键字模糊匹配)
# 按关键字模糊匹配
*只能加一个单词
#可以加任意单词字符
## 生产者
channel.exchange_declare(exchange='m3',exchange_type='topic')
# 发布消息
channel.basic_publish(exchange='m3',
# routing_key='lqz.nb', # lqz.#,lqz.* 两个都能匹配
routing_key='lqz.nb.bb', # 这个只有lqz.#的消费者可以匹配
body='lqz bnb')
# 消费者
# 让exchange和queque进行绑定.
channel.queue_bind(exchange='m3',queue=queue_name,routing_key='lqz.#')
# 应用场景
生产者
import pika
credentials = pika.PlainCredentials("admin","admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('8.130.125.9',credentials=credentials))
channel = connection.channel()
# 声明一个exchange叫m1,路由模式是direct模式,可以按关键字匹配
channel.exchange_declare(exchange='m3',exchange_type='topic')
# 发布消息
channel.basic_publish(exchange='m3',
routing_key='lqz.nb.bb',
body='lqz bnb')
connection.close()
消费者1
*
只能加一个单词#
可以加任意单词字符
import pika
credentials = pika.PlainCredentials("admin","admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('8.130.125.9',credentials=credentials))
channel = connection.channel()
# exchange='m1',exchange(秘书)的名称
# exchange_type='fanout' , 秘书工作方式将消息发送给所有的队列
channel.exchange_declare(exchange='m3',exchange_type='topic')
# 随机生成一个队列,自动生成一个随机名字的队列
result = channel.queue_declare(queue='',exclusive=True)
queue_name = result.method.queue
print(queue_name) # 随机队列名字是什么
# 让exchange和queque进行绑定.
channel.queue_bind(exchange='m3',queue=queue_name,routing_key='lqz.#')
def callback(ch, method, properties, body):
print("消费者接受到了任务: %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('8.130.125.9',credentials=credentials))
channel = connection.channel()
# exchange='m1',exchange(秘书)的名称
# exchange_type='fanout' , 秘书工作方式将消息发送给所有的队列
channel.exchange_declare(exchange='m3',exchange_type='topic')
# 随机生成一个队列,自动生成一个随机名字的队列
result = channel.queue_declare(queue='',exclusive=True)
queue_name = result.method.queue
print(queue_name) # 随机队列名字是什么
# 让exchange和queque进行绑定.
channel.queue_bind(exchange='m3',queue=queue_name,routing_key='lqz.*')
def callback(ch, method, properties, body):
print("消费者接受到了任务: %r" % body)
channel.basic_consume(queue=queue_name,on_message_callback=callback,auto_ack=True)
channel.start_consuming()
十一 rpc
# rpc:Remote Procedure Call 远程过程调用
也就是说两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据
调用远端的一个函数,跟调用本地函数一样
# 分布式的系统中使用
-微服务之间的调用(不同的服务可以使用不同的编程,一个用GO,一个用python)
-resful的接口
-rpc调用
客户端(A服务)
import pika
import uuid
class FibonacciRpcClient(object):
def __init__(self):
credentials = pika.PlainCredentials("admin", "admin")
self.connection = pika.BlockingConnection(pika.ConnectionParameters('8.130.125.9', credentials=credentials))
# 声明一个连接
self.channel = self.connection.channel()
# 随机生成一个消息队列(用于接收结果)--->队列名字随机,类似于订阅模式中的订阅者
result = self.channel.queue_declare(queue='',exclusive=True)
self.callback_queue = result.method.queue # 队列名字
# 监听消息队列中是否有值返回,如果有值则执行 on_response 函数(一旦有结果,则执行on_response)
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: # props.correlation_id 任务id
self.response = body # 把结果管道内的信息赋值给self.response
def call(self, n):
self.response = None
self.corr_id = str(uuid.uuid4())
# 客户端 给 服务端 发送一个任务: 任务id = corr_id / 任务内容 = '30' / 用于接收结果的队列名称
self.channel.basic_publish(exchange='',
routing_key='rpc_queue', # 服务端接收任务的队列名称
properties=pika.BasicProperties( # 消息持久化
reply_to = self.callback_queue, # 用于接收结果的队列
correlation_id = self.corr_id, # 任务ID
),
body=str(n))
while self.response is None:
self.connection.process_data_events()
return self.response
fibonacci_rpc = FibonacciRpcClient()
# response=fibonacci(50)
response = fibonacci_rpc.call(50)
print('返回结果:',response)
服务端(B服务)
import pika
credentials = pika.PlainCredentials("admin","admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('8.130.125.9',credentials=credentials))
channel = connection.channel()
# 监听任务队列
channel.queue_declare(queue='rpc_queue')
'''
回调函数的四个参数:
ch:
method
props
body:管道内存对象 内容相关信息
'''
# 回调函数
def on_request(ch, method, props, body):
n = int(body)
response = n + 100
# props.reply_to 要放结果的队列.
# props.correlation_id 任务
# 把返回的结果放到reply_to中
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) # 闲置消费
# 服务端监听rpc_queue队列,看里面有没有消息
channel.basic_consume(
queue='rpc_queue',
on_message_callback=on_request,)
channel.start_consuming()
微服务架构
# 注册中心
注册中心的作用一句话概括就是存放和调度服务。它记录了服务和服务地址的映射关系。只要启动一个服务,就去注册中心注册一下。当上层需要调用下层服务,或者下层调用其他下层服务时,就会在注册中心找服务的地址,从而调用。
# 配置中心
其中对配置进行管理
# 监控中心
监控服务的状态,监控日志
# 链路追踪
指记录一个请求的全部流程。通过这个方式可以很方便的知道请求在哪个环节出了故障,系统的瓶颈在哪儿。