tornado+gunicorn部署设置max_body_size

背景

想通过 gunicorn 的 max_requests 配置实现重启进程,解决一些内存泄露问题。

因为gunicorn启动配置的是 tornado.web.Application 实例,并非直接使用 tornado.httpserver.HTTPServer 导致无法设置 max_body_size 和 max_buffer_size 。

而 gunicore配置项 中只有 limit_request_line /limit_request_fields / limit_request_field_size 无法满足 tornado的限制配置。

现状

依赖版本如下:

  • Python3.9
  • tornado==6.5.2
  • gunicorn==23.0.0

app.py

python 复制代码
# 定义实例
app = tornado.web.Application(url_patterns)

gunicorn.conf.py

python 复制代码
# 因为用的docker部署有restart配置
daemon = False
# 不能设置为True, tornado的IOLoop不能被共享
preload_app = False
# 可以有效解决OOM的问题
max_requests = 102400
max_requests_jitter = 1024
# 连接的空闲时间(秒),对应 idle_connection_timeout=60
keepalive = 60
# 对应 body_timeout/请求处理超时
timeout = 60
# bind
# port = os.environ.get("PROJECT_PORT", 8000)
# bind = "0.0.0.0:{}".format(port)
worker_class = "tornado"

启动命令

复制代码
gunicorn -c gunicorn.conf.py --bind 0.0.0.0:8000 --workers 2 app:app

自定义 TornadoWorker

解决方式可以通过自定义 TornadoWorker . 代码是基于 gunicorn 源码修改的,主要修改 server_class 初始化的部分,增加了 max_body_size 相关配置。

最终的是同步修改 gunicore 启动 worker_class 配置:

python 复制代码
worker_class = "gunicorn_worker.MyTornadoWorker"

gunicorn_worker 代码实现如下:

python 复制代码
#!/usr/bin/env python
# coding=utf-8
import typing

from gunicorn.sock import ssl_context
from gunicorn.workers.gtornado import TornadoWorker

import tornado
from tornado.wsgi import WSGIContainer
from tornado.ioloop import IOLoop, PeriodicCallback

MAX_FILE_SIZE = 100 * 1024 * 1024  # 100MB

class MyTornadoWorker(TornadoWorker):
    """专门为tornado>=6定制
    因为需要实现设置 max_body_size
    """

    def run(self) -> None:
        self.ioloop = IOLoop.instance()
        self.alive = True
        self.server_alive = False

        # tornado >= 5
        self.callbacks = []
        self.callbacks.append(PeriodicCallback(self.watchdog, 1000))
        self.callbacks.append(PeriodicCallback(self.heartbeat, 1000))
        for callback in self.callbacks:
            callback.start()
        # Assume the app is a WSGI callable if its not an
        # instance of tornado.web.Application or is an
        # instance of tornado.wsgi.WSGIApplication
        app = self.wsgi

        if not isinstance(app, WSGIContainer) and not isinstance(app, tornado.web.Application):
            app = WSGIContainer(app)

        # Monkey-patching HTTPConnection.finish to count the
        # number of requests being handled by Tornado. This
        # will help gunicorn shutdown the worker if max_requests
        # is exceeded.
        httpserver = tornado.httpserver.HTTPServer
        if hasattr(httpserver, "HTTPConnection"):
            old_connection_finish = httpserver.HTTPConnection.finish

            def finish(other: typing.Any) -> None:
                self.handle_request()
                old_connection_finish(other)

            httpserver.HTTPConnection.finish = finish

            server_class = httpserver
        else:

            class _HTTPServer(tornado.httpserver.HTTPServer):
                def on_close(instance: typing.Any, server_conn: typing.Any) -> None:
                    self.handle_request()
                    super().on_close(server_conn)

            server_class = _HTTPServer

        if self.cfg.is_ssl:
            server = server_class(
                app,
                ssl_options=ssl_context(self.cfg),
                max_body_size=MAX_FILE_SIZE,
                max_buffer_size=MAX_FILE_SIZE,
            )
        else:
            server = server_class(
                app,
                max_body_size=MAX_FILE_SIZE,
                max_buffer_size=MAX_FILE_SIZE,
            )

        self.server = server
        self.server_alive = True

        for s in self.sockets:
            s.setblocking(0)
            if hasattr(server, "add_socket"):  # tornado > 2.0
                server.add_socket(s)
            elif hasattr(server, "_sockets"):  # tornado 2.0
                server._sockets[s.fileno()] = s

        server.no_keep_alive = self.cfg.keepalive <= 0
        server.start(num_processes=1)
        self.ioloop.start()
相关推荐
CCblandy20 分钟前
向量检索 ANN 参数实测:换一种数据分布,recall 掉了 51 个百分点
人工智能·python·算法·自然语言处理·性能优化
云和数据.ChenGuang1 小时前
print(f“a+b=“,result) print(f“a+b={result}“)区别
java·前端·人工智能·python·深度学习
2601_962885721 小时前
如何用 Python 算最大回撤和回撤恢复期?
java·数据库·python
Want5951 小时前
Python大雪纷飞①
开发语言·python·数学建模
程序员清风2 小时前
Python 数据可视化入门:Matplotlib 核心用法
python·信息可视化·matplotlib
溪语流沙2 小时前
【Web全栈进阶】前端对接新契约:信封解包 + 统一错误处理
前端·vue.js·python·状态模式·fastapi
Python图像识别2 小时前
56-【2027毕设】YOLO11X光安检危险物品检测系统 - Python完整源码+PyQt5界面+训练模型+数据集
python·qt·课程设计
winfredzhang2 小时前
从零实现“照片按地理位置显示在矢量地图上”:Flask + SQLite + Leaflet 全栈实战
python·sqlite·flask
外收内放3 小时前
Python基础语法练习题(72)
开发语言·python
171320330计算机毕设编程3 小时前
计算机毕业设计 基于Hadoop的新能源汽车发展前景分析 LW PPT
java·大数据·hadoop·python·算法·随机森林·线性回归