使用ARQ做PDF OCR和 图片OCR的任务的方案

一、业务目标 & 前提假设

业务目标
• 支持 PDF OCR(多页)和 图片 OCR
• 任务耗时可能较长(几十秒~几分钟)
• 要求:
• 支持大量并发任务,不会把 FastAPI 顶死
• 支持重试(云 OCR 抖一下不要直接失败)
• 支持服务重启后任务可恢复(至少未执行/挂一半的任务还能补偿)
• 支持任务状态查询(PENDING/RUNNING/SUCCESS/FAILED/进度)

前提假设
• 技术栈:FastAPI + ARQ + Redis + Postgres + 对象存储(本地或 MinIO/OSS)
• OCR 方式:
• 可以是 云 OCR API(百度/阿里/腾讯)——IO 密集,非常适合 async
• 或者 本地 OCR 服务(例如 PaddleOCR 独立服务),ARQ 只负责调服务

关键点:真正重 CPU/GPU 的推理最好在独立的推理服务里跑,ARQ 更适合作为“编排 + IO 请求调度”。

二、基于 ARQ 的整体架构设计

  1. 组件划分
    1. FastAPI 服务(api-service)
      • 提供 HTTP API:
      • POST /ocr/tasks:上传文件 / 提交任务,返回 task_id
      • GET /ocr/tasks/{task_id}:查询任务状态+进度+结果摘要
      • 负责:
      • 文件接收 & 存储(写到对象存储/本地磁盘)
      • 创建 DB 记录(任务 & 文档 & 页)
      • 把任务扔进 ARQ 队列(只传 ID,不传大文件)
    2. Redis
      • ARQ 的队列 + 任务结果存储
      • 只存少量任务参数 / 状态,不存大文本(避免 Redis 爆)
    3. ARQ Worker(ocr-worker)
      • 使用 arq worker.WorkerSettings 启动
      • 核心任务:
      • ocr_document(doc_id, retry_count=0)
      • 内部:拆页 → 并发调用 OCR → 存 DB → 更新进度 → 合并结果
      • 任务函数全部使用 async def,适配云 OCR / HTTP 调用场景
    4. Postgres
      • 存任务状态 & 结果:
      • ocr_task 表:任务级别(PDF/图片)
      • ocr_page 表:按页存储识别结果
      • 提供数据持久化,保证重启后不会丢结果
    5. 对象存储 / 本地文件系统
      • 存原始 PDF/图片 + 拆页后的中间图片(如果有)

  1. 任务处理流程(以 PDF 为例)
    1. 提交任务(FastAPI)
      • 用户上传 PDF
      • API 做的事情:
    2. 保存文件到存储,得到 file_path 或 file_key
    3. 在 ocr_task 表插一条记录:
      • task_id
      • file_path
      • status = PENDING
      • progress = 0
    4. 通过 ARQ 入队:
job = await redis_pool.enqueue_job(
    "ocr_document",
    task_id,
    retry_count=0,
)
4.	返回 task_id 给前端

2.	Worker 侧:ocr_document 任务逻辑
async def ocr_document(ctx, task_id: str, retry_count: int = 0):
    db = ctx["db"]  # 启动时注入
    try:
        # 1. 更新任务状态为 RUNNING
        await db.update_task_status(task_id, "RUNNING")

        # 2. 根据 task_id 查出 file_path,判断是 PDF 还是图片
        task = await db.get_task(task_id)
        file_path = task.file_path

        if task.file_type == "pdf":
            # 2.1 拆 PDF 为多页图片
            page_paths = await split_pdf_to_images(file_path)
        else:
            page_paths = [file_path]

        total = len(page_paths)
        results = []
    # 3. 控制并发调用 OCR(云 OCR / 本地 OCR 服务)

sem = asyncio.Semaphore(5) # 限制同时请求数
async def ocr_one(i, page_path):
async with sem:
text, extra = await call_ocr_api(page_path)
await db.save_page_result(task_id, i, text, extra)
# 更新进度
await db.update_task_progress(task_id, int((i+1) / total * 100))

await asyncio.gather(*[
    ocr_one(i, p) for i, p in enumerate(page_paths)
])

    # 4. 合并结果/做后处理(可选)

await db.mark_task_success(task_id)

except TemporaryError as e:
# 自定义的“暂时性错误”,比如网络/云服务 5xx
MAX_RETRY = 3
if retry_count < MAX_RETRY:
# 10 秒后重试,并带上 retry_count+1
from arq import Retry
raise Retry(defer=10, kwargs={
“task_id”: task_id,
“retry_count”: retry_count + 1
})
else:
await db.mark_task_failed(task_id, reason=str(e))
raise

except Exception as e:
# 其他不可恢复错误
await db.mark_task_failed(task_id, reason=str(e))
raise

3.	查询任务结果
•	GET /ocr/tasks/{task_id} 从 Postgres 读:
•	status
•	progress
•	如果成功:可以返回文本摘要 / 页数 / 下载链接

  1. 宕机 & 重启时的恢复策略

1)Redis 队列里的任务
• 未开始执行的任务都在 Redis 里
• 只要 Redis 没挂(开启 AOF 或持久化),重启 worker 后会继续执行

2)执行中的任务(RUNNING)
• 配置 job_timeout,比如 10 分钟:

class WorkerSettings:
    functions = [ocr_document]
    redis_settings = RedisSettings(...)
    job_timeout = 600
•	如果 worker 崩掉 / kill -9:
•	Redis 认为这个 job 处于执行中,但 job_timeout 到期后会判定为失败
•	我们的补偿策略:
•	在 ocr_task 中维护 last_update_time(每处理一页更新一次)
•	启一个“巡检任务”(可以是另一个定时脚本 / 服务):
•	定期扫描 status=RUNNING 且 last_update_time 超过 N 分钟的任务
•	判断为“疑似僵尸任务”
•	再次通过 ARQ enqueue_job("ocr_document", task_id, retry_count=当前+1)

这样就实现了:
• 服务优雅关闭:worker 会把手上的任务跑完再退出
• 服务异常宕机:通过 job_timeout + last_update_time 把“半途挂掉”的任务重新入队

三、使用 ARQ 做这类业务的优点

  1. 和 FastAPI 风格统一:全链路 async
    • FastAPI 本身是 async 框架
    • ARQ 的任务函数也是 async def,调用云 OCR、对象存储、DB 都是 await
    • 整个项目是纯 async 风格,思维模型一致,协程调度简单清晰

  2. 对云 OCR / HTTP IO 场景特别友好
    • OCR 如果是走云厂商 API,本地主要是网络 IO + 等待时间
    • 使用 ARQ + asyncio.gather 可以轻松做到:
    • 一个 worker 同时跑多个 OCR 请求
    • 控制并发(Semaphore)避免打爆云服务 QPS
    • CPU 不重的情况下:这种 async 并发非常高效

  3. 架构简单、组件少
    • 只需要 Redis(既做队列又存 job 状态)
    • 对比 Celery:
    • 无需 RabbitMQ / 额外 backend
    • Worker 配置简单,一个 WorkerSettings 就够

对于你这种自己掌控部署、还要搞一堆微服务的人来说,少一个组件就少一堆运维心智负担。

  1. 重试机制可按业务精细控制
    • 用 Retry(defer=秒数, kwargs=…) 明确告诉 ARQ“过多久再重试”
    • 很适合 OCR 里这种“云接口暂时 500/超时,再试几次”的场景
    • 你可以在任务中设计:
    • 最大重试次数
    • 重试间隔(固定/递增)
    • 哪些异常重试,哪些异常直接失败
    • 完全业务驱动,不被框架的黑魔法限制

  2. 适合“调度+编排”,而不是“重推理”
    • 你本来就打算把 PaddleOCR / 大模型等重推理部分单独做服务:
    • ARQ 负责:排队 → 调 OCR 服务 → 存结果 → 更新进度
    • OCR 服务只负责推理
    • 在这个定位下,ARQ 非常合适当“业务编排层”的队列框架

四、使用 ARQ 的不足 / 风险点

  1. 仅支持 Redis,扩展性受限
    • ARQ 目前只支持 Redis 作为队列和结果存储
    • 如果你将来希望:
    • 使用 RabbitMQ / Kafka / SQS 等更“重量级”的消息系统
    • 或者需要更强的持久化语义 / 消息重放
    • 那 ARQ 就不适合,需要换框架(例如 Celery 或自己对接 Kafka)

对你目前来说,Redis 足够,但这是个中长期的约束。

  1. 没有内置类似 Celery beat 的定时调度器
    • ARQ 没有像 Celery beat 那样的“任务调度器”
    • 如果你要:
    • 定期扫描僵尸任务
    • 定时批量做 OCR 任务
    • 需要:
    • 用 crontab / APScheduler / 一个小的 FastAPI 定时服务来自行实现

不是不能做,就是需要你自己写一点调度逻辑。

  1. 重试策略需要自己封装“标准化”
    • ARQ 只提供一个 Retry 异常
    • “最大重试次数、退避策略、统一日志记录”都需要你自己封装一个小工具层
    • 对你来讲不难,但团队协作时要保证所有任务遵循同一套规范

Celery 这块有比较完整的官方支持(max_retries, countdown, retry_backoff 等)。

  1. 可视化监控和生态偏弱
    • Celery 有 Flower,还有无数经验博客
    • ARQ 的生态比较“极客”,可视化监控需要你自己接:
    • Prometheus + Grafana
    • 自写管理接口(比如列出任务状态、处理速度等)
    • 对你这种本来就要搭日志/监控体系的人来说问题不大,但不如 Celery 开箱即用。

  2. 对 CPU/GPU 密集任务不是最优形态
    • ARQ 是 async 单进程事件循环模型,要充分利用多核/多 GPU,需要:
    • 启动多个 worker 进程 / 容器
    • 或把重 CPU 逻辑放到其他服务(推荐)
    • Celery 的多进程 worker 模型在直接跑本地推理时更自然一些

对你的场景:推荐把重推理独立服务化,ARQ 做调度,这个缺点就不算大问题。

  1. Redis 任务持久化要自己注意配置
    • 如果 Redis 配置不好(比如纯内存、没有 AOF/RDB),崩溃时队列里的任务会丢
    • ARQ 自己不管这些,需要你在 Redis 层:
    • 开启 RDB/AOF
    • 做主从/哨兵(高可用)

不过这点不管 Celery/ARQ 都一样:broker 崩了都得你自己兜底。

五、结合你当前业务的建议结论

如果我们只看你现在这条线:
• PDF / 图片 OCR
• 很多调用云 OCR、未来还要调智能编目、质检等服务
• 有 FastAPI、Redis、Postgres 的基础
• 你能接受自己封装一层“任务重试 + 状态管理 + 监控”

那么:

✅ 用 ARQ 做“异步任务 & 编排层”是可行且好用的选择,尤其是对于 IO 型任务(云 OCR)很合适。
⚠️ 但前提是:
• 真正重推理(PaddleOCR / 大模型)放到独立推理服务
• ARQ + Redis 只存 ID & 状态,结果进 DB
• 你愿意自己写一点:重试封装、僵尸任务恢复、监控接口。

如果你后面打算把这一套做成“全公司统一任务中台”,还要承载各种类型的任务(视频转码、大模型推理等等),那可以:
• 当前 OCR 项目用 ARQ(轻便、开发快)
• 并并行规划一套 更通用的 Celery 任务平台 作为长远演进方向(甚至可以共存一段时间)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

MonkeyKing.sun

对你有帮助的话,可以打赏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值