Kết xuất PDF an toàn trong worker chạy lâu dài
Tổng quan nhanh
Phần tiêu đề “Tổng quan nhanh”Một worker PHP (PHP: Hypertext Preprocessor) chạy lâu dài (RoadRunner, Swoole, Laravel Octane) giữ một tiến trình hoạt động qua nhiều request. Nếu cứ phân tích lại cùng các phông chữ và giải mã lại cùng các hình ảnh ở mỗi request, bạn sẽ lãng phí thời gian xử lý và làm tăng bộ nhớ thường trú. NextPDF tránh chi phí đó bằng cách tách thành hai vòng đời:
- Vòng đời tiến trình, dùng chung:
FontRegistryvàImageRegistrylưu giữ các bảng phông chữ đã phân tích và bộ nhớ đệm hình ảnh đã giải mã. Hãy tạo các registry này một lần khi worker khởi động. - Vòng đời request, dùng một lần rồi bỏ: đối tượng
DocumentdoDocumentFactory::create()trả về. Hãy dựng nó, ghi ra, rồi để nó ra khỏi phạm vi. Khi đó, bộ thu gom rác của PHP có thể thu hồi toàn bộ đồ thị đối tượng.
Công thức này cho bạn thấy trình tự khởi động worker, phần thân xử lý từng request và cách reset định kỳ để giữ bộ nhớ đỉnh ổn định.
Cài đặt
Phần tiêu đề “Cài đặt”composer require nextpdf/core:^3Mẫu worker không yêu cầu extension bổ sung nào, và runtime worker (RoadRunner / Swoole / Octane) là tùy chọn. Bạn có thể chạy cùng mẫu factory đó trong vòng lặp for trên giao diện dòng lệnh (CLI); đây cũng chính là cách harness kiểm thử hoạt động.
Tổng quan khái niệm
Phần tiêu đề “Tổng quan khái niệm”Với mã worker, hãy bắt đầu từ DocumentFactory. Hãy khởi tạo nó một lần cùng với FontRegistry và ImageRegistry dùng chung:
FontRegistry::warmup()phân tích các tệp phông chữ bạn cung cấp và lưu các bảng đã phân tích vào bộ nhớ đệm.FontRegistry::lock()đóng băng registry để mã xử lý từng request không thể thay đổi tập phông chữ dùng chung.isLocked()báo cáo trạng thái hiện tại. Sau khi registry đã khóa, bạn có thể dùng chung nó an toàn giữa các coroutine chạy song song.- Hãy khởi tạo
ImageRegistryvới một ngân sáchmaxCacheBytes. Khi vượt quá ngân sách, registry loại bỏ các mục ít được dùng gần đây nhất. Hình ảnh lớn hơn ngân sách sẽ bỏ qua bộ nhớ đệm thay vì làm bộ nhớ đệm quá tải. ImageRegistry::reset()loại bỏ mọi hình ảnh khỏi bộ nhớ đệm trong khi registry vẫn sẵn sàng sử dụng. Request tiếp theo sẽ nạp lại theo nhu cầu. Hãy gọi nó theo một nhịp nhất định (sau mỗi N request, hoặc khimemoryUsage()vượt qua một ngưỡng) để đưa mức đỉnh trở về mức cơ sở.
Mỗi tài liệu do factory tạo ra là một tệp Portable Document Format (PDF) độc lập. ISO 32000-2 §7.5.5 quy định rằng trailer của một tệp chưa bao giờ được cập nhật sẽ không có mục Prev, và mỗi request của worker phát ra đúng loại tệp thế hệ đầu tiên đó. Vì vậy, các request không dùng chung trạng thái tài liệu, dù vẫn dùng chung bộ nhớ đệm phông chữ và hình ảnh. Thẻ BaseFont của phông chữ tập con (ISO 32000-2 §9.6.4) giữ ổn định qua các request vì phông chữ đã phân tích nằm trong registry dùng chung.
Bề mặt API
Phần tiêu đề “Bề mặt API”Công thức này dùng bề mặt API được sinh từ PHPDoc trên NextPDF\Core\DocumentFactory, NextPDF\Typography\FontRegistry, NextPDF\Graphics\ImageRegistry, và NextPDF\Support\MemoryReport. Các thành phần chính gồm DocumentFactory::create(), FontRegistry::warmup() / lock() / isLocked() / memoryUsage(), ImageRegistry::reset() / memoryUsage(), và MemoryReport::$currentBytes / $peakBytes / $entryCount / utilizationPercent().
Mã mẫu — bắt đầu nhanh
Phần tiêu đề “Mã mẫu — bắt đầu nhanh”<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\DocumentFactory;use NextPDF\Graphics\ImageRegistry;use NextPDF\Typography\FontRegistry;
// --- Worker boot (run ONCE, before the request loop) ---------------------$fonts = new FontRegistry();$fonts->lock(); // freeze the shared font set$images = new ImageRegistry(maxCacheBytes: 50 * 1024 * 1024);$factory = new DocumentFactory($fonts, $images);
// --- Per request ---------------------------------------------------------$doc = $factory->create();$doc->setTitle('Worker output');$doc->addPage();$doc->setFont('helvetica', 'B', 16);$doc->cell(0, 12, 'Generated in a shared-registry worker', newLine: true);$doc->save(getenv('NEXTPDF_COOKBOOK_OUTPUT') ?: __DIR__ . '/out.pdf');// $doc leaves scope here → GC reclaims the whole document tree.Mã mẫu — bản chính thức
Phần tiêu đề “Mã mẫu — bản chính thức”Ví dụ đầy đủ tuân theo kênh đầu ra của harness. Ví dụ này cho thấy trình tự khởi động, một vòng lặp request có giới hạn, lệnh reset() định kỳ, và phép kiểm tra bộ nhớ đỉnh. Đây là kịch bản mà harness chạy hai lần để kiểm tra tính tái lập.
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\DocumentFactory;use NextPDF\Graphics\ImageRegistry;use NextPDF\Typography\FontRegistry;
// --- Worker boot: shared, process-lifetime registries --------------------$fonts = new FontRegistry();$fonts->lock(); // share-safe once locked$images = new ImageRegistry(maxCacheBytes: 50 * 1024 * 1024);$factory = new DocumentFactory($fonts, $images);
$resetEvery = 4; // reset cadence in requests$peakAfterReset = 0;
// --- Simulated request loop ---------------------------------------------for ($request = 1; $request <= 12; $request++) { $doc = $factory->create(); $doc->setTitle("Worker Request #{$request}"); $doc->addPage(); $doc->setFont('helvetica', 'B', 16); $doc->cell(0, 12, "Worker Request #{$request}", newLine: true); $doc->setFont('helvetica', '', 11); $doc->cell(0, 8, 'Shared FontRegistry / ImageRegistry across requests.', newLine: true);
// The harness captures the LAST request's PDF via the side channel. if ($request === 12) { $doc->save(getenv('NEXTPDF_COOKBOOK_OUTPUT') ?: __DIR__ . '/out.pdf'); } else { $doc->getPdfData(); // force render, then drop }
unset($doc); // explicit end-of-request
// Bound the cache high-water mark on a fixed cadence. if ($request % $resetEvery === 0) { $images->reset(); \gc_collect_cycles(); $report = $images->memoryUsage(); $peakAfterReset = \max($peakAfterReset, $report->currentBytes); }}
$final = $images->memoryUsage();
fwrite(STDERR, \sprintf( "fonts.locked=%s images.entries=%d images.current=%dB peak_after_reset=%dB\n", $fonts->isLocked() ? 'yes' : 'no', $final->entryCount, $final->currentBytes, $peakAfterReset,));STDOUT được để trống cho harness; văn bản tiến trình được ghi vào STDERR. PDF chỉ được ghi vào NEXTPDF_COOKBOOK_OUTPUT; nó không bao giờ được in ra màn hình.
Trường hợp biên & điểm cần lưu ý
Phần tiêu đề “Trường hợp biên & điểm cần lưu ý”- Khóa trước khi dùng chung. Hãy gọi
FontRegistry::lock()khi khởi động. Registry còn có thể thay đổi mà bị hai coroutine cùng truy cập là một tình huống tranh chấp dữ liệu. Hãy dùngisLocked()làm phép kiểm tra trong health check. reset()không phải làunset().ImageRegistry::reset()loại bỏ dữ liệu nhị phân trong bộ nhớ đệm và giữ registry vẫn dùng được, nên đây là lệnh gọi định kỳ phù hợp. Nếu bạn hủy rồi dựng lại registry cho mỗi request, bạn sẽ mất lợi ích của bộ nhớ đệm dùng chung.- Bỏ qua hình ảnh quá lớn. Một hình ảnh lớn hơn
maxCacheBytesđược giải mã ở mỗi lần dùng và không bao giờ được đưa vào bộ nhớ đệm, nên nó không thể đẩy tập làm việc hiện tại ra ngoài. Đây là hành vi có chủ đích. Hãy định cỡ ngân sách theo những hình ảnh thường gặp của bạn, chứ không phải hình ảnh lớn hiếm gặp. - Tài liệu phải ra khỏi phạm vi. Nếu bạn giữ
Documenttrong biến static, một liên kết container tồn tại lâu dài, hoặc một closure bị worker giữ lại, toàn bộ đồ thị đối tượng vẫn còn sống và việc thu gom theo từng request không thể hoạt động. Một lệnh gọiunset()hoặc việc thoát khỏi phạm vi là bắt buộc. - Vị trí đặt
gc_collect_cycles(). Bộ thu gom chu trình của PHP không biết ranh giới giữa các request. Hãy gọi nó sau nhịp reset, chứ không phải ở mỗi request. Cách này giới hạn mức đỉnh mà không thêm chi phí thu gom vào đường dẫn nóng. - Lưu ý về tính tất định. Dấu thời gian của tài liệu và
/IDtrong trailer được tạo lại ở mỗi lần lưu (ISO 32000-2 §14.3). Do đó PDF được ghi lại sẽ được so sánh theo hồ sơ ngữ nghĩa (cây cú pháp trừu tượng (AST) có cùng cấu trúc kèm metadata, không bao giờ theo các byte dễ biến đổi). Xem mục “Tuân thủ”.
Hiệu năng
Phần tiêu đề “Hiệu năng”- Registry dùng chung biến việc phân tích phông chữ và giải mã hình ảnh lặp đi lặp lại thành chi phí khởi động một lần. Khi đó, công việc cho mỗi request chỉ còn là bố cục và tuần tự hóa.
- Bộ nhớ thường trú đỉnh được giới hạn bởi
maxCacheBytescộng với tập làm việc của một tài liệu đang được xử lý. Lệnhreset()định kỳ đưa bộ nhớ đệm về mức cơ sở, nên worker tồn tại lâu dài không xuất hiện dạng răng cưa có xu hướng đi lên. - Front-matter
performance_budget(wall_ms: 4000,peak_mb: 192) giới hạn lần chạy harness của vòng lặp 12 request. Harness áp đặt ngân sách này; đây không phải là bảo đảm cho các tài liệu bất kỳ. - Công thức này cung cấp phần bao phủ “memory/GC” trong danh sách thiếu sót §4.3 cho #31. Tệp hỗ trợ
examples/14-worker-factory.phptồn tại, vàtests/Cookbook/Php/WorkerSafeBatchRenderingRecipeTest.phpbổ sung phép kiểm tra memory/GC còn thiếu (mức đỉnh không tăng qua các chu kỳ sau reset).
Ghi chú về bảo mật
Phần tiêu đề “Ghi chú về bảo mật”- Mẫu worker xử lý một tài liệu cho mỗi request và chỉ dùng chung bộ nhớ đệm phông chữ đã phân tích cùng hình ảnh đã giải mã. Nội dung tài liệu không vượt qua ranh giới request. Một request không thể đọc dữ liệu tài liệu của request khác thông qua các registry dùng chung.
- Dữ liệu đầu vào không đáng tin cậy vẫn đi qua các ranh giới đầu vào thông thường của NextPDF, và mẫu worker không nới lỏng việc kiểm tra hợp lệ. Hãy xem đầu vào HyperText Markup Language (HTML) và tài nguyên của mỗi request là không đáng tin cậy, giống như cách bạn làm trong một tiến trình theo từng request.
Tuân thủ
Phần tiêu đề “Tuân thủ”| Tuyên bố | Tiêu chuẩn | Điều khoản | reference_id |
|---|---|---|---|
| Ngày sửa đổi tài liệu được tạo lại ở mỗi lần lưu, nên đầu ra theo từng request không ổn định ở mức byte. | ISO 32000-2 | §14.3 | |
Mỗi tài liệu của worker là tệp chưa bao giờ được cập nhật (không có Prev trong trailer); các request không dùng chung trạng thái tài liệu. | ISO 32000-2 | §7.5.5 | |
| Tiền tố thẻ của phông chữ tập con giữ ổn định qua các request vì phông chữ đã phân tích nằm trong registry dùng chung. | ISO 32000-2 | §9.6.4 |
Vì /ID trong trailer và ngày sửa đổi được tạo lại ở mỗi lần lưu, nên công thức này được kiểm chứng bằng hồ sơ tái lập ngữ nghĩa (sự bằng nhau của cây cú pháp trừu tượng (AST) có cấu trúc, cộng với việc so sánh riêng metadata). Một tuyên bố ở mức bit hoặc cấu trúc sẽ không chính xác đối với đầu ra của worker.