使用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 入队:
bash 复制代码
job = await redis_pool.enqueue_job(
    "ocr_document",
    task_id,
    retry_count=0,
)
复制代码
4.	返回 task_id 给前端

2.	Worker 侧:ocr_document 任务逻辑
bash 复制代码
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 分钟:

bash 复制代码
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 任务平台 作为长远演进方向(甚至可以共存一段时间)

相关推荐
kevin 14 小时前
合同盖章前,如何比对差异,确保纸质版与电子版100%一致?
人工智能·自动化·ocr
2501_930707785 小时前
如何使用C#代码在 PDF 中添加或删除附件
pdf
m5655bj5 小时前
如何使用 Python 调整 PDF 页面顺序?
python·pdf
YuanYWRS6 小时前
办公基础:实现PDF中表单不改变格式的情况下转成excel
pdf·excel
AI人工智能+7 小时前
授权委托书识别技术:利用深度学习和NLP实现纸质文档的智能解析
ocr·文档抽取·授权委托书识别
大强同学7 小时前
ShareX - 错误:英语 language is not available in this system for OCR.
ocr
E_ICEBLUE1 天前
PDF vs PDF/A:区别、场景与常用转换方法(2025 全面解读)
python·pdf
不惑_1 天前
在 Rokid 眼镜上实现工业巡检与 OCR,识别、理解与指导的现场智能
ocr
TextIn智能文档云平台1 天前
从散乱资料到智能知识库:基于TextIn与Coze的RAG实战
人工智能·pdf·知识库·rag·coze·文档解析