Python 全栈系列236 rabbit_agent搭建

说明

通过rabbit_agent, 以接口方式实现对队列的标准操作,将pika包在微服务内,而不必在太多地方重复的去写。至少在服务端发布消息时,不必再去考虑这些问题。

在分布式任务的情况下,客户端本身会启动一个持续监听队列的客户端服务,这些应该是容易通过简单的配置来实现的。

在未来的应用上,我计划使用rabbitmq作为公网的消息队列,满足分布式计算的要求。例如,我部署了n个大模型,希望它们可以处理接口请求。很显然,一台服务器放不下n个大模型,但是用户可以把请求先发到消息队列,然后在不同的机器上启动大模型,分别接受来自队列的消息,处理后返回。
这样,只要在服务端有一个前端,可以转发、收集消息展示在应用前端,那么就可行了。

内容

模式1:简单队列,连通性测试

适合简单缓存

有P(Produce)和C(Consumer)两端。
在这里插入图片描述
生产端在建立连接后,声明队列,然后往里面发消息。
Connection -> Channel -> Queue -> Message

生产

生产者:将消息发送到队列。
模式:只有在有新的消息要发布时才连接队列。(然后就可以释放连接)

import pika
credentials = pika.PlainCredentials('xxx', 'xxx')
import time
with pika.BlockingConnection(pika.ConnectionParameters('HOST', 11111, '/', credentials)) as connection:channel = connection.channel()channel.queue_declare(queue='hello')# 方式一:基本队列for i in range(100):time.sleep(0.1)channel.basic_publish(exchange='',routing_key='hello',body='Hello World!')print(" [x] Sent 'Hello World!'")
消费

消费者:将消息提取出来并打印。
模式:一直处于监听状态,所以连接需要一直保持。

def callback(ch, method, properties, body):print(f" [x] Received {body}")connection = pika.BlockingConnection(pika.ConnectionParameters('HOST', 11111, '/', credentials))
channel = connection.channel()
channel.queue_declare(queue='hello')
# 方式一:基本队列
channel.basic_consume(queue='hello',auto_ack=True,on_message_callback=callback)print(' [*] Waiting for messages. To exit press CTRL+C')
channel.start_consuming()

模式2:工作队列,区分消费者

适合分布式任务

在这里插入图片描述

在这里插入图片描述
这个模式下,稍微有点复杂。

简单模式生产者:

import sys
message = ' '.join(sys.argv[1:]) or "Hello World!"
channel.basic_publish(exchange='',routing_key='hello',body=message)
print(f" [x] Sent {message}")

如果这时候rabbitmq挂了,那么数据就会丢失,这时候要在声明队列时声明持久化的。但这要求队列在一开始就声明是持久化的。如果队列一开始没声明,再次声明会报错。
同时在发布消息的时候,也要声明持久化

channel.queue_declare(queue='hello', durable=True)
channel.basic_publish(exchange='',routing_key="hello1",body=message,properties=pika.BasicProperties(delivery_mode = pika.DeliveryMode.Persistent))

配置完重启服务测试

docker restart rabbitmq_24091_24092

在这里插入图片描述
ok,生产端的消息被持久化了,即使重启消息也没有丢失。

接下来就是客户端。也就是worker。
考虑到worker同样存在不可靠的情况,有可能消息消费到一半,然后worker挂了。所以这里主要是消息的应答机制。

默认情况下,worker采用自动应答机制。即获取消息就认为被正常消费。这适用于产品的稳定性很高,或者消息的重要性很低的情况(允许漏消息)。

def callback(ch, method, properties, body):print(f" [x] Received {body.decode()}")time.sleep(body.count(b'.'))print(" [x] Done")
# 方式一:基本队列
channel.basic_consume(queue='hello',on_message_callback=callback, auto_ack =True)    

如果要做更可靠的确认,可以采取这种手工应答的机制。即消费时声明不自动确认,然后在callback内部确认。

# 手动确认
def callback(ch, method, properties, body):print(f" [x] Received {body.decode()}")time.sleep(body.count(b'.'))print(" [x] Done")ch.basic_ack(delivery_tag = method.delivery_tag)
channel.basic_consume(queue='hello',on_message_callback=callback, auto_ack =False)    print(' [*] Waiting for messages. To exit press CTRL+C')
channel.start_consuming()

最后是负载均衡,在这里,通过消费者声明自己的预取数量来完成。

channel.basic_qos(prefetch_count=3)
channel.basic_consume(queue='hello1',on_message_callback=callback, auto_ack =False)    

3 广播

我认为在复杂决策场景下可以用到。
在这里插入图片描述

emit_log.py

channel = connection.channel()channel.exchange_declare(exchange='logs', exchange_type='fanout')message = ' '.join(sys.argv[1:]) or "info: Hello World!"
channel.basic_publish(exchange='logs', routing_key='', body=message)
print(f" [x] Sent {message}")
connection.close()'''
如您所见,建立连接后我们声明了交换。此步骤是必要的,因为禁止发布到不存在的交易所。如果还没有队列绑定到交换器,消息将会丢失,但这对我们来说没关系;如果还没有消费者在监听,我们可以安全地丢弃该消息。python3 emit_log.py First message.
'''

receive_logs1.py

channel = connection.channel()channel.exchange_declare(exchange='logs', exchange_type='fanout')result = channel.queue_declare(queue='', exclusive=True)
queue_name = result.method.queuechannel.queue_bind(exchange='logs', queue=queue_name)print(' [*] Waiting for logs. To exit press CTRL+C')def callback(ch, method, properties, body):print(f" [x] {body}")channel.basic_consume(queue=queue_name, on_message_callback=callback, auto_ack=True)channel.start_consuming()'''python3 receive_logs1.py >> logs_from_rabbit1.log
python3 receive_logs2.py >> logs_from_rabbit2.log
'''

在两个终端分别执行

python3 receive_logs1.py >> logs_from_rabbit1.log
python3 receive_logs2.py >> logs_from_rabbit2.log

在这里插入图片描述
当关闭后,数据被写入日志
在这里插入图片描述
队列被自动删除
在这里插入图片描述

4 路由

fanout是无意识转发,direct可以通过不同的路由键值决定队列分发,或者消息丢弃(如严重程度低的)。这种过滤和转发是通过路由键来确定的 routing_key
在这里插入图片描述

5 主题

有点像正则,实现更复杂的过滤。
在这里插入图片描述

5 微服务

命名为rabbit_agent_24098,第一步先实现模式二(worker)和模式三(subscribe)

先获取到基本包,并安装,能省不少事

wget  Basefuncs-1.2-py3-none-any.whl 
pip install Basefuncs-1.2-py3-none-any.whl

然后是简单的server_funcs.py,在server_funcs里定义了两个基础文件夹(现在看来也不是特别需要)

# 【创建tornado所需问文件夹】
import os
# 如果路径不存在则创建
def create_folder_if_notexist(somepath):if not os.path.exists(somepath):os.makedirs(somepath)return Truem_static = os.path.join(os.getcwd(),'m_static')
m_template = os.path.join(os.getcwd(),'m_template')create_folder_if_notexist(m_static)
create_folder_if_notexist(m_template)settings = {
'static_path':m_static,
'template_path':m_template
}# 如果需要序列化含np的内容
import json
from json import JSONEncoder
class MyEncoder(JSONEncoder):def default(self, obj):if isinstance(obj, np.integer):return int(obj)elif isinstance(obj, np.floating):return float(obj)elif isinstance(obj, np.ndarray):return obj.tolist()if isinstance(obj, datetime):return obj.__str__()if isinstance(obj, dd.timedelta):return obj.__str__()else:return super(MyEncoder, self).default(obj)# json.dumps(foo, cls=MyJsonEncoder)from Basefuncs import * 
# 读取配置
conf_dict = get_conf_dict('configs.conf')

服务端:

from server_funcs import *
import tornado.httpserver  # http服务器
import tornado.ioloop  # ?
import tornado.options  # 指定服务端口和路径解析
import tornado.web  # web模块
from tornado.options import define, options
import os.path  # 获取和生成template文件路径import pika
import json
# 全局配置文件
# rabbit01 = conf_dict['rabbit01']# 应用列表
app_list = []IndexHandler_path = r'/'
class IndexHandler(tornado.web.RequestHandler):def get(self):self.write('【GET】This is Website for Internal API System')self.write('Please Refer to API document')print('Get got a request test')# print(buffer_dict)def post(self):request_body = self.request.bodyprint('Trying Decode Json')some_dict = json.loads(request_body)print(some_dict)msg_dict = {}msg_dict['info'] = '【POST】This is Website for Internal API System'msg_dict['input_dict'] = some_dictself.write(json.dumps(msg_dict))print('Post got a request test')
IndexHandler_tuple = (IndexHandler_path,IndexHandler)
app_list.append(IndexHandler_tuple)# 发布消息:传入对应的队列服务器名称,获取对应的服务器配置,然后进行连接并发布消息
## 模式二:Work Queues
WorkQMessageHandler_path = r'/send_workq_message/'
class WorkQMessageHandler(tornado.web.RequestHandler):def post(self):request_body = self.request.bodysome_dict = json.loads(request_body)# 1 第一层rabbit = some_dict['rabbit']exchange = some_dict.get('exchange') or ''exchange_type = some_dict.get('exchange_type') or ''# queue不可缺少queue = some_dict['queue']durable = some_dict.get('durable') if durable is None:durable = True routing_key = some_dict.get('routing_key') or ''message_list = some_dict['message_list']print(some_dict)the_rabbit_conf_dict = conf_dict[rabbit]credentials = pika.PlainCredentials(the_rabbit_conf_dict['user'], the_rabbit_conf_dict['pwd'])msg_dict = {}with pika.BlockingConnection(pika.ConnectionParameters(the_rabbit_conf_dict['host'], the_rabbit_conf_dict['port'], '/', credentials)) as connection:channel = connection.channel()if len(exchange.strip())>1:channel.exchange_declare(exchange=exchange, exchange_type=exchange_type)# 队列的持久化与否要一开始设置好if durable is True:channel.queue_declare(queue=queue, durable=True)for message in message_list:print('a :',message )channel.basic_publish(exchange= exchange, routing_key=routing_key, body=json.dumps(message),properties=pika.BasicProperties(delivery_mode = pika.DeliveryMode.Persistent))msg_dict['durable'] = Truemsg_dict['status'] = Trueelse:channel.queue_declare(queue=queue)for message in message_list:print('b :',message )channel.basic_publish(exchange= exchange, routing_key=routing_key, body=json.dumps(message) )msg_dict['durable'] = Falsemsg_dict['status'] = Truemsg_dict['messages'] = len(message_list)self.write(json.dumps(msg_dict))
WorkQMessageHandler_tuple = (WorkQMessageHandler_path,WorkQMessageHandler)
app_list.append(WorkQMessageHandler_tuple)## 模式三:Publish/Subscribe
## 模式二:Work Queues
SubscribeMessageHandler_path = r'/send_subscribe_message/'
class SubscribeMessageHandler(tornado.web.RequestHandler):def post(self):request_body = self.request.bodysome_dict = json.loads(request_body)# 1 第一层rabbit = some_dict['rabbit']exchange = some_dict.get('exchange') or ''exchange_type = some_dict.get('exchange_type') or ''routing_key = some_dict.get('routing_key') or ''message_list = some_dict['message_list']print(some_dict)the_rabbit_conf_dict = conf_dict[rabbit]credentials = pika.PlainCredentials(the_rabbit_conf_dict['user'], the_rabbit_conf_dict['pwd'])msg_dict = {}with pika.BlockingConnection(pika.ConnectionParameters(the_rabbit_conf_dict['host'], the_rabbit_conf_dict['port'], '/', credentials)) as connection:channel = connection.channel()if len(exchange.strip())>1:channel.exchange_declare(exchange=exchange, exchange_type=exchange_type)for message in message_list:channel.basic_publish(exchange= exchange, routing_key=routing_key, body=json.dumps(message))            SubscribeMessageHandler_tuple = (SubscribeMessageHandler_path,SubscribeMessageHandler)
app_list.append(SubscribeMessageHandler_tuple)if __name__ == '__main__':#tornado.options.parse_command_line()apps = tornado.web.Application(app_list, **settings)http_server = tornado.httpserver.HTTPServer(apps)define('port', default=8000, help='run on the given port', type=int)http_server.listen(options.port)# 单核# 多核打开注释# 0 是全部核# http_server.start(num_processes=10) # tornado将按照cpu核数来fork进程# ---启动print('Server Started')tornado.ioloop.IOLoop.instance().start()

本地开发测试完之后,发布为镜像,然后启动服务。

docker run -d \--restart=always \--name=rabbit_agent_24098 \-v /etc/localtime:/etc/localtime  \-v /etc/timezone:/etc/timezone\-v /etc/hostname:/etc/hostname\-e "LANG=C.UTF-8" \-w /workspace\-p 24098:8000\myregistry.domain.com:24052/server.andy.rabbit_agent_24098:v100 \sh -c "python3 server.py"

模式二测试:WorkerQ

在生产端发送消息。声明了一个不持久化的队列,然后发送消息列表。注意:如果生产端声明非持久队列,那么消费端也要做同样的声明。否则会出现声明错误。另,如果消息ACK失败,RabbitMQ会在TTL之后将消息放回队列。如果消费者的通道断开连接,那么RabbitMQ也会将消息放回队列。

import requests as req 
message_list = [{'msg_id':1,'msg':'first msg'},{'msg_id':2,'msg':'second msg'}]# 1 模式2 WorkQ:服务端发送消息
para_dict = {}
para_dict['rabbit'] = 'rabbit01'
para_dict['routing_key'] = 'hello2'
para_dict['durable'] = False
para_dict['message_list'] = message_list
para_dict['queue'] = 'hello2'# res = req.post('http://127.0.0.1:8000/send_workq_message/', json = para_dict)
res = req.post('http://WAN_IP:24098/send_workq_message/', json = para_dict)

在消费端执行消费。默认的情况下,body里存放的是二进制字符串。以下采取了自动和手动方式进行消息确认。

import pika
import json
credentials = pika.PlainCredentials('xxx', 'xxx')
connection = pika.BlockingConnection(pika.ConnectionParameters('HOST', PORT, '/', credentials))channel = connection.channel()import time# 自动确认
# def callback(ch, method, properties, body):
#     print(f" [x] Received {body.decode()}")
#     time.sleep(body.count(b'.'))
#     print(" [x] Done")
# # 方式一:基本队列
# channel.basic_consume(queue='hello',
#                         on_message_callback=callback, auto_ack =True)    # 手动确认
# def callback(ch, method, properties, body):
#     input_data = json.loads(body.decode())
#     print(f" [x] Received ",input_data)
#     # time.sleep(body.count(b'.'))
#     print(" [x] Done")
#     ch.basic_ack(delivery_tag = method.delivery_tag)
def callback(ch, method, properties, body):# input_data = json.loads(body.decode())print(f" [x] Received ",body.decode())# time.sleep(body.count(b'.'))print(" [x] Done")ch.basic_ack(delivery_tag = method.delivery_tag)# channel.queue_declare(queue='hello1')
channel.queue_declare(queue='hello1',durable=True)
channel.basic_qos(prefetch_count=3)
channel.basic_consume(queue='hello1',on_message_callback=callback, auto_ack =False)    print(' [*] Waiting for messages. To exit press CTRL+C')
channel.start_consuming()

模式三测试:订阅模式

para_dict = {}
para_dict['rabbit'] = 'rabbit01'
para_dict['routing_key'] = None
para_dict['durable'] = False
para_dict['message_list'] = message_list
para_dict['exchange'] = 'logs'
para_dict['exchange_type'] = 'fanout'res = req.post('http://127.0.0.1:8000/send_subscribe_message/', json = para_dict)

订阅的worker用了另一种形式:使用系统分配的默认队列,使用完之后自动删除

#!/usr/bin/env python
import pika
credentials = pika.PlainCredentials(xxx, xxx)
connection = pika.BlockingConnection(pika.ConnectionParameters(WAN_IP, PORT, '/', credentials))channel = connection.channel()channel.exchange_declare(exchange='logs', exchange_type='fanout')result = channel.queue_declare(queue='', exclusive=True)
queue_name = result.method.queuechannel.queue_bind(exchange='logs', queue=queue_name)print(' [*] Waiting for logs. To exit press CTRL+C')def callback(ch, method, properties, body):input_data = json.loads(body.decode())print(f" [x] ",input_data)channel.basic_consume(queue=queue_name, on_message_callback=callback, auto_ack=True)channel.start_consuming()

6 结束

到这里第一版就算完成了,可以开始先用RabbitMQ做一些应用。

目前能想到的是用于分布式任务,队列中存放任务的元信息。Worker可以通过直接或者间接方式取数。
直接方式是指worker直接发起数据库的拉取动作,获得数据然后执行。
间接方式则是worker向指定队列反馈消息,由另一个服务来分发数据文件(针对租用算力机没有额外端口的情况)

在应用上,可以

  • 1 为任务搭建具有前端的微服务,数据量不大,可以通过RabbitMQ直接传数据
  • 2 接受来自量化程序的交易消息

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/768329.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Django(一)- 环境搭建和快速入门

一、搭建环境 1、创建Python虚拟环境 (base) C:\Users\35351>conda create -n django_study python3.9 2、安装Django (django_study) C:\Users\35351>pip install Django >> 查看安装版本 (django_study) C:\Users\35351>python -m django --version 3、安…

数据分析与挖掘

数据起源: 规模庞大,结构复杂,难以通过现有商业工具和技术在可容忍的时间内获取、管理和处理的数据集。具有5V特性:数量(Volume):数据量大、多样性(Variety)&#xff1a…

CSS(二)

一、CSS 的复合选择器 1.1 什么是复合选择器 在 CSS 中,可以根据选择器的类型把选择器分为基础选择器和复合选择器,复合选择器是建立在基础选择器之上,对基本选择器进行组合形成的。 复合选择器可以更准确、更高效的选择目标元素&#xff…

OC对象 - 关联对象(如何给分类添加成员变量)

文章目录 OC对象 - 关联对象(如何给分类添加成员变量)1. 基本使用1.1 提供的API1.1.1 添加关联对象1.1.2 获得关联对象1.1.3 移除所有关联对象1.1.3 修饰符 1.2 使用方法1.2 Key的常见用法1.2.1 使用的get方法的selecor作为key1.2.2 使用指针的地址作为k…

LeetCode每日一题——移除链表元素

移除链表元素OJ链接:203. 移除链表元素 - 力扣(LeetCode) 题目: 思路: 这与之前的移除元素的题目很相似,那么我们同样可以用类似的做法(双指针)进行解题。但是这是一个链表删除&a…

树状数组原理和代码

树状数组 求下标的对应 求i管着的下标的范围 方法:拆掉最右侧的1然后1 到你自己 query sum 1-i的和 拆掉最右侧的1 再把下一个数值吸收到sum 重复这个过程直到全变0为止 add 方法:加上最右侧的1 到上限为止 lowbit方法 单点增加范围查询模板 #inc…

no main manifest attribute, in xxx.jar

找不到主类&#xff0c;如果是maven 项目&#xff0c;在pom.xml 指定主类 <mainClass>com.example.demo.Demo2Application</mainClass>还是不行的话&#xff0c;把 <skip>true</skip>去掉

nodejs+vue高校师资管理系统python-flask-django-php

快速发展的社会中&#xff0c;人们的生活水平都在提高&#xff0c;生活节奏也在逐渐加快。为了节省时间和提高工作效率&#xff0c;越来越多的人选择利用互联网进行线上打理各种事务&#xff0c;然后线上管理系统也就相继涌现。与此同时&#xff0c;人们开始接受方便的生活方式…

从FasterTransformer源码解读开始了解大模型(1.0)了解FasterTransformer

从FasterTransformer源码解读开始了解大模型&#xff08;1.0&#xff09;了解FasterTransformer 写在前面的话 最近的一年时间真是令人感慨&#xff0c;换了个工作方向&#xff0c;学了些深度算子开发相关知识&#xff0c;工作也转到对LLM的学习和开发了。入行不算深&#xf…

谷粒商城——缓存的概念

1. 使用缓存的好处&#xff1a;减少数据库的访问频率&#xff0c;提高用户获取数据的速度。 2. 什么样的数据适合存储到缓存中&#xff1f; ①及时性、数据一致性要求不高的数据&#xff0c;例如物流信息、商品类目信息 ②访问量大更新频率不高的数据(读多、写少) 3. 读模式…

加密技术概述

传输数据时的四个问题 窃听 数字加密 假冒 消息认证或数字签名 篡改 消息认证码或数字签名 事后否认 数字签名 加密技术 将数据变成第三者的计算机无法理解的形式&#xff0c;然后再将其恢复成原本数据的一系列操作就是加密技术。 哈希函数 哈希函数可以把给定的数据转…

设计模式之状态模式(一)

设计模式专栏&#xff1a; http://t.csdnimg.cn/4Mt4u 目录 1.概述 2.结构 3.实现 4.总结 1.概述 状态模式( State Pattern)也称为状态机模式( State Machine pattern), 是允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类, 属于行为型模式。 在状…

Vue3+echarts绘制世界地图

先放效果图 之前所查找的资料都没有讲清楚如何引入地图文件并绘制地图&#xff0c;下面做一个记录。 首先下载对应的地图json文件&#xff0c;这里可以参考我的这篇文章&#xff0c;提供了下载地址&#xff1a;记录echarts各种地图json文件下载地址-CSDN博客 第二步&#xff…

笔记本和台式机主板内部结构分析

笔记本和态势机主板内存接口以及配件安装位置 笔记本主板 1 以thinkpad L-490为例,使用拆机小工具拆机&#xff0c;打开后面板&#xff0c;内部结构示意图如下 台式机主板 以技嘉-B660M-AORUS-PRO-AX型号主板为例 笔记本电脑和台式机电脑的相同之处 CPU&#xff1a;笔记本…

【boost_search搜索引擎】1.获取数据源

boost搜索引擎 1、项目介绍2、获取数据源 1、项目介绍 boost_search项目和百度那种不一样&#xff0c;百度是全站搜索&#xff0c;而boost_search是一个站内搜索。而项目的宏观上实现思路就如同图上的思路。 2、获取数据源 我们要实现一个站内搜索&#xff0c;我们就要有这…

Rust 程序设计语言学习——结构体

结构体和元组类似&#xff0c;它们都包含多个相关的值。和元组一样&#xff0c;结构体的每一部分可以是不同类型。但不同于元组&#xff0c;结构体需要命名各部分数据以便能清楚的表明其值的意义。由于有了这些名字&#xff0c;结构体比元组更灵活&#xff1a;不需要依赖顺序来…

医院预约挂号系统设计与实现|jsp+ Mysql+Java+ Tomcat(可运行源码+数据库+设计文档)

本项目包含可运行源码数据库LW&#xff0c;文末可获取本项目的所有资料。 推荐阅读100套最新项目 最新ssmjava项目文档视频演示可运行源码分享 最新jspjava项目文档视频演示可运行源码分享 最新Spring Boot项目文档视频演示可运行源码分享 2024年56套包含java&#xff0c;…

初识kafka-数据存储篇1

目录 背景 1 kafka总体体系结构 2 疑问解答 2.1 高吞吐低延迟 2.2 实现分布式存储和数据读取 2.3 如何保证数据不丢失 背景 最近在和产品过项目审批的时候&#xff0c;深刻感受到业务方对系统的时时响应提出了更高的要求。目前手上大部分的业务都是基础定时任务去实现的&…

nodejs+vue高校会议室预订管理系统python-flask-django-php

伴随着我国社会的发展&#xff0c;人民生活质量日益提高。于是对系统进行规范而严格是十分有必要的&#xff0c;所以许许多多的信息管理系统应运而生。此时单靠人力应对这些事务就显得有些力不从心了。所以本论文将设计一套高校会议室预订管理系统&#xff0c;帮助学校进行会议…

JDK,JRE,JVM之间的关系

他们明面上的关系是JDK包含JRE&#xff0c;JRE包含JVM。 简单理解JDK就是Java开发工具包。JRE是Java运行环境。JVM是Java虚拟机。 JDK是面向开发者的&#xff0c;JRE是面向JAVA程序的用户的。也就是说开发者开发JAVA程序是需要用到JDK&#xff0c;如果用户不去开发JAVA程序&am…