新闻中心
在FastAPI中异步管理和监控外部服务的启动与关闭

本文详细阐述了如何在fastapi应用中异步启动、监控并优雅地关闭外部服务,例如j*a服务。通过利用`asyncio.subprocessprotocol`捕获子进程日志,并结合`asyncio.future`实现服务启动和退出的精确信号通知,解决了传统`subprocess`阻塞和异步子进程无法等待启动完成的问题。文章推荐使用fastapi的`lifespan`事件管理器,提供了一个健壮且专业的解决方案,确保外部服务与fastapi应用生命周期同步。
在现代微服务架构中,一个应用经常需要协同多个外部服务。例如,一个Python FastAPI服务可能需要启动并与一个J*a服务通过HTTP进行通信。管理这些外部服务的生命周期,特别是确保它们正确启动和关闭,是构建健壮系统面临的一个挑战。本文将深入探讨如何使用FastAPI和Python的asyncio库,特别是asyncio.SubprocessProtocol,来异步地启动、监控并优雅地关闭外部服务。
1. 挑战与传统方法的局限性
在Python中启动外部进程最直接的方式是使用subprocess模块。然而,subprocess.run()是阻塞的,它会暂停主程序的执行直到子进程完成,这对于需要长时间运行的外部服务来说是不可接受的。
为了解决阻塞问题,asyncio.subprocess_shell或asyncio.create_subprocess_shell提供了异步启动子进程的能力。但单纯地启动一个子进程并不意味着服务已经“准备就绪”。外部服务可能需要一定时间来初始化、加载资源并开始监听请求。我们面临的核心问题是:如何在异步启动子进程后,准确判断外部服务何时真正启动成功,并等待其就绪?
传统的做法可能是通过一个循环来检查一个标志位,例如:
# 示例:存在问题的等待方式 # while not self.protocal.is_startup: # pass
这种忙等(busy-waiting)方式会阻塞事件循环,导致整个FastAPI应用冻结,无法处理其他请求。因此,我们需要一种非阻塞且高效的机制来监控子进程的输出并获取其状态。
2. 使用 asyncio.SubprocessProtocol 监控子进程输出
asyncio.SubprocessProtocol是asyncio库中用于与子进程交互的核心组件。它允许我们定义回调方法来处理子进程的输出(标准输出和标准错误)以及其生命周期事件(连接丢失、进程退出)。通过继承并重写这些方法,我们可以实时监控子进程的日志,并根据特定日志内容判断服务状态。
以下是一个自定义MyProtocol的示例,它旨在监听J*a服务启动成功的特定字符串:
VALL-E
VALL-E是一种用于文本到语音生成 (TTS) 的语言建模方法
134
查看详情
import asyncio
import re
from logging import getLogger
logger = getLogger(__name__)
class MyProtocol(asyncio.SubprocessProtocol):
def __init__(self, started_future: asyncio.Future, exited_future: asyncio.Future):
# started_future 和 exited_future 用于向外部传递启动和退出信号
self.started_future = started_future
self.exited_future = exited_future
# 定义一个正则表达式来匹配服务启动成功的日志信息
self.startup_str = re.compile("Server - Started")
def pipe_data_received(self, fd, data):
"""
当子进程的管道(stdout/stderr)接收到数据时调用。
"""
log_data = data.decode().strip() # 解码并清理日志数据
logger.info(f"Subprocess Output: {log_data}")
super().pipe_data_received(fd, data)
# 检查是否已启动,避免重复设置Future
if not self.started_future.done():
if re.search(self.startup_str, log_data):
logger.info("External service startup signal detected!")
self.started_future.set_result(True) # 设置Future结果,通知服务已启动
def pipe_connection_lost(self, fd, exc):
"""
当子进程的管道连接丢失时调用。
"""
if exc is None:
logger.debug(f"Pipe {fd} Closed normally.")
else:
logger.error(f"Pipe {fd} Closed with error: {exc}")
super().pipe_connection_lost(fd, exc)
def process_exited(self):
"""
当子进程退出时调用。
"""
logger.info("External service process exited.")
super().process_exited()
# 设置exited_future结果,通知服务已退出
if not self.exited_future.done():
self.exited_future.set_result(True)
在这个MyProtocol中:
- __init__方法接收两个asyncio.Future对象:started_future和exited_future。这些Future是关键,它们作为异步任务的“信标”,用于在特定事件发生时通知等待者。
- pipe_data_received方法会捕获子进程的标准输出或标准错误。我们在这里使用正则表达式self.startup_str来匹配J*a服务启动成功的特定日志字符串。一旦匹配成功,self.started_future.set_result(True)就会被调用,标记started_future为已完成,并携带一个结果。
- process_exited方法在子进程退出时被调用,同样通过self.exited_future.set_result(True)来发出退出信号。
3. 使用 asyncio.Future 实现精确的启动与关闭等待
asyncio.Future是asyncio中用于表示一个异步操作最终结果的低级可等待对象。通过在FastAPI的生命周期事件中创建Future对象,并将其传递给MyProtocol,我们可以在主应用中await这些Future,从而非阻塞地等待外部服务的状态变化。
4. 推荐的FastAPI生命周期管理:lifespan
FastAPI推荐使用lifespan事件管理器来处理应用启动和关闭时的异步任务,而不是已弃用的@app.on_event("startup")和@app.on_event("shutdown")装饰器。lifespan是一个异步上下文管理器,它提供了一个清晰的结构来管理资源。
以下是将上述MyProtocol和asyncio.Future集成到FastAPI lifespan中的完整示例:
import asyncio
from contextlib import asynccontextmanager
import re
from logging import getLogger
from fastapi import FastAPI
logger = getLogger(__name__)
# 定义全局变量以在lifespan函数外部访问transport和protocol
transport: asyncio.SubprocessTransport
protocol: "MyProtocol"
# MyProtocol 类定义(同上文所示)
class MyProtocol(asyncio.SubprocessProtocol):
def __init__(self, started_future: asyncio.Future, exited_future: asyncio.Future):
self.started_future = started_future
self.exited_future = exited_future
self.startup_str = re.compile("Server - Started") # 示例:匹配J*a服务启动日志
def pipe_data_received(self, fd, data):
log_data = data.decode().strip()
logger.info(f"Subprocess Output (fd={fd}): {log_data}")
super().pipe_data_rece
ived(fd, data)
if not self.started_future.done(): # 避免重复设置
if re.search(self.startup_str, log_data):
logger.info("External service startup signal detected!")
self.started_future.set_result(True)
def pipe_connection_lost(self, fd, exc):
if exc is None:
logger.debug(f"Pipe {fd} Closed normally.")
else:
logger.error(f"Pipe {fd} Closed with error: {exc}")
super().pipe_connection_lost(fd, exc)
def process_exited(self):
logger.info("External service process exited.")
super().process_exited()
if not self.exited_future.done(): # 避免重复设置
self.exited_future.set_result(True)
@asynccontextmanager
async def lifespan(app: FastAPI):
"""
FastAPI应用的生命周期事件管理器。
在应用启动时执行yield之前的代码,在应用关闭时执行yield之后的代码。
"""
global transport, protocol
loop = asyncio.get_running_loop()
# 创建Future对象,用于等待外部服务的启动和退出信号
started_future = asyncio.Future(loop=loop)
exited_future = asyncio.Future(loop=loop)
# 启动外部子进程,并传入MyProtocol实例
# 注意:这里使用lambda函数来延迟MyProtocol的实例化,
# 确保在subprocess_shell调用时才创建protocol实例并传入Future
transport, protocol = await loop.subprocess_shell(
lambda: MyProtocol(started_future, exited_future),
"/start_j*a_server.sh" # 替换为你的J*a服务启动脚本
)
logger.info("External service process started.")
try:
# 等待外部服务启动成功,设置超时时间防止无限等待
await asyncio.wait_for(started_future, timeout=15.0) # 增加超时时间以适应实际情况
logger.info("External service reported startup success.")
except asyncio.TimeoutError:
logger.error("External service startup timed out!")
# 在超时情况下可以考虑杀死子进程或抛出异常
transport.close()
raise RuntimeError("External service failed to start in time.")
# ------ yield 关键字之前的代码在应用启动时执行 ------
yield # FastAPI应用在此处开始处理请求
# ------ yield 关键字之后的代码在应用关闭时执行 ------
logger.info("FastAPI application shutting down, waiting for external service exit.")
try:
# 等待外部服务优雅退出,同样设置超时
await asyncio.wait_for(exited_future, timeout=10.0)
logger.info("External service reported graceful shutdown.")
except asyncio.TimeoutError:
logger.warning("External service did not exit gracefully within timeout. Forcing close.")
finally:
# 无论外部服务是否优雅退出,都关闭transport以清理资源
if transport.is_closing():
logger.debug("Subprocess transport is already closing.")
else:
transport.close()
logger.info("Subprocess transport closed.")
app = FastAPI(lifespan=lifespan)
# 示例路由
@app.get("/")
async def read_root():
return {"message": "FastAPI is running and external service is managed!"}
5. 代码解析与注意事项
- 全局变量 transport 和 protocol: 在lifespan外部定义它们是为了确保在整个应用生命周期中,transport和protocol对象可以被访问和管理,尤其是在yield之后进行清理。
- asyncio.Future 的作用: started_future和exited_future是核心。它们在lifespan中创建,并作为参数传递给MyProtocol的实例。当MyProtocol检测到特定的日志(如“Server - Started”)或进程退出时,它会调用future.set_result(True)来完成对应的Future。
- await asyncio.wait_for(): 这是非阻塞等待Future完成的关键。它会等待started_future或exited_future被设置结果,但同时会监听超时。如果Future在指定时间内没有完成,asyncio.TimeoutError会被抛出,允许我们进行错误处理,例如记录警告或强制关闭子进程。
-
loop.subprocess_shell(lambda: MyProtocol(...), ...):
- subprocess_shell是asyncio中启动子进程的异步方法。
- 第一个参数是一个可调用对象,它在子进程启动时被调用以创建SubprocessProtocol的实例。使用lambda函数确保MyProtocol的实例是在subprocess_shell真正需要它时才创建,并且能够正确地接收到started_future和exited_future。
- 第二个参数是外部服务的启动命令或脚本路径(例如:/start_j*a_server.sh)。
-
错误处理与资源清理:
- try...except asyncio.TimeoutError块用于处理外部服务启动或关闭超时的场景。超时后,可以根据业务逻辑决定是抛出异常停止FastAPI启动,还是记录警告并继续。
- finally块确保transport.close()在应用关闭时被调用,无论外部服务是否优雅退出,都清理了与子进程的连接资源。
- 日志匹配的健壮性: re.compile("Server - Started")用于匹配启动成功的日志。在实际应用中,这个正则表达式应该足够健壮,能够准确识别外部服务的成功启动信号,避免误判。
6. 总结
通过结合FastAPI的lifespan事件管理器、asyncio.SubprocessProtocol以及asyncio.Future,我们构建了一个强大而灵活的机制来管理外部服务的生命周期。这种方法不仅解决了异步子进程的阻塞问题,还提供了精确的启动和关闭状态监控,使得FastAPI应用能够与外部依赖服务协同工作,提高了系统的整体可靠性和可维护性。在设计集成外部服务的系统时,采用这种模式将是更专业和健壮的选择。
以上就是在FastAPI中异步管理和监控外部服务的启动与关闭的详细内容,更多请关注其它相关文章!
# 启动时
# 网络营销与推广总结范文
# 新绛网站建设流程步骤
# 影视推广在线观看网站
# 重庆网站建设建站系统
# 固原数字化网站推广电话
# 泉州短视频seo推荐
# 深圳推广网站
# 阿里巴巴网站如何建设
# SEO北京租房望京公园
# 常州抖音关键词排名
# 我们可以
# 推荐使用
# python
# 是在
# 抛出
# 全局变量
# 它会
# 是一个
# 管理器
# 异步任务
# 路由
# ai
# app
# 正则表达式
# java
相关栏目:
【
科技资讯46185 】
【
网络学院92790 】
相关推荐:
AO3最新可访问网址 Archive of Our Own官方在线入口
QQ邮箱网页版入口 QQ邮箱官方邮箱登录通道
解决macOS Tkinter应用双击启动崩溃:PyInstaller打包指南
韩剧圈正版入口页面_韩剧圈官网登录链接
钉钉视频会议画面卡顿如何解决 钉钉会议画面优化方法
如何在Promise链中有效终止错误处理后的执行
迅雷下载到U盘速度很慢怎么办_迅雷U盘下载慢优化方法
C++ explicit关键字防止隐式转换_C++构造函数安全规范
百度浏览器字体显示异常偏小_百度浏览器字体渲染修复方案
深入理解Go语言中Map值与方法接收器的交互:为什么需要临时变量
MongoDB聚合管道:正确匹配对象数组中_id的方法
Pandas DataFrame 高效批量赋值:告别循环与笛卡尔积误区
windows10怎么查看硬盘序列号_windows10硬盘id查询命令
在J*a中如何开发在线活动报名与管理系统_活动报名管理项目实战解析
星露谷物语官网入口 星露谷物语游戏官网入口
如何将HTML表格多行数据保存到Google Sheets
妖精动漫免费平台 妖精动漫官网资源观看网址
cad怎么合并重叠的线段_cad清理重复重叠线条的操作方法
ArchiveofOurOwn小说阅读-ArchiveofOurOwn同人作品访问链接
J*aScript map 迭代中检测空数组元素的有效方法
谷歌浏览器怎么给标签页静音_Chrome标签静音快捷操作
漫蛙2(台版)官方入口地址 漫蛙2(台版)正版漫画网页端
《燕云十六声》两周内达九百万玩家!位居畅销榜第五
怎么在html里运行vbs脚本_html中运行vbs脚本方法【教程】
MinIO大规模对象列表性能瓶颈深度解析与外部元数据管理策略
漫蛙网页登录入口 漫蛙漫画官方授权网址
火狐浏览器占用内存高卡顿怎么办 火狐浏览器性能优化设置技巧
HTML空白字符处理机制:渲染、DOM与编码实践
深入理解J*a编译器的兼容性选项:从-source到--release
必由学登录入口 必由学官方网站在线访问链接
怎样把文件彻底粉碎无法恢复_Windows下安全删除敏感数据【隐私保护】
德邦快递查询平台 德邦快递物流信息查询入口
iwriter统一登录平台 iwrite账号密码登录页面
C++ string find函数返回值npos详解_C++字符串查找失败的判断条件
必由学官方平台入口 必由学在线课堂登录地址
不同用户不同价格! 索尼开启账户个性化定价测试
PDO预处理语句中冒号的正确处理:区分SQL函数格式与命名占位符
魅族20怎样在浏览器开无图省流_iPhone魅族20浏览器开无图省流【流量节省】
Win10系统怎么查看已安装更新_Win10卸载有问题的更新补丁
移动端XML文件怎么转换成Excel 手机和平板上的解决方案
BetterDiscord插件中安全更新用户简介的实践指南
Win10快速启动功能利弊分析 Win10开启或关闭快速启动教程【技巧】
MAC怎么安装Homebrew包管理器_MAC为开发者和高级用户安装命令行工具
192.168.1.1管理中心入口 192.168.1.1路由器网页设置平台
单12V-2×6实现为RTX 5090供电750W!甚至都没敢跑分
顺丰快件物流信息 官方网站查询入口
如何优雅地解决Livewire文件上传难题?SpatieLivewireFilepond让一切变得简单
QQ邮箱在线使用入口 QQ邮箱个人账号网页版登录
qq游戏网页版直接玩_qq游戏免下载快速入口
Lar*el递归关系中排除子孙节点的策略


2025-11-04
浏览次数:次
返回列表
ived(fd, data)
if not self.started_future.done(): # 避免重复设置
if re.search(self.startup_str, log_data):
logger.info("External service startup signal detected!")
self.started_future.set_result(True)
def pipe_connection_lost(self, fd, exc):
if exc is None:
logger.debug(f"Pipe {fd} Closed normally.")
else:
logger.error(f"Pipe {fd} Closed with error: {exc}")
super().pipe_connection_lost(fd, exc)
def process_exited(self):
logger.info("External service process exited.")
super().process_exited()
if not self.exited_future.done(): # 避免重复设置
self.exited_future.set_result(True)
@asynccontextmanager
async def lifespan(app: FastAPI):
"""
FastAPI应用的生命周期事件管理器。
在应用启动时执行yield之前的代码,在应用关闭时执行yield之后的代码。
"""
global transport, protocol
loop = asyncio.get_running_loop()
# 创建Future对象,用于等待外部服务的启动和退出信号
started_future = asyncio.Future(loop=loop)
exited_future = asyncio.Future(loop=loop)
# 启动外部子进程,并传入MyProtocol实例
# 注意:这里使用lambda函数来延迟MyProtocol的实例化,
# 确保在subprocess_shell调用时才创建protocol实例并传入Future
transport, protocol = await loop.subprocess_shell(
lambda: MyProtocol(started_future, exited_future),
"/start_j*a_server.sh" # 替换为你的J*a服务启动脚本
)
logger.info("External service process started.")
try:
# 等待外部服务启动成功,设置超时时间防止无限等待
await asyncio.wait_for(started_future, timeout=15.0) # 增加超时时间以适应实际情况
logger.info("External service reported startup success.")
except asyncio.TimeoutError:
logger.error("External service startup timed out!")
# 在超时情况下可以考虑杀死子进程或抛出异常
transport.close()
raise RuntimeError("External service failed to start in time.")
# ------ yield 关键字之前的代码在应用启动时执行 ------
yield # FastAPI应用在此处开始处理请求
# ------ yield 关键字之后的代码在应用关闭时执行 ------
logger.info("FastAPI application shutting down, waiting for external service exit.")
try:
# 等待外部服务优雅退出,同样设置超时
await asyncio.wait_for(exited_future, timeout=10.0)
logger.info("External service reported graceful shutdown.")
except asyncio.TimeoutError:
logger.warning("External service did not exit gracefully within timeout. Forcing close.")
finally:
# 无论外部服务是否优雅退出,都关闭transport以清理资源
if transport.is_closing():
logger.debug("Subprocess transport is already closing.")
else:
transport.close()
logger.info("Subprocess transport closed.")
app = FastAPI(lifespan=lifespan)
# 示例路由
@app.get("/")
async def read_root():
return {"message": "FastAPI is running and external service is managed!"}