跳到內容
getnextpdf.com

Pro 版本

Stream — 深入參考

本頁記錄 NextPDF\Pro\Stream 子系統超出總覽頁面之外的公開合約、類別、方法與失敗模式。以下每個型別都屬於已記錄的 Pro 公開範圍。

此能力隨 NextPDF Pronextpdf/pro)出貨,並在 Pro 級授權封套(license envelope)下啟用。未具該權利的部署不會載入此能力的類別。比較版本並取得授權

沒有適用的個別功能授權旗標;程式碼隨 Pro 版本出貨。worker 數量、批次大小、重試預算與儲存後端皆為執行階段參數。

NextPDF\Pro\Stream\Engine\RenderEngineInterface 是吞吐量引擎與 document-job 串流處理器之間的合約。引擎實作它(掌管並行度、worker-pool 生命週期、背壓、有界記憶體);串流處理器消費它(掌管具鍵狀態、去重、重試、檢查點與 exactly-once 提交)。引擎回傳位元組加上 sha-256,絕不回傳已提交的位置——正是這份無副作用,讓處理器能恰好一次地暫存(stage)、提交與設檢查點。

public function renderBatch(array $manifests, array $variablesByJobId = []): array; // list<EngineRenderResult>, input order
public function maxBatchSize(): int; // int<1, max> backpressure hint
public function isAvailable(): bool;

$manifests 是一個大小至多為 maxBatchSize()list<RenderManifest>$variablesByJobId 將 job id 映射至 array<string, scalar> 範本變數。單一 manifest 的失敗是一個逐項 Failed/Timeout 結果,絕不會中止整個批次。

同步、單行程基準。會在透過 Core 的 SingleDocumentRenderer 算繪之前,先以 RenderManifestValidator(16 MiB 內嵌酬載上限、conformance/signature 允許清單、sha-256 內容雜湊格式、BCP-47 locale 語法)對每個 manifest 進行 fail-closed 驗證。一個阻斷性的驗證錯誤會短路為 EngineRenderResult::failed(jobId, 'SPEC-MANIFEST-INVALID', ...);一個算繪例外則成為 'SPEC-RENDER-EXCEPTION'。建構子:__construct(SingleDocumentRenderer $renderer, int $maxBatchSize = 64, ?RenderManifestValidator $validator = null)——maxBatchSize < 1 會擲出 InvalidArgumentExceptionisAvailable() 永遠為 true

NextPDF\Pro\Stream\Engine\ConcurrentRenderEngine

標題為「NextPDF\Pro\Stream\Engine\ConcurrentRenderEngine」的區段

final readonly__construct(RenderUnitExecutorInterface $executor)。會把每個 manifest 包裹進一個帶索引的 RenderUnit,透過執行器執行它們,並依索引重新排序完成項,使輸出與循序算繪逐位元組相同。一個落在 [0, count) 之外的完成索引會擲出 RenderEngineException::unknownUnit();一個重複的索引會擲出 duplicateResult();一個缺漏的索引會擲出 missingResult()maxBatchSize()isAvailable() 委派給執行器。

NextPDF\Pro\Stream\Engine\RenderUnitExecutorInterface

標題為「NextPDF\Pro\Stream\Engine\RenderUnitExecutorInterface」的區段
public function execute(array $units): iterable; // iterable<CompletedRenderUnit>, any order
public function maxBatchSize(): int;
public function isAvailable(): bool;

實作可以任意順序產出完成項;ConcurrentRenderEngine 會依索引還原順序。

NextPDF\Pro\Stream\Engine\InlineRenderUnitExecutor

標題為「NextPDF\Pro\Stream\Engine\InlineRenderUnitExecutor」的區段

final readonly__construct(RenderEngineInterface $inner)。會依序透過內層引擎算繪每個單元——這是一個並行執行器必須逐位元組相符的確定性正確性參考。無時間、行程、執行緒或隨機性。

NextPDF\Pro\Stream\Engine\ProcessPoolRenderUnitExecutor

標題為「NextPDF\Pro\Stream\Engine\ProcessPoolRenderUnitExecutor」的區段

final readonly。會把一個批次分配到至多 maxWorkersphp worker 子行程(每個一塊 chunk),由它們並行算繪;輸出與 inline 基準逐位元組相同。建構子:

__construct(
int $maxWorkers = 4,
int $maxBatchSize = 64,
?string $phpBinary = null,
?string $workerScript = null,
?string $autoload = null,
?int $timeoutSeconds = 300, // null disables the wall-clock watchdog
)

穩健性合約:

  • **無死結、Windows 安全。**單元酬載與結果透過暫存檔(而非 pipe)傳遞;父行程輪詢 proc_get_status(),並僅在一個 worker 已退出後才把 pipe 抽乾至 EOF,因此一個 worker 無法卡住父行程。
  • 有界等待。timeoutSeconds 為整個並行算繪設上限;逾期時,每個仍在執行的 worker 都會被終止,並擲出一個 RenderEngineException
  • **資源衛生。**一個 finally 會關閉 pipe、對存活的 worker 做一次有界的終止並回收(terminate-and-reap)嘗試(優雅終止 → 強制終止 → 回收;一個在有界寬限期內未被觀察到停止的子行程會被放棄,而非冒著無限期阻塞的風險),並在所有路徑上 unlink 每個暫存檔。
  • **可信關聯。**每個 worker 都必須回傳恰好其被指派的索引集合(無缺漏、無重複、無外來索引);每個算繪結果的位元組都會被重新雜湊並與 worker 回報的 sha-256 比對,且任何非 rendered/failed 的狀態都會硬性失敗。單一 manifest 的算繪失敗是一個逐單元 Failed 結果;只有基礎設施性的故障(非零退出、無法讀取/損毀的輸出、逾時)才會讓執行器硬性失敗。

isAvailable() 要求 autoload 檔與 worker 腳本都存在。一個非正值的界限或負的逾時會擲出 InvalidArgumentException

final readonly——int<0, max> $indexRenderManifest $manifestarray<string, scalar> $variables。關聯依 index,絕不依 job id(job id 在一個批次內不保證唯一)。

final readonly——int $index(不可信,由引擎驗證)、EngineRenderResult $result

final readonly。欄位:jobIdEngineRenderStatus $status?string $bytes?string $sha256int $pageCount?string $errorCode?string $errorMessagearray<non-empty-string, float> $timings。工廠方法:rendered(jobId, bytes, sha256, pageCount, timings = [])failed(jobId, errorCode, errorMessage)timedOut(jobId, errorMessage)(代碼 SPEC-ENGINE-TIMEOUT)。isRendered() 回報狀態。一個算繪後的結果攜帶位元組與摘要,絕不攜帶已提交的位置。

字串支援的 enum:RenderedFailedTimeoutisRetryable() 僅對 Timeouttrue,因此呼叫端無須重新檢視錯誤即可將逾時歸類為暫時性。

NextPDF\Pro\Stream\Commit\OutputCommitterInterface

標題為「NextPDF\Pro\Stream\Commit\OutputCommitterInterface」的區段
public function commit(
string $jobId,
OutputObjectKey $target,
string $bytes,
string $sha256,
bool $overwrite = false,
): CommitReceipt;

Exactly-once 發佈:原子、idempotent(逐位元組相同的重新提交不進行任何寫入,並回傳一個 idempotentReuse = trueCommitReceipt——一張全新的收據,而非原始那張;其 committedAt 是當前時鐘)、不靜默覆蓋,且經完整性檢查(committer 會重新計算摘要)。失敗模式:CommitIntegrityException(宣告的 sha-256 與位元組不符)、OutputCommitConflictException(在 overwrite = false 下,分歧的位元組寫入一個已被占用的鍵)、UnsupportedTargetException(不支援的目標方案)。

NextPDF\Pro\Stream\Commit\LocalFilesystemCommitter

標題為「NextPDF\Pro\Stream\Commit\LocalFilesystemCommitter」的區段

final readonly,實作 OutputCommitterInterface, DurableCapability__construct(string $rootDirectory, ?AtomicFileWriter $writer = null, ?ClockInterface $clock = null)。僅服務 file 方案;將每個目標解析至單一設定好的根目錄之下,並透過一個原子 writer 寫入(O_EXCL temp → fsync → 同卷 rename)。整段臨界區(含父目錄建立)在一個保存於輸出鍵空間之外的 per-root lock file 上以排他 flock 執行,且若該 lock 無法開啟或取得,提交即 fail-closed。它會拒絕符號連結的最終元件,以及任何含冒號的鍵(NTFS 替代資料串流向量)。對同一個鍵的跨主機並行 exactly-once 需要可持久的 Enterprise committer。一個本身是或包含系統暫存目錄的根目錄會擲出 InvalidArgumentException

final readonly——jobIdOutputObjectKey $targetsha256int<0, max> $bytesWrittenbool $idempotentReuseDateTimeImmutable $committedAttoArray() / fromArray() 完全可往返(目標是結構化的,而非有損的 URI);fromArray() 是嚴格的,並在欄位缺漏或格式不良時擲出 InvalidArgumentException

NextPDF\Pro\Stream\Checkpoint\CheckpointStoreInterface

標題為「NextPDF\Pro\Stream\Checkpoint\CheckpointStoreInterface」的區段

load(string $runId): ?RunCheckpointsave(RunCheckpoint $checkpoint): void(可持久且原子——讀取者絕不會看到一個寫到一半的檢查點)。

final readonly——runIdint<0, max> $committedOffsetarray $keyedStateDateTimeImmutable $updatedAtSCHEMA_VERSION = '1.0'。工廠方法 start(runId, at)advancedTo(committedOffset, keyedState, at)toArray()/toJson()/fromArray()/fromJson() 序列化它;fromArray() 要求一個非空的 run id 與一個有效的 updated_at,會拒絕不相容(非 1.x)的 schema_version,並透過在每個深度丟棄任何不可 JSON 序列化的值來正規化具鍵狀態,使復原後的狀態永遠可重新序列化。在復原時,處理器會快轉越過 committedOffset 並還原具鍵狀態;在最後一道屏障之後被變動的狀態會向前重算,絕非錯誤,因為可持久的 exactly-once 來自 committer 的摘要去重。

NextPDF\Pro\Stream\Checkpoint\FilesystemCheckpointStore

標題為「NextPDF\Pro\Stream\Checkpoint\FilesystemCheckpointStore」的區段

final readonly,實作 CheckpointStoreInterface, DurableCapability。每次執行一個 JSON 檔,原子地寫入。Run id 必須符合 [A-Za-z0-9._-]+ 且不含 ..;一個不存在的目錄會擲出 InvalidArgumentException

NextPDF\Pro\Stream\Dedup\IdempotencyStoreInterface

標題為「NextPDF\Pro\Stream\Dedup\IdempotencyStoreInterface」的區段

isCommitted(IdempotencyKey $key): boolmarkCommitted(IdempotencyKey $key, CommitReceipt $receipt): voidreceiptFor(IdempotencyKey $key): ?CommitReceipt。這是在算繪一個重播之前就短路的快速路徑;committer 的摘要比對仍是可持久的保證,因此一筆遺失的記錄至多浪費一次 committer 會去重的重新算繪。

  • InMemoryIdempotencyStore——單次執行/測試範圍(當機時遺失)。
  • FilesystemIdempotencyStore——DurableCapability;每個已提交的鍵一個原子 JSON 檔(序列化後的收據),以鍵值的雜湊命名。標記是 idempotent 的;一次並行的重新標記會無害地在一個檔案上競爭。一個不存在的目錄會擲出 InvalidArgumentException

final readonly——positive-int $maxAttemptspositive-int $baseDelayMspositive-int $maxDelayMs__construct(int $maxAttempts = 3, int $baseDelayMs = 100, int $maxDelayMs = 30000),具不變式 maxAttempts >= 11 <= baseDelayMs <= maxDelayMs <= 7 days(否則 InvalidArgumentException)。工廠方法 default()none()(單次嘗試)。shouldRetry(int $attempt): booldelayMsForAttempt(int $attempt): int<0, max> 是確定性的指數退避 baseDelayMs * 2^(attempt-1),上限為 maxDelayMs(無內建抖動;請在呼叫端施加)。

NextPDF\Pro\Stream\Retry\DeadLetterStoreInterface

標題為「NextPDF\Pro\Stream\Retry\DeadLetterStoreInterface」的區段

add(DeadLetterRecord $record): voidall(): list<DeadLetterRecord>count(): int<0, max>

final readonly——jobIdidempotencyKeyValuepositive-int $attemptslastErrorCodelastErrorMessageDateTimeImmutable $failedAt、選用的 ?string $runId、選用的 int<1, max> $sourceOffset。當兩者皆已知時,dedupKey()runId:sourceOffset,否則為 idempotency 鍵值。fromArray() 嚴格地將 failed_at 解析為 ATOM(拒絕相對或非 ATOM 的表達式),使序列化/反序列化保持對稱。

  • InMemoryDeadLetterStore——單次執行/測試範圍。
  • FilesystemDeadLetterStore——DurableCapability;每筆記錄一個原子 JSON 檔,以 dedup 鍵的 SHA-256 雜湊命名(….dlq.json),因此在恢復時重新加入同一項目是 idempotent 的。all() 以確定性(已排序)順序讀取記錄,並透過擲出例外來暴露一筆損毀的記錄;count() 是一次廉價的檔案計數,而非有效性檢查。

NextPDF\Pro\Stream\State\KeyedStateStoreInterface

標題為「NextPDF\Pro\Stream\State\KeyedStateStoreInterface」的區段

hasgetputremoveclear,加上用於檢查點邊界的 snapshot(): arrayrestore(array $snapshot): void。值必須可 JSON 序列化。對於預設的算繪並提交工作負載,不使用任何具鍵狀態;它的存在是為了聚合/開窗(windowing)擴充。InMemoryKeyedStateStore 是單次執行的實作;對預設工作負載而言,在復原時失去它是一個語意上的 no-op,因為 exactly-once 來自 committer 的摘要去重。

final readonly__construct(string $tenantField = 'tenant_id', string $documentField = 'document_id')keyFor(RenderManifest $manifest): non-empty-string 會從 manifest 中繼資料推導分割鍵,形式為 rawurlencode(tenant):rawurlencode(document)(此編碼阻止 ("a:b","c")("a","b:c") 碰撞),並在任一欄位缺席時回退至 job id——因此每個 manifest 都解析為一個穩定、非空的鍵。

NextPDF\Pro\Stream\DurableCapability 是任何狀態能在行程重啟後存活的儲存/committer 的標記介面。一次當機安全的執行要求每個協作者都實作它,因此它會快速失敗,而非承諾某個記憶體內儲存無法保有的 exactly-once。

所有子系統例外都實作 NextPDF\Pro\Stream\Exception\StreamException(擴充 Throwable),因此呼叫端可以一致地 catch (StreamException)

  • RenderEngineExceptionRuntimeException)——執行器違反了批次合約(未知、重複或缺漏的單元;worker 故障;逾時)。
  • CommitIntegrityExceptionRuntimeException)——宣告的 sha-256 與酬載不符;spec 代碼 SPEC-COMMIT-422
  • OutputCommitConflictExceptionRuntimeException)——在 overwrite 停用下,分歧的位元組寫入一個已被占用的鍵;spec 代碼 SPEC-COMMIT-409(透過 specCode() 暴露)。
  • UnsupportedTargetExceptionInvalidArgumentException)——一個 committer 無法服務的目標方案。

引擎會對照 Core manifest 模型驗證 manifest,並產生確定性的位元組加上 sha-256 摘要;committer 則強制原子、經完整性檢查的 exactly-once 寫入。本模組除了 sha-256 內容摘要之外不進行任何密碼學運算,也未定義任何 FIPS 特定行為。

  • renderBatch() 絕不會因單一 manifest 的失敗而中止;請檢視每個 EngineRenderResult
  • ProcessPoolRenderUnitExecutor 嚴格依索引關聯並重新雜湊 worker 位元組;一個有 bug 的 worker 會硬性失敗,而非破壞輸出。
  • LocalFilesystemCommitter 是單主機;跨主機 exactly-once 需要可持久的 Enterprise committer。
  • 當機安全的執行必須全程使用 DurableCapability(檔案系統)儲存,而非記憶體內變體。

本頁僅記錄外部可觀察的行為與受支援的公開 API 範圍。內部命名空間路徑、輔助類別、機制表、runbook 檔名與工單前綴皆不在範圍內。