【问题标题】:Python Flask API with stomp listener multiprocessing带有 stomp 监听器多处理的 Python Flask API
【发布时间】:2021-01-28 01:23:42
【问题描述】:

TLDR: 我需要为多处理设置一个烧瓶应用程序,以便 API 和 stomp 队列侦听器在单独的进程中运行,因此不会干扰彼此的操作。

详情: 我正在构建一个具有 API 端点的 python 烧瓶应用程序,并且还创建一个消息队列侦听器以使用 stomp 包连接到一个 activemq 队列。

我需要实现多处理,以便 API 和侦听器不会阻止彼此的操作。这样,API 将接受新请求,并且侦听器将继续侦听新消息并相应地执行任务。

代码的简化版本如下所示(为简洁起见,省略了一些细节)。

问题:多处理导致应用程序卡住。 worker 的 run 方法没有被一致地调用,因此监听器永远不会被创建。

# Start the worker as a subprocess -- this is not working -- app gets stuck before the worker's run method is called
m = Manager()
shared_state = m.dict()
worker = MyWorker(shared_state=shared_state)
worker.start()

经过几天的故障排除后,我怀疑问题是由于未正确设置多处理。我能够证明是这样,因为当我剥离所有多处理代码并直接调用工作者的 run 方法时,所有队列管理代码都正常工作,CustomWorker 模块创建侦听器,创建消息,并拿起消息。我认为这表明队列管理代码工作正常,问题的根源很可能是多处理造成的。

# Removing the multiprocessing and calling the worker's run method directly works without getting stuck so the issue is likely due to multiprocessing not being setup correctly
worker = MyWorker()
worker.run()

这是我目前的代码:

应用程序

这部分代码创建 API 并尝试创建一个新进程来创建队列侦听器。 'custom_worker_utils' 模块是一个自定义模块,它在 CustomWorker() 类的 run 方法中创建 stomp 监听器。

from flask import Flask, request, make_response, jsonify
from flask_restx import Resource, Api
import sys, os, logging, time
basedir = os.path.dirname(os.getcwd())

sys.path.append('..')

from custom_worker_utils.custom_worker_utils import *
from multiprocessing import Manager

# app.py
def create_app():
    app = Flask(__name__)
    app.config['BASE_DIR'] = basedir

    api = Api(app, version='1.0', title='MPS Worker', description='MPS Common Worker')
    logger = get_logger()

    '''
    This is a placeholder to trigger the sending of a message to the first queue
    '''
    @api.route('/initialapicall', endpoint="initialapicall", methods=['GET', 'POST', 'PUT', 'DELETE'])
    class InitialApiCall(Resource):
    
        #Sends a message to the queue
        def get(self, *args, **kwargs):
            mqconn = get_mq_connection()
            message = create_queue_message(initial_tracker_file)
            mqconn.send('/queue/test1', message,  headers = {"persistent":"true"})
            return make_response(jsonify({'message': 'Initial Test Call Worked!'}), 200)
    
    # Start the worker as a subprocess -- this is not working -- app gets stuck before the worker's run method is called
    m = Manager()
    shared_state = m.dict()
    worker = MyWorker(shared_state=shared_state)
    worker.start()

    # Removing the multiprocessing and calling the worker's run method directly works without getting stuck so the issue is likely due to multiprocessing not being setup correctly
    #worker = MyWorker()
    #worker.run()
    
    return app

自定义工作者工具

run() 方法被调用,连接到队列并使用 stomp 包创建监听器

# custom_worker_utils.py
from multiprocessing import Manager, Process
from _datetime import datetime
import os, time, json, stomp, requests, logging, random

'''
The listener
'''
class MyListener(stomp.ConnectionListener):

    def __init__(self, p):
        self.process = p
        self.logger = p.logger
        self.conn = p.mqconn
        self.conn.connect(_user, _password, wait=True)
        self.subscribe_to_queue()
                
    def on_message(self, headers, message):
        message_data = json.loads(message)
        ticket_id = message_data[constants.TICKET_ID]
        prev_status = message_data[constants.PREVIOUS_STEP_STATUS]
        task_name = message_data[constants.TASK_NAME]
        #Run the service
        if prev_status == "success":
            resp = self.process.do_task(ticket_id, task_name)
        elif hasattr(self, 'revert_task'):
            resp = self.process.revert_task(ticket_id, task_name)
        else:
            resp = True
        if (resp):
            self.logger.debug('Acknowledging')
            self.logger.debug(resp)
            self.conn.ack(headers['message-id'], self.process.conn_id)
        else:
            self.conn.nack(headers['message-id'], self.process.conn_id)

    def on_disconnected(self):
        self.conn.connect('admin', 'admin', wait=True)
        self.subscribe_to_queue()
        
    def subscribe_to_queue(self):
        queue = os.getenv('QUEUE_NAME')
        self.conn.subscribe(destination=queue, id=self.process.conn_id, ack='client-individual')
        

def get_mq_connection():
  conn = stomp.Connection([(_host, _port)], heartbeats=(4000, 4000))
  conn.connect(_user, _password, wait=True)
  return conn

class CustomWorker(Process):
    
    def __init__(self, **kwargs):
        super(CustomWorker, self).__init__()
        self.logger = logging.getLogger("Worker Log")
        log_level = os.getenv('LOG_LEVEL', 'WARN')
        self.logger.setLevel(log_level)
        self.mqconn = get_mq_connection()
        self.conn_id = random.randrange(1,100)
        for k, v in kwargs.items():
            setattr(self, k, v)

    def revert_task(self, ticket_id, task_name):
        # If the subclass does not implement this,
        # then there is nothing to undo so just return True
        return True

    def run(self):
        lst = MyListener(self)
        self.mqconn.set_listener('queue_listener', lst)
        while True:
            pass

【问题讨论】:

  • “我需要实现多处理,以便 API 和侦听器不会阻塞彼此的操作。” => 创建一个 Flask 程序来处理 API 请求。为您的听众创建一个完全独立的程序。这两个程序(或进程)应该通过套接字/管道进行通信,因为这看起来就像您设置的那样。基本上不要试图劫持烧瓶进程并让它产生额外的进程,就像你发现的那样,它不是为那样工作而设计的。
  • @steve 非常感谢!您能否提供更多有关进程应如何通过套接字/管道进行通信的信息?
  • 进程通过进程间通信 (IPC) 进行通信 查看 wiki/google 以获取有关不同类型 IPC 的更多信息。两种类型的 IPC 是套接字/管道。 ActiveMQ 是一种通过管道/套接字证明 IPC 的机制。基本上你想完全分离你的 Flask 代码和 stomp 监听器。它们应该是完全独立的程序,然后通过 IPC 进行通信。因此,您可以从命令行运行您的烧瓶应用程序。然后你会单独运行你的监听器。
  • 构建两个通过 API 通信的微服务

标签: python flask multiprocessing message-queue stomp


【解决方案1】:

似乎Celery 正是您所需要的。 Celery 是一个任务队列,可以跨工作进程甚至跨机器分配工作。 Miguel Grinberg created a great post about that,展示如何通过烧瓶接受任务并使用 Celery 作为任务生成它们。 祝你好运!

【讨论】:

    【解决方案2】:

    为了解决这个问题,我决定将 flask API 和消息队列侦听器作为两个完全独立的应用程序运行在同一个 docker 容器中。我已经安装并配置了 supervisord 以单独启动和进程。

    [supervisord]
    nodaemon=true
    logfile=/home/appuser/logs/supervisord.log
    
    [program:gunicorn]
    command=gunicorn -w 1 -c gunicorn.conf.py "app:create_app()" -b 0.0.0.0:8081 --timeout 10000
    directory=/home/appuser/app
    user=appuser
    autostart=true
    autorestart=true
    stdout_logfile=/home/appuser/logs/supervisord_worker_stdout.log
    stderr_logfile=/home/appuser/logs/supervisord_worker_stderr.log
    
    [program:mqlistener]
    command=python3 start_listener.py
    directory=/home/appuser/mqlistener
    user=appuser
    autostart=true
    autorestart=true
    stdout_logfile=/home/appuser/logs/supervisord_mqlistener_stdout.log
    stderr_logfile=/home/appuser/logs/supervisord_mqlistener_stderr.log
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多