Enterprise 版本
计量 — 深度参考
NextPDF\Enterprise\Metering 命名空间提供编排级的用量计量,用于计费可见性与审计。其公共接口面为六个符号:MeterCollector、MeterEntry、MeteringReporter、MeteringBackendInterface、PrometheusMeteringBackend 与 PrometheusPushgatewayException。收集器在内存中缓冲不可变条目,并按批次刷新。上报器将每个批次扇出到一个或多个后端,并带有逐后端的重试与故障隔离。计量是尽力而为且非致命的:一次计量后端中断会降低可观测性,绝不会影响文档处理。此数据流并非配额强制执行的权威来源。工作流层面的指南请参见 计量。
可用性与授权
标题为“可用性与授权”的章节此能力随 NextPDF Enterprise(nextpdf/enterprise)发行,并通过 Enterprise 层级的许可证封套激活。缺少该授权的部署不会加载此能力的类。比较版本并获取许可证。
计量是一项基础 Enterprise 能力,只要安装了 Enterprise 包即可用;没有单独的逐功能标记。NextPDF Core(Apache-2.0)与 NextPDF Pro 没有任何收集器、上报器或后端接口面;该契约仅随 nextpdf/enterprise 发行。
公共 API 接口面
标题为“公共 API 接口面”的章节| 符号 | 参数 | 默认行为 | 返回 | 抛出或失败于 | 说明 |
|---|---|---|---|---|---|
MeterCollector::__construct | MeteringReporter $reporter、int $bufferSize = 100 | 创建一个带空内存缓冲的收集器 | 新的 MeterCollector | 不抛出 | $bufferSize 记录为 positive-int |
MeterCollector::record | string $operation、int $count、string $tenantId、string $licenseId、int $pagesProcessed = 0、float $durationMs = 0.0、array $metadata = [] | 追加一个以当前时间打上时间戳的不可变 MeterEntry;当缓冲达到 $bufferSize 时自动刷新 | void | 不抛出;自动刷新委托给上报器,而上报器绝不抛出 | 时间戳在记录时刻取得 |
MeterCollector::flush | — | 将所有已缓冲条目交给上报器;空缓冲为无操作 | void | 不抛出;后端故障由上报器吸收 | 缓冲在交接前被换出;可重入安全 |
MeterCollector::bufferCount | — | 返回已缓冲条目的数量 | int<0, max> | 不抛出 | 用于诊断与背压决策 |
MeterCollector::registerShutdownFlush | — | 通过 register_shutdown_function 注册 flush() | void | 不抛出 | 在 PHP-FPM 部署中于引导时调用一次 |
MeterEntry::__construct | string $operation、int $count、DateTimeImmutable $timestamp、string $tenantId、string $licenseId、int $pagesProcessed = 0、float $durationMs = 0.0、array $metadata = [] | 原样存储所提供的值 | 新的 MeterEntry | 未声明 @throws;在 strict_types 下,参数类型不匹配时 PHP 会抛出 TypeError | final readonly;全部八个提升属性均为 public |
MeteringReporter::__construct | list<MeteringBackendInterface> $backends、int $maxRetries = 2、LoggerInterface $logger = new NullLogger() | 校验并存储后端列表 | 新的 MeteringReporter | 当 $backends 为空时抛出 InvalidArgumentException | $maxRetries 计入每个后端的投递尝试总数 |
MeteringReporter::report | list<MeterEntry> $entries | 独立地将该批次投递给每个后端,并带逐后端重试 | void | 不抛出;尝试用尽时以错误级别记录并丢弃该后端的批次 | 空列表为无操作 |
MeteringBackendInterface::report | list<MeterEntry> $entries | 将一个批次投递给后端 | void | 当后端不可达时抛出 RuntimeException | 各实现必须是幂等的(按 timestamp + operation + tenantId 去重) |
MeteringBackendInterface::isHealthy | — | 可达性探针 | bool | 未声明 @throws | 仅用于诊断;上报器不据此进行门控 |
MeteringBackendInterface::backendName | — | 诊断用的后端名称 | non-empty-string | 未声明 @throws | 例如 "prometheus"、"billing-api"、"null" |
PrometheusMeteringBackend::__construct | ClientInterface $httpClient、RequestFactoryInterface $requestFactory、StreamFactoryInterface $streamFactory、string $pushgatewayUrl、string $jobName = 'nextpdf_metering' | 配置一个 Pushgateway 推送目标 | 新的 PrometheusMeteringBackend | 不抛出 | 注入 PSR-18 客户端与 PSR-17 工厂 |
PrometheusMeteringBackend::report | list<MeterEntry> $entries | 按操作与租户序列聚合该批次,并将展示文本 POST 到 <pushgatewayUrl>/metrics/job/<jobName> | void | 在非 2xx 状态或 PSR-18 传输失败时抛出 PrometheusPushgatewayException | 空列表为无操作 |
PrometheusMeteringBackend::isHealthy | — | 探测 Pushgateway 健康端点;仅在 HTTP 200 时为 true | bool | 不抛出;任何失败返回 false | 只读 GET 探针 |
PrometheusMeteringBackend::backendName | — | 返回 "prometheus" | non-empty-string | 不抛出 | 常量 |
PrometheusPushgatewayException | — | 表示一次失败的 Pushgateway 投递 | — | 即为该可抛出对象 | final;继承 RuntimeException |
public function __construct( private readonly MeteringReporter $reporter, private readonly int $bufferSize = 100,) {}
public function record( string $operation, int $count, string $tenantId, string $licenseId, int $pagesProcessed = 0, float $durationMs = 0.0, array $metadata = [],): void
public function flush(): void
public function bufferCount(): int
public function registerShutdownFlush(): voidpublic function __construct( public string $operation, public int $count, public DateTimeImmutable $timestamp, public string $tenantId, public string $licenseId, public int $pagesProcessed = 0, public float $durationMs = 0.0, public array $metadata = [],) {}public function report(array $entries): void;
public function isHealthy(): bool;
public function backendName(): string;public function __construct( array $backends, private readonly int $maxRetries = 2, private readonly LoggerInterface $logger = new NullLogger(),)
public function report(array $entries): voidpublic function __construct( private readonly ClientInterface $httpClient, private readonly RequestFactoryInterface $requestFactory, private readonly StreamFactoryInterface $streamFactory, private readonly string $pushgatewayUrl, private readonly string $jobName = self::DEFAULT_JOB_NAME,) {}final class PrometheusPushgatewayException extends RuntimeException {}MeterEntry 的 public readonly 属性
| 属性 | 类型 | 含义 |
|---|---|---|
$operation | non-empty-string | 操作类型,例如 "parse"、"compress"、"embed"、"rag_query" |
$count | positive-int | 所消耗的单位数 |
$timestamp | DateTimeImmutable | 操作发生的时刻;收集器在记录时刻打上时间戳 |
$tenantId | non-empty-string | 租户标识符 |
$licenseId | non-empty-string | 许可证标识符 |
$pagesProcessed | int<0, max> | 已处理的 PDF 页数;非 PDF 操作为 0 |
$durationMs | float | 操作时长(毫秒) |
$metadata | array<string, mixed> | 自由格式的、操作特定的元数据 |
行为契约
标题为“行为契约”的章节MeterCollector::record()构造一个不可变的MeterEntry,为其打上当前时间的时间戳,并将其追加到内存缓冲中。当缓冲达到$bufferSize个条目时,收集器会自动刷新。flush()是幂等且可重入安全的。空缓冲为无操作。在该批次被交给上报器之前,缓冲会被换出,因此一次可重入的刷新无法重复发送。MeteringReporter会以空后端列表拒绝构造。该InvalidArgumentException是收集器/上报器路径上唯一的异常。MeteringReporter::report()独立地将每个批次投递给每个后端。一个失败的后端绝不会阻止另一个后端接收同一批次。$maxRetries计入每个后端的投递尝试总数;默认值2意味着一次初始尝试加一次重试。每次失败的尝试都会记录一条警告,包含后端名称、尝试次数与条目数。- 当某个后端的最后一次尝试失败时,上报器会额外以错误级别记录被丢弃的条目数,然后继续。它绝不会从
report()抛出,因此调用方不得从一次正常返回推断已投递。 - 各后端必须是幂等的。接口契约要求按时间戳、操作与租户标识符进行去重。上报器本身不去重。
PrometheusMeteringBackend::report()将该批次聚合为逐操作、逐租户的序列,并以 Content-Typetext/plain; version=0.0.4将 Prometheus 文本展示 POST 到<pushgatewayUrl>/metrics/job/<jobName>。默认的 job 名称为nextpdf_metering。- 推送的载荷携带三个计数器——
nextpdf_operations_total、nextpdf_pages_processed_total与nextpdf_operation_duration_ms_total——每个都按操作与租户打标签。 - 此计量数据流是非权威的。配额强制执行与权威的计算计量消费的是该部署单独的权威用量数字,绝非此缓冲。编排计量中的一处缺口是一处可观测性缺口,而非一处计费正确性缺口。
边界情形与失败模式
标题为“边界情形与失败模式”的章节- 被重复或被重放的批次。 由后端幂等性吸收;上报器不去重。不要依赖恰好一次的投递。
- 用尽的重试。 该后端的批次会被丢弃并以错误级别记录。
report()或flush()的一次正常返回绝不意味着已投递。 - 刷新前进程退出。 缓冲仅存于内存。一次崩溃,或一次未注册关闭处理器的退出,都会丢失已缓冲的条目。
- 工作进程模型不匹配。 PHP-FPM 部署在引导时调用一次
registerShutdownFlush(),使剩余部分在请求结束时刷新。长时运行的工作进程(Octane、Symfony worker、队列 worker)则必须以周期定时器刷新;否则条目会累积,直到工作进程退出。 $bufferSize小于1。 违反已记录的positive-int契约;可观察到的结果是每次record()调用都会刷新。- 敏感元数据。
$metadata是自由格式的,且可能携带敏感的操作上下文。存储、留存与访问控制是后端运营方的责任。 - Pushgateway 投递失败。 一次非 2xx 响应会抛出携带 HTTP 状态与响应体的
PrometheusPushgatewayException;一次 PSR-18 传输失败会被包裹为同一异常类型。上报器的重试与隔离循环会吸收两者。 - 健康探针。
PrometheusMeteringBackend::isHealthy()对<pushgatewayUrl>/-/healthy发起一次 GET,仅在 HTTP 200 时返回true。任何传输错误返回false;该探针绝不抛出。 - 恶意标签值。 操作或租户值中的反斜杠、双引号与换行符会在发出时被转义,因此一个标签值无法注入额外的展示行或破坏标签块。
- FIPS 模式。 收集器与上报器不执行任何密码学操作,且没有任何 FIPS 特有的行为。一个在传输中签名或加密的后端,会继承其宿主密码学提供方的 FIPS 态势。
符合性
标题为“符合性”的章节没有任何外部标准管辖进程内的收集器、上报器或后端契约;没有任何规范性规范可供引用,因此本页按设计不携带任何 RAG 引用。Prometheus 后端发出 Prometheus 文本展示格式,并以 Content-Type text/plain; version=0.0.4 推送;该格式是一种生态系统约定,而非 ISO 或 IETF 标准,此论断以产品源码为依据。NextPDF 不对此接口面作出任何符合性或认证声明。
开发说明
标题为“开发说明”的章节- 所有类都声明
strict_types=1且为final;MeterEntry是final readonly,并带有提升的 public 属性。参数类型不匹配会在调用方引发 PHPTypeError。 - 该模块的类携带包级
@since注解2.1.0;PrometheusPushgatewayException携带@since3.2.0。 - 上报器的 logger 默认为 PSR-3
NullLogger。请在生产中注入一个真实的 logger,否则被丢弃的批次不会留下任何痕迹。 - 单元测试:实现一个假的
MeteringBackendInterface,并直接构造MeterEntry值。Prometheus 后端接受 PSR-18/PSR-17 抽象,因此一个 mock HTTP 客户端可以离线演练完整的推送路径。 - 建议的边界测试:缓冲恰好位于
$bufferSize、可重入刷新、空缓冲刷新、一个后端失败而第二个成功,以及重试用尽的日志记录。 - 后端实现者在投递失败时抛出
RuntimeException(或其子类);上报器会吸收它。在上游添加进一步重试之前,请遵守幂等性要求。
发布边界
标题为“发布边界”的章节本页仅记录可从外部观察到的行为与受支持的公共 API 接口面。内部命名空间路径、辅助类、机制表、runbook 文件名与工单前缀均不在范围内。
另请参阅
标题为“另请参阅”的章节- 计量 — NextPDF Enterprise — 能力页面:工作流、配置与已完成的部署示例。
- 计费 — 深度参考 — 套餐层级、超额语义与告警阶梯。
- SaaS — 深度参考 — 多租户编排接口面。
- 许可 — 深度参考 — 激活 Enterprise 能力的许可证封套。