周二下午2点,你的用户上传了一份50页扫描件。浏览器卡住不动,服务器彻底无响应——这不是噩梦,是同步处理CPU密集型任务的标配结局。
OCR(光学字符识别)是典型的CPU密集型操作。用Tesseract或EasyOCR处理单份文档,耗时2-30秒不等。FastAPI的async/await能处理并发请求,但有个致命盲区:异步不等于并行,CPU-bound任务会阻塞事件循环。图像算法在跑的时候,你的服务器线程被占得死死的。
BackgroundTasks和Celery的分水岭就在这里。选错架构,轻则用户体验崩坏,重则服务完全瘫痪。
方案一:BackgroundTasks,零依赖的"轻量灭火器"
FastAPI内置的BackgroundTasks是"即插即用"型方案。不需要Redis、不需要消息队列,代码写起来像这样:
用户上传文件→生成唯一job_id→丢进后台任务队列→立即返回"已排队"响应。主线程解放,用户不用干等。
但这里有三个隐形天花板。第一,任务和主进程共享内存,OCR吃满CPU时,API响应照样变慢。第二,进程重启=任务全丢,没有持久化。第三,无法横向扩展,单机性能就是天花板。
适合场景:日处理量<1000份、能接受偶发延迟、团队没运维资源。
方案二:Celery+Redis,工业级的"任务调度中心"
当单台服务器撑不住,Celery是标准答案。任务丢进Redis队列,Worker进程分布式消费,API层只负责收文件和发通知。
架构变成:FastAPI处理上传→Redis做任务队列→多个Worker并行OCR→结果写数据库/回调通知。CPU密集型操作被隔离到独立进程,API层始终保持响应。
代价是复杂度指数级上升。Redis运维、Worker监控、任务重试机制、死信队列处理——这些都需要人盯。一个配置错误的CELERY_ACKS_LATE就能让任务重复执行,把数据库打爆。
适合场景:日处理量>5000份、需要水平扩展、有专职运维或DevOps。
方案三:混合架构,80%场景的最优解
真实世界里,多数团队卡在中间地带:BackgroundTasks不够稳,Celery又太重。
一个被验证过的折中方案:小文件(<10页)走BackgroundTasks,大文件或批量任务走Celery。用文件页数或大小做路由判断,代码里加个简单分支:
if file_size < 5MB and estimated_pages < 10: 走BackgroundTasks,秒级响应。else: 进Celery队列,用户轮询状态或等Webhook。
这种"分层处理"把架构复杂度控制在可维护范围,同时覆盖90%的用户体验场景。
关键决策:你的瓶颈到底在哪?
选方案前先做一道算术题:峰值QPS × 平均处理时长 ÷ 并发Worker数 = 所需最小Worker数。
假设峰值10请求/秒,单份OCR平均5秒,你需要至少50个并发Worker才能不排队。BackgroundTasks做不到,Celery可以轻松横向扩展到50台机器。
但反过来,如果日均只有200份文档,上Celery就是"用航母运快递"。Redis的内存成本、Worker的空转消耗、部署复杂度——这些隐性成本往往被低估。
一个来自生产环境的教训:某团队用Celery处理日均300份合同,结果Redis内存泄漏导致凌晨任务堆积,早上用户集中投诉。切回BackgroundTasks+定时重启策略后,稳定性反而提升。
另一个反面案例:某金融公司坚持用BackgroundTasks处理年报扫描件,旺季CPU飙到100%,API P99延迟从200ms涨到15秒。迁移到Celery后,延迟回落,但运维人力增加了0.5个FTE。
技术选型没有银弹,只有对业务节奏的匹配度。
热门跟贴