OpenTelemetry 实体事件可以做什么?

指标、日志与追踪告诉你系统如何运行,却很少说明系统中实际有哪些东西:现在存在哪些主机、接口、交换机、服务和卷,以及这些信息在过去一小时、一天或一个季度里怎样变化。这样一份不断变化的资产清单,一直是开放可观测性技术栈的盲点。

由 Entities SIG 推进、在 Entity Data Model 中描述的 OpenTelemetry 实体事件,开始填补这一空白。实体事件构成一个事件流。真正值得讨论的问题是:事件到达以后,应该怎样使用?本文以一个开源消费端为例,介绍一种解决方式。

实体数据模型及其约定仍在开发中,尚未稳定且持续演进。以下属性名只是示例,应与当前规范核对。本文讨论的是消费实体事件的总体方式,经验适用于不同消费端。

用一分钟认识实体事件

OpenTelemetry 将实体事件作为带有实体语义约定的 OTLP 日志记录传输。每个事件包含实体类型、标识属性、描述属性,以及说明生命周期的事件类型。消费端只需检查是否存在 otel.entity.event.type,就能识别这类记录:

# An entity-event log record (illustrative)
LogRecord
  Timestamp: 2026-05-26T08:00:00Z              # the producer-side time
  attributes:
    otel.entity.event.type: entity_state       # observed; or entity_delete
    otel.entity.type:       host
    otel.entity.id:         { host.name: web-server-1 }            # identity (a map)
    otel.entity.attributes: { os.type: linux, host.arch: amd64 }   # descriptive (a map)

主机代理、网络代理,以及任何支持 OTLP 的生产端,都可以发出这些事件。消费端的职责是把这一连串观测转化为能够查询的信息。整个流程是:OpenTelemetry 生产端经 OTLP 发送实体事件;持久的事件溯源日志保存它们;投影通过重放日志建立实时、双时间实体图;GraphQL API 面向人员与工具,MCP 服务器面向 AI 助手。

下面介绍这个流程的四个步骤。

OTel生产端到持久事件日志、双时间实体图及GraphQL/MCP接口的管线(官方原图)
官方原文流程图

第一步:保存事件流,而不是覆盖状态

最直观的办法是建立“当前实体”表,并就地更新各行。但这恰好丢掉了基础设施问题最难处理的维度:时间。一旦覆盖 web-server-1 的地址,就失去了它曾经变更以及何时变更的信息。

更好的默认方案是事件溯源:将每个实体事件追加到持久、有序的日志中,把日志作为权威记录。当前图则是投影,通过重放日志构建实体与关系的内存模型。从头重建整张图,也只是一次重放。

这样既能直接读取当前状态,也不会丢失历史。

第二步:明确采用双时间模型

需要保存两个时间戳:

– 事件时间:现实中发生变化的时间,取自 LogRecord 时间戳。

– 记录时间:消费端获知变化的时间,在接收时自行记录,不能从生产端获取。

保留两者,才能回答两类不同的问题:

– 现实视图:“上周二 db-07 的连接关系是什么?”

– 审计视图:“09:00 时,我们对 db-07 知道些什么?”

第二个问题在事故复盘中尤其关键。只有不把两条时间轴合并,才能回答它。从一开始就设计双时间模型,成本远低于事后补救。

第三步:给实体一个不可变身份

OpenTelemetry 把实体 ID 视为不可变。这也是一张希望成为权威记录的图应遵守的原则。身份必须精确匹配:一次观测要么属于已知实体(ID 相同),要么属于另一个实体。

陷阱在于把会变化的值放入身份。如果主机身份包含当前租用的 IP,DHCP 续租就会把它分裂成一个新实体。应选择在实体整个生命周期内稳定的属性作为身份;当前地址、资源使用量、最近观测状态等确实会变化的信息,都应放入描述属性。

这样,地址变更就是同一个实体的属性更新;真正的身份变化则正确地产生新实体,不会把两个不同对象悄悄合并。

某个值如果会随时间重复使用,不必丢弃它,而应搭配区分信息。OpenTelemetry 的 process 实体就是一个好例子:PID 可以复用,因此进程以 process.pid 与 process.creation.time 共同标识。这对组合在该进程生命周期内保持稳定,而变化信息仍然属于描述属性。

应尽早把这点做对。若采用“宽松”匹配,把某个标识值不同的观测也当作同一实体,就会默默合并不同实体:同一主机上只在端口上不同的两个数据库,可能变成一个。对于权威记录,静默碰撞比失去某种启发式匹配更糟。

第四步:让图可以查询

时间图只有能够被人员和机器查询,才有用。通过两种接口可以覆盖两类使用者:

– GraphQL API,面向人员、仪表盘和工具。

– Model Context Protocol(MCP)服务器,让 AI 助手代表运维人员查询图。

MCP 给实体事件带来了自然语言查询的可能。由于各个类型、字段和参数都有详细描述,大语言模型可以检查模式,并调用带类型的工具,例如查找实体、获取邻居、查看实体历史和近期变更、描述模式,进而回答自然语言问题:

$ ask "which switches did db-07 depend on last Tuesday — and what changed since?"

→ db-07 dependency path @ 2026-05-26
    core ← leaf-sw-3, spine-sw-1
  Δ since: leaf-sw-3 → leaf-sw-9 (2026-05-28 14:12 UTC)
    spine path unchanged

上述问题意为:“上周二 db-07 依赖哪些交换机,此后发生了什么变化?”回答示例显示叶交换机从 leaf-sw-3 变为 leaf-sw-9,脊交换机路径没有变化。

无须在不同仪表盘之间来回切换,助手可以直接对完全来自 OTLP 实体事件、实时且具备时间维度的图进行推理。

规范已经包含关系

资产清单只是故事的一半,另一半是拓扑:“这个服务依赖那个数据库”“这个进程运行在那台主机上”。文章初稿写成时,关系仍是后续工作。此后,实体事件规范随 v1.58.0 规范版本于 2026-06-22 发布,并直接描述关系,参见 Entity events 与 opentelemetry-specification#4836。

关系作为 entity.relationships 数组嵌入实体状态事件。每个描述符给出关系 type 与目标实体的 entity.type、entity.id;方向是 source --[type]--> target,关系类型是开放枚举,例如 depends_on、contains:

# Relationships ride inside an entity-state event (spec #4836, shipped in v1.58.0)
LogRecord
  attributes:
    otel.entity.event.type: entity_state
    otel.entity.type:       service.instance
    otel.entity.id:         { service.instance.id: checkout-1 }
    entity.relationships:
      - type:        depends_on
        entity.type: service.instance
        entity.id:   { service.instance.id: payments-1 }

边随拥有它的实体一起传输,不是单独事件。删除关系时,源实体只需重新发出不包含该描述符的状态。构建时间图的消费端读取每个状态事件,更新或插入实体,并协调其出边;数组随时间变化,关系便相应增加或消失,与属性更新采用同样的变更分类。

为什么使用图:连接其他观测信号

目标并不是建立孤立的资产清单,而是让已有遥测数据发挥作用。OpenTelemetry 在 Resource 上携带实体,所以被跟踪的同一批实体,已经附加在指标、日志和追踪上。资产与拓扑图就成了连接这些信号的键:

– 先确定范围:利用图选择需要拉取的信号,即真正关心的实体和拓扑切片,避免盲目查询。

– 按实体关联:指标峰值、日志行与追踪可以关联到同一主机、进程或服务,因为它们共享实体身份,无须手动匹配标签。

– 沿关系追查:depends_on、runs_on 等关系把关联提升为影响范围分析。db-07 性能下降时,图可以指出应优先检查追踪和指标的上游服务。

一张实时、具有时间维度、说明对象存在状态与连接方式的图,使你能够查询其他可观测性数据,并回答任意历史时间点的问题。

保持生产端通用

消费端应能接收任意 OpenTelemetry 生产端的数据,使用标准而非专有协议。它不自行运行采集器,也不直接轮询设备。从主机、网络设备或云 API 发出实体事件,是生产端的职责。生产端保持通用,才能维持开放生态。

几点运行注意事项

希望成为权威记录的消费端,必须面对以下问题:

– 时钟偏差:事件时间来自生产端,不同生产端的时钟会漂移。不能假定跨生产端存在统一全局顺序;应依据每个实体的时间轴推理,并用自身记录时间辅助回答“何时知道什么”。

– 事件量与心跳:生产端会周期性重申实体状态,所以大多数事件都表示“没有变化”。可以合并连续未变化的观测,保留一段序列的第一条与最后一条,避免稳定运行时日志膨胀;关系出现或消失等结构变化则应原样保存。

– 静默合并:精确身份也有另一面。如果两个实体意外共享标识键,就会合并。应把标识键视为与生产端之间的契约,宁可明确拒绝或标记观测,也不要静默合并。

要点

– 实体事件让“存在哪些对象,以及怎样连接”成为 OpenTelemetry 的一等数据。

– 应按事件溯源、双时间事件流消费,而非只维护可变表。

– 实体 ID 应不可变且精确匹配;变化的信息放在描述属性中,历史才能保留。

– 同时向人员提供 GraphQL、向助手提供 MCP;自然语言查询是值得关注实体事件的重要理由。

– 关系已在规范中定义,嵌入每个实体的状态事件,随 v1.58.0 规范版本发布。

– 实体随 Resource 传输,因此图可以连接已有指标、日志与追踪。

参与讨论

阅读 Entity Data Model 与 OTEP 0256,并参与 OpenTelemetry Entities SIG、Semantic Conventions 的讨论。实体事件与关系随 v1.58.0 发布,但身份作用域等内容仍在推进。

感谢 Entities SIG 完成本文所依据的规范工作。

进一步阅读:Entity Data Model、Entity Events、OTEP 0256(v1.58.0)。

版本核对

本文保留2026-08-14原文的说明性属性名。OTel 1.61.0 官方规范文档仍标为 Development;数据模型中的进程身份示例使用 process.start_time,与本文的 process.creation.time 不同。当前Entity Events使用 EventName: entity.state、entity.type、entity.id 和 entity.description,与博客的 otel.entity.event.type 等属性也不同。实现时应以所选规范版本及语义约定为准,不能直接把博客示例当作稳定协议。当前规范页面的关系数组说明用 type,后续关系结构表又用 relationship.type,亦须按版本源文核对,不能擅自推断二者兼容。

来源与许可

作者:Matthieu Noirbusson(Sensor Factory)。原文发布于 2026-08-14:What can you do with OpenTelemetry entity events?。本文为中文翻译,示例属性、版本与命令保留原文;示例属于说明性内容。原文网站项目采用 CC BY 4.0,许可源文件。许可中的免责声明同样适用。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容