news 2026/10/12 2:16:14

Easegress Custom Data 全指南:用 CustomDataKind 与 CustomData 实现集群级任意数据持久化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Easegress Custom Data 全指南:用 CustomDataKind 与 CustomData 实现集群级任意数据持久化
  • 云原生
  • API网关
  • 微服务
  • 服务网格

【免费下载链接】easegress

A Cloud Native traffic orchestration system. (CNCF Project)

项目地址:https://gitcode.com/gh_mirrors/ea/easegress
点击查看免费下载

导读:Easegress 的 Custom Data 特性为集群内的"任何"数据提供了一套统一、可校验、可订阅的持久化存储,供 Pipeline、过滤器等各类组件共享与复用。本文以官方文档为骨架,结合仓库源码,系统讲解CustomDataKind的类型定义、CustomData的数据约束、完整的 v2 REST API 与egctl操作命令,并深入到 etcd 存储布局、JSON Schema 校验与事务批量更新等底层实现,帮助你从使用到原理全面掌握这一开发扩展能力。

为什么需要 Custom Data

Easegress 是一个云原生流量编排系统,其内部存在大量"组件之外"的附属数据需要持久化:例如某个过滤器要保存自身的运行状态、某个 Controller 要写入业务自定义的元数据等。这些数据有两种特点:

  • Schema 不固定:不同组件持久化的数据结构千差万别;
  • 需要跨成员共享:集群中所有节点应当看到同一份数据。

为此,Easegress 提供了Custom Data特性:它实现了一个存储"任何数据"(any data)的通用能力,可以被其他组件直接用来做数据持久化。由于不同组件的数据结构不同,必须通过CustomDataKind(自定义数据类型)来区分彼此。整个特性由三部分构成:

  • CustomDataKind:数据类型的"定义",规定了标识字段与数据校验规则;
  • CustomData:实际存储的数据项,是与 Kind 关联的键值映射;
  • API / egctl:对外暴露的增删改查与批量操作入口。

从源码看,存储层实现在 pkg/cluster/customdata/customdata.go,API 层路由注册在 pkg/api/customdata.go,客户端命令则位于 cmd/client 下。

CustomDataKind:自定义数据类型

CustomDataKind用于描述"某一类数据"的规范:它定义了这类数据项的标识字段(ID)以及可选的 JSON Schema 校验规则。

定义示例

下面的 YAML 定义了一个名为kind1的 CustomDataKind:

name: kind1 kind: CustomDataKind idField: name jsonSchema: type: object properties: name: type: string required: - name

各字段含义如下:

字段必填说明
name是该 Kind 的名称,也是后续数据项的"类型名",在 API 路径中作为{kind name}使用
kind是固定为CustomDataKind,标识这是一份类型定义资源
idField否数据项中用作唯一标识的字段名,默认值为name
jsonSchema否一段 JSON Schema 规范,若提供,则该 Kind 下所有数据项在写入前都会按此规则校验

在源码 pkg/cluster/customdata/customdata.go 中,Kind结构体的定义与此一一对应:

type Kind struct { Name string `json:"name" jsonschema:"required"` IDField string `json:"idField,omitempty"` JSONSchema dynamicobject.DynamicObject `json:"jsonSchema,omitempty"` }

idField 的默认与解析逻辑

idField的取值逻辑非常直接:为空时回退为name。对应源码 GetIDField 与 dataID:

func (k *Kind) GetIDField() string { if k.IDField == "" { return "name" } return k.IDField } func (k *Kind) dataID(data Data) string { var id string if k.IDField == "" { id, _ = data["name"].(string) } else { id, _ = data[k.IDField].(string) } return id }

即:数据项的 ID 就是其idField字段的值,同一 Kind 内 ID 必须唯一。测试用例 TestDataID 验证了默认name与自定义key两种场景下的 ID 提取结果。

JSON Schema 的两层校验

JSON Schema 校验发生在两个时机,且都使用gojsonschema库:

  1. 定义 Kind 时:PutKind会先加载kind.JSONSchema并调用gojsonschema.NewSchema校验 Schema 本身是否合法,非法 Schema 直接拒绝创建(customdata.go)。
  2. 写入数据时:PutData与BatchUpdateData都会将数据项与 Schema 比对,校验失败返回validation failed错误(customdata.go)。

对应的测试 TestPutKind 覆盖了非法 Schema、重复创建、更新不存在 Kind 等失败路径,TestPutData 则覆盖了空 ID、校验失败、校验通过等场景。

创建 Kind 的命令

官方文档给出的两种方式等价:

egctl create -f kind1.yaml egctl apply -f kind1.yaml

两者都支持从文件或 stdin 读取 YAML:create对应 HTTPPOST,apply同样通过POST路径落库(详见下文"批量更新"说明)。

CustomData:数据项

CustomData本质上是一个 map:键必须是字符串,值可以是任意合法的 JSON 值;嵌套 map 的键同样必须是字符串。

在源码中,数据项被定义为dynamicobject.DynamicObject,即map[string]interface{}(customdata.go、dynamicobject.go)。dynamicobject.go 中的UnmarshalYAML还特别处理了一个细节:从 YAML 反序列化时嵌套 map 可能变成map[interface{}]interface{},标准json包无法处理,因此会递归转换为map[string]interface{}——这正是文档要求"嵌套 map 的键必须是字符串"的根本原因。

数据项必须包含 ID 字段

每个CustomData数据项必须包含其所属CustomDataKind定义的idField。以上文kind1为例,其idField为name,因此数据项必须携带name字段作为标识。

写入时若 ID 为空,PutData会直接报错data id is empty(customdata.go)。

数据项示例

以下是一个kind1类型的数据项:

name: data1 field1: 12 field2: abc field3: [1, 2, 3, 4]
  • name: data1是 ID 字段,取值为data1;
  • field1、field2、field3是任意业务字段,值分别为数字、字符串和数组,均为合法 JSON 值。

数据项的键对应 etcd 存储键:同一 Kind 的数据存储在以/custom-data/{kind}/为前缀的键空间下,每条数据以idField的值作为键名(见下文"存储布局")。

REST API 速查

Custom Data 的 REST API 位于 Easegress API Server 的/apis/v2前缀下(见 pkg/api/api.go 中的APIPrefixV2常量,默认端口2381,即http://{ip}:{port}/apis/v2)。路由注册在 pkg/api/customdata.go,完整端点如下。

CustomDataKind 相关 API

操作方法URLBody
创建 CustomDataKindPOST/apis/v2/customdatakindsKind 定义(YAML)
更新 CustomDataKindPUT/apis/v2/customdatakindsKind 定义(YAML)
查询单个 Kind 定义GET/apis/v2/customdatakinds/{kind name}-
列出所有 Kind 定义GET/apis/v2/customdatakinds-
删除一个 KindDELETE/apis/v2/customdatakinds/{kind name}-

值得注意:GET /apis/v2/customdatakinds与GET /apis/v2/customdatakinds/{kind name}返回的每个 Kind 都附带len字段(该 Kind 当前的数据条数),由DataLen计算得出(customdata.go、customdata.go)。这也是egctl get customdatakind能打印出 DATA-NUM 列的来源。

CustomData 相关 API

操作方法URLBody
创建 CustomDataPOST/apis/v2/customdata/{kind name}数据项定义(YAML)
更新 CustomDataPUT/apis/v2/customdata/{kind name}数据项定义(YAML)
查询单个数据项GET/apis/v2/customdata/{kind name}/{data id}-
列出某 Kind 的所有数据GET/apis/v2/customdata/{kind name}-
删除单个数据项DELETE/apis/v2/customdata/{kind name}/{data id}-
删除某 Kind 的所有数据DELETE/apis/v2/customdata/{kind name}-
批量更新(Change Request)POST/apis/v2/customdata/{kind name}/items变更请求(YAML)
批量删除(某 Kind 全部数据)DELETE/apis/v2/customdata/{kind name}/items-

创建/更新的语义细节

从 pkg/api/customdata.go 与 pkg/api/customdata.go 可以看到:

  • create(POST)走PutData(kind, data, false):数据已存在则报错existed;
  • update(PUT)走PutData(kind, data, true):数据不存在则报错not found;
  • 创建成功后响应头Location会携带新资源的完整路径,响应码为201 Created。

同理,Kind 的创建/更新也遵循这一"幂等区分"约定:PutKind(kind, false)不允许覆盖已存在的 Kind,PutKind(kind, true)不允许更新不存在的 Kind(customdata.go)。

批量更新:Change Request

对于需要一次性增删多条数据的场景,Custom Data 提供了批量更新接口:POST /apis/v2/customdata/{kind name}/items,请求体是一个 Change Request(YAML),结构如下:

name: kind1 kind: CustomData rebuild: false delete: [data1, data2] list: - name: data3 field1: 12 - name: data4 field1: foo

语义说明:

字段默认值说明
name-目标CustomDataKind的名称
kind-固定为CustomData
rebuildfalse为true时,先删除该 Kind 下的所有既有数据项,再处理list中的数据
delete-要删除的数据项 ID 数组;当rebuild为true时该字段被忽略
list-要创建或更新的数据项数组(写入采用 upsert 语义,同一 ID 直接覆盖)

底层实现中,delete与list的写操作被放进**一个 etcd 事务(STM)**内原子执行,要么全部成功、要么全部失败(customdata.go),这保证了批量操作的一致性。rebuild的全量清理则由 API 层先调用DeleteAllData完成(pkg/api/customdata.go)。

客户端同样支持以 Change Request 形式创建或应用:

egctl create -f customdata-change-request.yaml egctl apply -f customdata-change-request.yaml

其中name是CustomDataKind名称,kind为CustomData。

egctl 实战操作

egctl 同时保留了新旧两套命令:老式命令(v1 风格)在源码中已标记为(Deprecated),新式命令(v2 风格,与egctl get/describe/create/apply/delete/edit统一资源模型)为推荐用法。

新版命令(推荐)

新式命令在 cmd/client/commandv2 中注册,资源定义见 cmd/client/resources/customdata.go 与 cmd/client/resources/customdatakind.go:

# Kind 操作 egctl get customdatakind # 列出所有 Kind egctl get customdatakind kind1 # 查询单个 Kind egctl describe customdatakind kind1 # 描述单个 Kind egctl delete customdatakind kind1 # 删除一个 Kind # 数据操作(customdata 支持两级参数:<kind> [<id>]) egctl get customdata kind1 # 列出 kind1 的所有数据 egctl get customdata kind1 data1 # 查询 kind1 下的 data1 egctl describe customdata kind1 data1 egctl delete customdata kind1 data1 # 删除单条数据 egctl delete customdata kind1 --all # 删除 kind1 的全部数据 # 创建 / 应用 / 编辑 egctl create -f kind1.yaml # 创建 Kind(或通过 Change Request 写数据) egctl apply -f kind1.yaml egctl edit customdata kind1 # 以批量形式编辑 kind1 的所有数据 egctl edit customdata kind1 data1 # 编辑单条数据

几个值得注意的行为(均有源码依据):

  • get customdata <kind>默认以表格输出,每行仅展示该 Kind 的 ID 字段值;describe则打印完整字段(customdata.go)。
  • edit不允许修改 ID 字段:编辑保存时会比较新旧数据的idField值,不一致则报错edit cannot change the <idField> of custom data(customdata.go)。
  • get/describe customdatakind会以表格展示NAME / ID-FIELD / JSON-SCHEMA / DATA-NUM四列(customdatakind.go)。
  • 所有 v2 命令的 URL 均由 cmd/client/general/urls.go 统一定义,前缀为/apis/v2。

旧版命令(已弃用)

老式命令定义在 cmd/client/command/customdata.go,Short描述中明确标注(Deprecated),但功能仍完整可用:

egctl custom-data-kind list # 列出所有 Kind egctl custom-data-kind get <kind> # 查询单个 Kind egctl custom-data-kind create -f <kind file> # 创建 Kind egctl custom-data-kind update -f <kind file> # 更新 Kind egctl custom-data-kind delete <kind> # 删除 Kind egctl custom-data list <kind> # 列出某 Kind 的数据 egctl custom-data get <kind> <id> # 查询单条数据 egctl custom-data create <kind> -f <data file> # 创建数据 egctl custom-data update <kind> -f <data file> # 更新数据 egctl custom-data batch-update <kind> -f <change-request file> # 批量更新 egctl custom-data delete <kind> <id> # 删除单条数据

底层实现:存储布局、并发与监听

etcd 存储布局

Custom Data 最终落在集群的 etcd 中,键空间由 pkg/cluster/layout.go 定义:

customDataKindPrefix = "/custom-data-kinds/" customDataPrefix = "/custom-data/"
  • Kind 定义存储键:/custom-data-kinds/{kind name};
  • 数据项存储键:/custom-data/{kind name}/{data id}。

API Server 初始化时通过Layout()获取这两个前缀并构造 Store(pkg/api/server.go),后续所有读写都基于cluster.Cluster接口完成(GetRaw / Put / Delete / STM 等)。

并发安全与一致性

  • 单条写:PutData的"已存在/不存在"检查基于先读后写;批量写则通过cluster.STM(etcd 事务)包裹,确保delete与list原子生效。
  • 删除 Kind 的级联行为:DeleteKind会先调用DeleteAllData清空该 Kind 下所有数据,再删除 Kind 定义本身,避免产生悬挂数据(customdata.go)。
  • DELETE /apis/v2/customdatakinds会一次删除全部 Kind 及其数据(DeleteAllKinds),属于高危操作。

数据变更监听:Watch

除增删改查外,Store 还提供了Watch(ctx, kind, onChange)方法,用于持续监听某个 Kind 的数据变更:它通过集群 Syncer 拉取该 Kind 前缀下的全部键值,每次变化都会以完整数据切片回调onChange(customdata.go)。这为"数据变更驱动业务逻辑"(例如配置下发、规则同步)提供了标准机制。

实践建议与限制

  1. 先定义 Kind,再写数据:PutData会先检查 Kind 是否存在,不存在直接返回kind %s not found;
  2. 善用jsonSchema做数据守门:它在 Kind 创建与数据写入两个阶段都会校验,是保证数据质量的第一道防线;
  3. 大批量更新优先走 Change Request:借助 etcd STM 保证原子性,避免逐条POST的非事务风险;
  4. rebuild: true会清空整个 Kind:适用于"整体替换"场景,但请确认不会误删存量数据;
  5. ID 一经写入不可通过edit修改:如需变更 ID,请走"删除 + 新建"流程。

说明:本文 API 与命令均基于当前仓库源码(pkg/api/customdata.go、pkg/cluster/customdata/customdata.go、cmd/client),默认 API Server 地址为http://{ip}:2381,实际以你的 Easegress 集群配置为准。

  • 云原生
  • API网关
  • 微服务
  • 服务网格

【免费下载链接】easegress

A Cloud Native traffic orchestration system. (CNCF Project)

项目地址:https://gitcode.com/gh_mirrors/ea/easegress
点击查看免费下载

相关推荐

上一篇:GrapesJS Trait Manager API 完全指南:组件设置面板的配置、事件与自定义类型开发
下一篇:Envoy HTTP/2 流控整数溢出修复深度解析:unconsumed_bytes_ 回绕问题

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/12 2:15:22

CLion+CMake+OpenOCD:构建树莓派Pico高效调试环境

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华