实体生成与写入

概述

在 UModel 系统中,实体(Entity)是 EntitySet 的具体实例,代表系统中的各种资源对象,如服务、主机、Pod 等。本文档详细介绍实体的数据格式、写入方法和最佳实践。

说明

关于 UModel、EntitySet、Entity 的详细概念,请参见EntityStore 简介

应用案例

当前应用案例相关素材,请参考:umodel.zip

实体数据格式

实体数据通过 SLS Log 协议写入到 EntityStore${workspace}__entity日志库中。每个实体记录包含系统字段和自定义字段两部分。

系统字段

字段名

类型

是否必须

示例

说明

__domain__

string

apm, k8s, acs

实体所属的域,用于数据隔离和分类

__entity_type__

string

apm.service, k8s.pod

实体类型,定义实体的分类

__entity_id__

string

391274C9A1D369FB5222E273D02B81B8

实体唯一标识符,128hex

__method__

string

否(默认Update)

Create, Update, Expire, Delete, Revise

操作类型,控制实体的生命周期

__first_observed_time__

int

1700000000

首次观察时间,秒级时间戳

__last_observed_time__

int

1700000001

最近观察时间,秒级时间戳

__keep_alive_seconds__

int

否(默认3600)

3600

存活时间,秒为单位

字段说明

__entity_id__生成规则

  • 标准格式:128位十六进制字符串。

  • 建议生成方法:对主键使用 MD5 或双重 xxhash 函数。

  • 兜底处理(不建议):如不符合128hex格式,内部自动进行 xxhash 转换。

时间字段管理

  • __first_observed_time__:实体首次被发现的时间,一旦设置通常不变。

  • __last_observed_time__:实体最近一次被观察的时间,每次更新时更新。

  • 实体可见时间范围:[FirstObservedTime, LastObservedTime + KeepAliveSeconds]

  • 说明:若不确定实体的首次被发现时间,可以不填 __first_observed_time__ 此字段。EntityStore 在写入时会自动使用第一次接收的 __last_observed_time__ 的值作为 __first_observed_time__ 的值。

自定义字段

除系统字段外,可以添加 EntitySet 中定义的各类 Field 字段:

{
  "__domain__": "apm",
  "__entity_type__": "apm.service",
  "__entity_id__": "391274C9A1D369FB5222E273D02B81B8",
  "__method__": "Update",
  "__last_observed_time__": 1700000001,
  "__keep_alive_seconds__": 3600,
  "service_name": "user-service",
  "version": "v1.2.3",
  "cluster": "production",
  "labels": {
    "env": "prod",
    "team": "backend"
  }
}

JSON 字段注意事项

⚠️ 重要:对于JSON格式的字段(如labels、annotations),必须确保JSON对象中的键按字母顺序序列化,避免频繁重建同一实体的索引。

实体时间区间与查询覆盖机制

时间区间基础概念

实体在 EntityStore 中具有明确的时间可见范围,查询引擎只有在查询时间窗口与实体可见时间产生交集时,才会返回该实体。

实体可见时间计算

实体的可见时间范围由以下公式确定:

说明

注意:实体可见时间范围、和查询时间范围遵循左闭右开原则。

实体状态

可见时间范围

说明

正常状态

[FirstObservedTime, LastObservedTime + KeepAliveSeconds)

包含KeepAlive延长时间

已过期状态(显示调用Expire后)

[FirstObservedTime, LastObservedTime)

不包含KeepAlive延长时间

已删除状态

无可见时间

任何查询都不返回

时间交集判断规则

查询引擎使用以下规则判断实体是否应该被返回:

查询返回实体 = 查询时间窗口 ∩ 实体可见时间

具体判断逻辑:

if (QueryEndTime >= EntityFirstObservedTime && 
    QueryStartTime <= EntityVisibleEndTime) {
    返回实体
} else {
    不返回实体
}

写入方法详解

Create - 创建实体

使用场景:明确创建新实体时使用,如监听到Pod创建事件。

行为逻辑

  • 若实体已存在:不做任何操作。

  • 若实体不存在:创建新实体,覆盖所有字段。

示例

{
  "__domain__": "k8s",
  "__entity_type__": "k8s.pod",
  "__entity_id__": "abc123...",
  "__method__": "Create",
  "__first_observed_time__": 1700000000,
  "__last_observed_time__": 1700000000,
  "pod_name": "web-app-123",
  "namespace": "default"
}

Update - 更新实体

使用场景:大部分场景使用,包括定期全量更新、增量更新等。

行为逻辑

  • 覆盖除__first_observed_time__外的所有字段。

  • 对于__first_observed_time__

    • 若实体已存在:不修改原值。

    • 若实体不存在且事件包含该字段:使用事件中的值。

    • 若实体不存在且事件不包含该字段:使用__last_observed_time__的值。

示例

{
  "__domain__": "apm",
  "__entity_type__": "apm.service",
  "__entity_id__": "def456...",
  "__method__": "Update",
  "__last_observed_time__": 1700000100,
  "service_name": "payment-service",
  "status": "running",
  "instance_count": 3
}

Expire - 实体过期

使用场景:实体明确被删除时使用,如监听到Pod删除事件。

行为逻辑

  • 标记实体的__deleted__true。

  • 更新__last_observed_time__

  • 查询时不再附加计算KeepAlive时间。

时间范围变化

  • 过期前:[FirstObservedTime, LastObservedTime + KeepAliveSeconds]

  • 过期后:[FirstObservedTime, LastObservedTime]

示例

{
  "__domain__": "k8s",
  "__entity_type__": "k8s.pod",
  "__entity_id__": "abc123...",
  "__method__": "Expire",
  "__last_observed_time__": 1700000200
}

Delete - 删除实体(谨慎使用)

使用场景:功能被禁用或需要实体在全周期都不可见的场景。

行为逻辑

  • 直接从底层数据库删除实体记录。

  • 任何时间段的查询都无法找到该实体。

⚠️ 注意:这是不可逆操作,请谨慎使用。

Revise - 订正实体(谨慎使用)

使用场景:数据写错需要强制订正时使用,通常与Delete组合使用。

行为逻辑

  • 直接覆盖所有字段,包括__first_observed_time__

  • 不考虑原有数据状。

写入最佳实践

推荐方式:增量事件 + 定期全量

核心思路是以增量事件为主,定期全量写入作为数据校对和容错机制。

配置建议

  • 增量事件:实时或近实时写入。

  • 定期全量:频率建议1小时及以上。

  • KeepAlive时间:根据业务需求设置,通常10分钟到1小时。

  • 说明:KeepAlive时间根据上报周期进行调整,建议不低于1个上报周期。此外定期上报周期建议不低于5分钟,避免频繁更新影响查询性能。

实际场景应用

场景1:云产品资源同步

使用RMC提供的增量+全量同步机制:

增量事件处理

  • 监听资源创建事件 -> 使用Create方法

  • 监听资源更新事件 -> 使用Update方法

  • 监听资源删除事件 -> 使用Delete方法

全量事件处理

  • 定期全量同步 -> 统一使用Update方法

  • KeepAlive 根据业务需求灵活配置

// 云资源创建事件
{
  "__domain__": "acs",
  "__entity_type__": "acs.ecs.instance",
  "__entity_id__": "i-bp1234567890",
  "__method__": "Create",
  "__first_observed_time__": 1700000000,
  "__last_observed_time__": 1700000000,
  "__keep_alive_seconds__": 86400,
  "instance_id": "i-bp1234567890",
  "instance_name": "web-server-01",
  "instance_type": "ecs.c6.large",
  "status": "Running"
}

场景2:Agent采集资产数据

使用LoongCollector采集K8s资产数据的模式:

启动时全量采集

  • 进程启动时采集一次全量数据

  • 后续每1小时采集一次全量数据

  • 使用Update方法写入

增量事件订阅

  • 订阅K8s API事件

  • 根据事件类型转换为对应的写入方法

  • 实时写入增量变化

// K8s Pod全量采集
{
  "__domain__": "k8s",
  "__entity_type__": "k8s.pod",
  "__entity_id__": "pod-web-app-123",
  "__method__": "Update",
  "__last_observed_time__": 1700000400,
  "__keep_alive_seconds__": 3600,
  "pod_name": "web-app-123",
  "namespace": "production",
  "node_name": "worker-01",
  "pod_ip": "10.0.1.100",
  "labels": {
    "app": "web-app",
    "version": "v1.0"
  }
}

不推荐的写入方式

纯事件管理

  • 问题:需要查询所有时间段数据,性能较差。

  • 场景:仅在实体生命周期完全由事件驱动时考虑。

纯全量写入

  • 问题:数据量大,实体发现延迟高。

  • 建议:如无法识别增量,控制写入频率为10分钟或1小时。

写入接口

实体写入通过SLS Log协议进行,写入到对应workspace${workspace}__entity日志库即可。后台会自动同步到 EntityStore 存储引擎。

支持的写入方式:

  1. ETL转换写入:通过数据加工任务转换和写入。

  2. ScheduledSQL计算写入:通过定时SQL查询计算并写入。

  3. API直接上报:通过SDKAPI直接写入日志库。

注意事项

  1. 数据一致性:确保同一实体的__domain____entity_type____entity_id__组合在整个生命周期内保持一致。

  2. 时间戳管理:所有时间字段使用秒级 Unix 时间戳,确保时区一致性。

  3. 方法选择:根据实际业务场景选择合适的 Method,避免不必要的Delete操作。

  4. 性能考虑:合理设置 KeepAlive 时间,平衡数据保留需求和查询性能。

  5. JSON格式:确保 JSON 字段的键值按字母顺序序列化,提高索引效率。

  6. 实体数量:建议单个 EntitySet 的实体数量不超过1万,系统中整体实体数量不超过100万,避免影响查询性能和写入。