Skip to main content
内核为所有正在运行的服务提供一个共享的键值存储。服务可以用它保存一些必须通过内核读取或更新的小型状态,例如缓存值、服务状态,或与其他服务共享的数据。 KV 中的值是原始字节。内核不会为它们指定数据格式,因此您可以将某个值编码为 JSON、文本、二进制结构,或您的服务所理解的任何其他格式。

存储模型

该存储采用两级结构:
例如:
KV 存储属于内核,而不属于某个具体的服务实例。所有连接到同一个内核的服务都使用同一个存储。服务命名空间只是一种组织上的约定,并不是权限边界。在当前实现中,除非某个命名空间是只读的,否则一个服务可以读写另一个服务的命名空间。 按照约定,请使用您服务专属的命名空间,通常从服务名称派生而来。如果多个服务有意共享同一个命名空间,请为它们约定好的键名和值格式编写文档。

操作

这些操作由协议中描述的 Host 契约定义。

读取一个值

读取请求包含: 响应包含一个 found 布尔值和一个 value 字节序列。键不存在并不是一种错误:foundfalse,且不返回值。 读取一个值不会创建其命名空间或键。

写入一个值

写入请求包含相同的 namespacekey 字段,此外还有: 写入一个已存在的键会替换其值。当写入被拒绝时(例如该命名空间是只读的),该操作会返回一个错误字符串。

删除一个值

删除请求包含 namespacekey。删除一个已存在的键会移除其值;删除一个不存在的键会成功执行,且不会改变该存储。 当删除被拒绝时(包括尝试从只读命名空间中删除),该操作会返回一个错误字符串。

列出键

列表请求包含一个 namespace 字段:
  • namespace 非空时,响应包含该命名空间中当前存在的键。
  • namespace 为空时,响应包含存储中当前存在的所有命名空间的名称。
返回的名称由内核排序。列出操作不会返回值。

生命周期与持久性

内核在创建服务管理器时创建该 KV 存储。该存储位于内存中:
  • 停止或重启一个服务不会清除其 KV 值;
  • 加载同一服务的新版本不会清除其 KV 值;以及
  • 重启内核会创建一个新的空存储,并清除所有值。
因此 KV 并不是一个持久化数据库。如果数据必须在内核重启后依然存在,请将其持久化到别处,并将 KV 视为运行时状态或共享缓存。

并发与值的所有权

内核会对每一次 KV 操作进行同步。当多个服务并发调用时,读取、写入、删除和列出操作都是安全的。 不存在事务或比较并交换(compare-and-swap)操作。类似”读取、修改、写入”这样的一系列操作,相对于另一个服务而言并不是原子的。如果多个服务更新同一个键,请协调好所有权,或者设计好值的结构,使丢失更新不会破坏应用状态。 内核在存储和读取字节值时都会进行拷贝。在写入之后修改一个字节缓冲区不会改变已存储的值,修改一个返回的缓冲区也不会改变存储中的值。

sys 命名空间

sys 是一个由内核管理的只读命名空间。服务可以读取和列出它,但通过服务的 host 边界进行的写入和删除都会返回错误。 内核使用该命名空间保存主机管理的状态,包括服务注册表信息——例如,sys/service/catalog/<name> 用于保存扫描到的包元数据,sys/service/<instance-id> 用于保存某个运行实例已注册的路由和传输。请不要写入 sys,也不要依赖未在文档中说明的系统键,除非内核文档明确定义了它们。

后端行为

WASM 和 gRPC 服务使用相同的 KV 契约:
  • WASM 服务通过生成的 host 绑定访问该存储;以及
  • gRPC 服务通过内核的 host 回调通道访问该存储。
两种运行时类型的存储语义是相同的。运行时改变的是请求跨越服务边界的方式,而不是内核存储或返回值的方式。static 服务没有代码,永远不会自己调用这些操作。

推荐用法

请使用可预测的命名空间和键方案:
请保持值足够小以适应调用路径,并在其他组件使用之前就定义好其编码方式。在解码某个值之前先检查 found 结果,处理好写入和删除的错误,并且不要把一次成功的写入当作持久化保证。