目录
1 背景需求
2 技术方案
2.1 消息队列
2.2 进程守护
3 源码介绍
3.1 supervisor部分
3.1.1 supervisord.conf 内容
3.1.2 MM3D.conf 和 MM3D_2.conf 内容
3.2 算法程序(也就是我的主函数)
某C端产品,前端嵌入式(安卓)将采集的数据发送给后端,后端服务器(Java)要负责将数据交到算法服务器(python,C++),算法服务器收到数据并处理完后将结果再返回给后端,后端拿着结果二次加工后再发给前端显示。
基本要求:
架构图如下:
消息队列(Message queue)原理比较简单(当然细节很多),主要作用就是把所有生产者的数据放到一个队列中,所有消费者从从这个队列里取,确保每个数据仅被消费一次,互相不冲突。
详细原理可参考:
消息队列(mq)是什么? - 知乎
什么是消息队列啊? - 知乎
RabbitMQ 入门系列(9)— Python 的 pika 库常用函数及参数说明_wohu1104的专栏-CSDN博客
进程守护的目的是让异常崩溃的程序能自动重启。
Supervisor是用Python开发的一套通用的进程管理程序,能将一个普通的命令行进程变为后台daemon,并监控进程状态,异常退出时能自动重启。
几个要点的解释:
更多信息,我看了比较好的参考如下(推荐级分先后顺序):
Supervisor使用详解 - 浪淘沙& - 博客园
详解Supervisor进程守护监控 - 请叫我头头哥 - 博客园
supervisor 使用详解_11111-CSDN博客_supervisor
算法服务器部分运行的逻辑是:
supervisor安装好后,配置文件一般放在/etc/supervisor文件夹内,里面有如下两个文件:
; supervisor config file
[unix_http_server]
file=/var/run/supervisor.sock ; (the path to the socket file)
chmod=0700 ; sockef file mode (default 0700)
[supervisord]
logfile=/var/log/supervisor/supervisord.log ; (main log file;default $CWD/supervisord.log)
pidfile=/var/run/supervisord.pid ; (supervisord pidfile;default supervisord.pid)
childlogdir=/var/log/supervisor ; ('AUTO' child log dir, default $TEMP)
; the below section must remain in the config file for RPC
; (supervisorctl/web interface) to work, additional interfaces may be
; added by defining them in separate rpcinterface: sections
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
[supervisorctl]
serverurl=unix:///var/run/supervisor.sock ; use a unix:// URL for a unix socket
; The [include] section can just contain the "files" setting. This
; setting can list multiple files (separated by whitespace or
; newlines). It can also contain wildcards. The filenames are
; interpreted as relative to this file. Included files *cannot*
; include files themselves.
[include]
files = /etc/supervisor/conf.d/*.conf
MM3D.conf内容如下:
[program:MM3D]
directory=/mm3d/RV2_magic_mirror_algo/MM3D
command=sh /mm3d/RV2_magic_mirror_algo/MM3D/excute.sh
autostart=true
autorestart=true
startretries=100
redirect_stderr=true
stdout_logfile=/mm3d/RV2_magic_mirror_algo/MM3D/supervisor_out.log
MM3D_2.conf内容如下:
[program:MM3D_2]
directory=/mm3d/RV2_magic_mirror_algo/MM3D
command=sh /mm3d/RV2_magic_mirror_algo/MM3D/excute.sh
autostart=true
autorestart=true
startretries=100
redirect_stderr=true
stdout_logfile=/mm3d/RV2_magic_mirror_algo/MM3D/supervisor_2out.log
注意:conf文件中,比较重要的参数感觉有两个:
算法程序主要包括两块:
代码如下:
(config_MQ.py就省略了,里面是一些SDK、模型等地址,以及MQ的IP地址和密码等)
import os
import numpy as np
from pathlib import Path
from config_MQ import Config
import time
from loguru import logger
import sys
import pika
from ftplib import FTP
import json
def ftp_connect():
try:
"""连接ftp:return:"""
ftp = FTP()
logger.debug('config.ftp_host: {}', config.ftp_host)
logger.debug('config.ftp_port: {}', config.ftp_port)
ftp.connect(config.ftp_host, config.ftp_port) # 连接远程服务器IP地址
ftp.encoding = 'utf-8'
ftp.set_debuglevel(1) # 不开启调试模式
ftp.login(config.ftp_user, config.ftp_pwd) # 登录ftp
# print(ftp.getwelcome()) # ftp服务器欢迎语
except Exception as e:
#print(e)
logger.exception('ftp_connect error: {}', e)
return None
else:
return ftp
def read_file(file_path, target_dir, filename):
ftp = ftp_connect() # 连接ftp
# ftp服务器上文件的路径
# 本地文件下载保存的路径
# 本地文件下载写入的路径文件
# writefile = '%s/%s' % (write_path, filename)
write_path = target_dir + '/%s' % (filename + '.zip')
with open(write_path, "wb") as f:
ftp.retrbinary('RETR %s' % file_path, f.write)
ftp.close();
def callbackTry(ch, method, properties, body):
print(body.decode())
ch.basic_ack(delivery_tag=method.delivery_tag)
## 拿到消息转json
bodyJson = json.loads(body.decode())
filepath = bodyJson['filepath']
user_id = bodyJson['keypair']
callback_url = bodyJson['callbackUrl'] # 回调云端地址
sample_raw_dir = os.path.join(raw_data_root, user_id) #../../MM3D_RAW/B16XXXXXXXX
sample_result_dir = os.path.join(result_data_root, user_id) # ../../MM3D_Result/B16XXXXXXX
# 拿到ftp url下载文件并保存sample_raw_dir
if not os.path.isdir(sample_raw_dir):
try:
os.mkdir(sample_raw_dir)
except Exception as e:
logger.exception('Fail to mkdir to raw data: {}', e)
#print('Fail to mkdir to raw data', e)
if not os.path.isdir(sample_result_dir):
try:
os.mkdir(sample_result_dir)
except Exception as e:
logger.exception('Fail to mkdir to result data: {}', e)
#print('Fail to mkdir to result data', e)
try:
# zip_file = user_id + '.zip'
# file.save(os.path.join(sample_raw_dir, zip_file))
read_file(filepath, sample_raw_dir, user_id) #通过FTP拉取数据包并保存在本地
except Exception as e:
logger.exception('Fail to save raw data: {}', e)
#print("Fail to save raw data", e)
start_time = int(round(time.time() * 1000))
sample_key_pair = sample_raw_dir.split('/')[-1]
# 识别文件的路劲
logger.debug("sample_raw_dir :{}", sample_raw_dir)
logger.debug("callback_url :{}", callback_url)
############################ 算法部分 ############################
## TODO 调用算法程序识别
def callback(ch, method, properties, body):
try:
callbackTry(ch, method, properties, body)
except Exception as e:
logger.exception('algo error: {}', e)
#print("algo error:", e)
def init_rabbitmq():
# 创建连接时的登录凭证。 username: MQ 账号, password: MQ 密码
credentials = pika.PlainCredentials(config.rabbitmq_user, config.rabbitmq_pwd)
# 阻塞式连接 MQ
# parameters: 连接参数(包含主机/端口/虚拟主机/账号/密码等凭证信息)
connection = pika.BlockingConnection(
pika.ConnectionParameters(host=config.rabbitmq_host, port=config.rabbitmq_port, virtual_host='/',
credentials=credentials))
# 获取与 rabbitmq 通信的通道
channel = connection.channel()
# 声明交换器
exchange = "algoExchange"
channel.exchange_declare(exchange=exchange, durable=True, exchange_type='topic')
# 声明队列
ex_queue = "algoExchange_queue"
channel.queue_declare(queue=ex_queue, durable=True, auto_delete=True)
# 通过路由键将队列和交换器绑定
channel.queue_bind(exchange=exchange, queue=ex_queue, routing_key='algoConfigRoutingKey')
# 从队列中拿到消息开始消费
#(当要消费时,调用该回调函数 callback)
channel.basic_consume(ex_queue, callback,
auto_ack=True) # auto_ack设置成 False,在调用callback函数时,未收到确认标识,消息会重回队列。True,无论调用callback成功与否,消息都被消费掉
# 处理 I/O 事件和 basic_consume 的回调, 直到所有的消费者被取消
# (开始循环,直到发送退出消息)
channel.start_consuming()
if __name__ == "__main__":
'''configue logger rotation="00:00:00",'''
logger.add('../log/log-{time:YYYY-MM-DD}-PID='+ str(os.getpid()) +'.log', level="DEBUG",encoding="utf-8", colorize=True, format="{time} {message} " )
config = Config()
raw_data_root = config.raw_data_root
result_data_root = config.result_data_root
raw_data_backup_root = config.raw_data_backup
raw_data_backup_root_path = Path(raw_data_backup_root)
if not raw_data_backup_root_path.is_dir():
os.mkdir(config.raw_data_backup)
############################ 算法初始化部分 ############################
## TODO 调用初始化
############################ rabbitmq部分 ############################
init_rabbitmq()