uvicorn中开启reload会忽略workers

/ 默认分类 / 0 条评论 / 971浏览

1.问题记录描述

python中使用fastapi+uvicorn构建web服务的时候,如果我们设置开启热更新之后,worker参数就会被忽略,无论怎样配置,开启的web进程也只有2个

if __name__ == '__main__':
    uvicorn.run(app='main:app', host="0.0.0.0", port=8000, reload=True, workers=4)

2.问题原因

当使用 uvicorn 启动 FastAPI 应用时,特别是设置 reload=True 的时候,uvicorn 只会启动两个进程:

这种方式确保开发过程中代码修改能自动重载,因此会看到两个进程在运行。这个行为在开发环境中是预期的,但在生产环境中,通常不需要启用 reload,这样只会有一个工作进程(如果没有指定workers数量)。

我们再从uvicorn的源码角度证明下这个问题:

1. uvicorn.run() 函数

首先,uvicorn.run() 函数是一个封装的入口函数,实质上调用了 uvicorn.Config 和 uvicorn.Server。

# uvicorn/__init__.py
def run(app, **kwargs):
    config = Config(app, **kwargs)
    server = Server(config)
    server.run()
2. Config 类

接下来查看 Config 类,尤其是 reload 和 workers 参数的处理。

# uvicorn/config.py
class Config:
    def __init__(self, app, host="127.0.0.1", port=8000, reload=False, workers=1, **kwargs):
        self.app = app
        self.host = host
        self.port = port
        self.reload = reload
        self.workers = workers
        # 其他初始化参数...
3. Server 类

Server 类负责运行服务器,查看其 run 方法:

# uvicorn/server.py
class Server:
    def __init__(self, config: Config):
        self.config = config
        self.should_exit = False

    def run(self, sockets=None):
        if self.config.should_reload:
            # 如果配置了 reload,则使用 'run' 方法中的 'run_reload'
            self.run_reload()
        elif self.config.workers > 1:
            # 否则,如果配置了多个 workers,则使用 'run' 方法中的 'run_multiple'
            self.run_multiple(sockets=sockets)
        else:
            # 默认的运行方式
            self.run_single(sockets=sockets)

    def run_reload(self):
        # reload 模式下的运行逻辑,启动一个监视文件变化的进程
        change_reload(config, target=run, sockets=sockets)

    def run_multiple(self, sockets=None):
        # 多个 workers 的运行逻辑
        sock = sockets[0] if sockets else None
        workers = []
        for _ in range(self.config.workers):
            pid = os.fork()
            if pid == 0:
                self.run_single(sockets=[sock])
                os._exit(0)
            else:
                workers.append(pid)
        self._run_with_reloader()
4. run_reload 方法

run_reload 方法启动一个进程监视文件变化,并重启工作进程:

# uvicorn/supervisors/basereload.py
class ChangeReload:
    def __init__(self, config: Config, target: Callable, sockets: Optional[List[socket.socket]] = None):
        self.config = config
        self.target = target
        self.sockets = sockets or []

    def run(self):
        logger = logging.getLogger("uvicorn.error")
        reloader_name = "watchgod"
        reloader = WatchGodReload(self.config, target=self.target, sockets=self.sockets)
        pid = os.fork()
        if pid == 0:
            # 在子进程中运行目标函数
            reloader.run()
            os._exit(0)
        else:
            # 在父进程中等待文件变化,重启子进程
            self.watch()

所以,从上述源码可以看出,当 reload=True 时,uvicorn 会调用 run_reload 方法,该方法只会启动一个监视文件变化的主进程和一个实际处理请求的工作进程。此时 workers 参数被忽略,无论设置多少个 workers,都会只启动这两个进程。