为什么你的 PDF pipeline 比需要的更慢

发布: (2026年3月2日 GMT+8 17:09)
7 分钟阅读
原文: Dev.to

Source: Dev.to

每个后端工程师都有过这种经历。客户想要发票、报告、证书、对账单。 “直接生成 PDF”,他们说,就像是一个 print() 语句一样。

于是你就去使用标准工具链:用 Jinja2 渲染 HTML,启动一个无头 Chrome 实例,调用 page.pdf(),并祈祷在第 500 份文档时不会出现 OOM(内存溢出)。

它能工作。直到它不行了。

无头浏览器税

当你通过无头浏览器生成 PDF 时,实际会发生以下步骤:

  1. 启动一个 Chromium 进程(或连接到进程池)
  2. 创建一个新的页面上下文
  3. 加载你的 HTML + CSS + 资源
  4. 等待字体、图片、布局完成
  5. 调用打印为 PDF 的 API
  6. 序列化 PDF 字节
  7. 销毁页面上下文

对于单张发票,这大约需要 2–5 秒。而对于每月 10 000 份对账单的批量处理,你将面临 数小时的计算、数 GB 的内存,以及需要专门基础设施仅用于打印文档的部署。

最糟糕的是什么?Chromium 正在渲染完整的网页——包括 JavaScript 引擎、DOM、CSSOM、布局树、绘制、合成——而你真正需要的只是“把这些文字放在这些位置并画几条线”。

确定性渲染的真实含义

确定性的 PDF 渲染器不会把你的文档当作网页来解释。它读取结构化模板,解析布局,并直接写入 PDF 基元。没有浏览器。没有 JavaScript 引擎。没有中间渲染步骤。

对比

指标无头浏览器本地渲染器
每个文档的时间2–5 秒50–200 毫秒
每个文档的内存200–500 MB10–30 MB
批量 10 0005–14 小时8–30 分钟
确定性否(竞争条件)是(SHA‑256 可复现)
依赖项Chrome/Puppeteer

最后一行的重要性超出你的想象。“无依赖”意味着你的 PDF 生成可以在 Lambda 函数、Docker 容器、CI 流水线,或是没有任何软件安装的裸金属服务器上运行。无需管理 Chrome 二进制文件。没有版本不匹配。没有沙箱的麻烦。

没有人谈论的可复现性问题

使用无头浏览器生成相同的发票两次。比较字节。它们不会匹配。

  • 字体在不同运行之间略有不同的渲染。
  • 时间戳嵌入到元数据中。
  • 图像压缩在位层面上不稳定。

这意味着你不能通过比较哈希来验证文档是否未被篡改,不能激进地缓存,也不能构建依赖文档身份的审计追踪。

确定性渲染器对相同的输入始终产生相同的字节——永远如此。这不是学术问题;它是医疗、金融和法律文档流水线中的合规要求。如果你为受监管行业生成文档,non‑determinism 就是一种风险。

实际效果

我构建了 Fullbleed 来解决这个问题。它是一个 Rust 原生的 PDF 渲染引擎,并提供 Python 绑定。

安装并渲染单个文档

pip install fullbleed
fullbleed render invoice.html --output invoice.pdf

批量渲染

from fullbleed import render_batch

documents = [
    {"template": "invoice.html", "data": customer}
    for customer in customers
]

# 通过 Rayon 在所有可用核心上并行渲染
results = render_batch(documents, workers="auto")

1 万张发票。几分钟,而不是几小时。输出确定性。根本不需要 Chrome。

何时应该(以及不应该)使用原生渲染器

在以下情况下使用原生渲染器:

  • 需要批量生成文档(发票、对账单、报告)
  • 需要可复现的输出以满足合规或审计要求
  • 管道运行在受限环境中(Lambda、CI、边缘)
  • 性能至关重要(每份文档亚秒级)
  • 希望没有外部依赖

在以下情况下坚持使用无头 Chrome:

  • 渲染任意用户提供的 HTML/CSS/JS
  • 需要像素级精准的网页截图
  • 模板中包含复杂的 JavaScript 交互
  • 每天生成的文档少于 10 份且不在乎速度

大多数后端工程师默认使用无头 Chrome,因为他们熟悉它。但如果你的使用场景是结构化文档生成——即使用模板填充数据——那么你实际上为不需要的功能支付了巨大的性能和复杂度代价。

要点

PDF 生成是一个已经解决的问题,但大多数团队的实现很糟糕。并不是因为他们不称职,而是因为显而易见的工具(wkhtmltopdf、Puppeteer、Playwright)更注重通用性而非性能。当你的实际需求是“用这些数据填充模板并生成 PDF”时,专门构建的渲染器 10–100× 更快,使用的内存只有很小的一部分,并且产生确定性的输出。

如果你正在构建文档流水线并想尝试这种方法,Fullbleed 是开源的(AGPLv3)并可通过 pip install fullbleed 安装。商业许可证可用于专有使用。

0 浏览
Back to Blog

相关文章

阅读更多 »

防线:三系统,而非单一

三个系统,而不是一个。“Rate limiting” 常被用作一个统称,指任何拒绝或放慢请求的行为。实际上,它包含三种不同的机制……