)
Dapr 状态存储行为规范解读并发模型、一致性模型与配置探测API-005 决策记录【免费下载链接】daprDapr is a portable runtime for building distributed applications across cloud and edge, combining event-driven architecture with workflow orchestration.项目地址: https://gitcode.com/GitHub_Trending/da/dapr本篇技术指南围绕 Dapr 官方决策记录 API-005: State Store Behavior 展开系统讲解 Dapr 对状态存储State Store组件行为的显式规范乐观并发模型ETag 实现的 first-write / last-write、一致性模型eventual / strong、Actor 事务的 ACID 要求以及面向组件的配置探测configuration probe机制。读者读完本文后将能理解 Dapr 状态存储 API 中concurrency、consistency、etag等请求要素的语义与默认值并能在实际接入 Dapr 状态存储组件或实现 Dapr-compatible 组件时正确选择与声明行为。一、决策背景为何要把组件行为写进规范API-005 记录了 Dapr 在演进 API 规范过程中的一项明确决策不仅要把 API 的接口形状shape定义清楚还要把组件的运行时行为behavior显式写入规范并保证实现与之对齐。文档原文将其定位为随着我们继续夯实 API 规范需要在规范中显式定义组件行为并确保这些行为在我们的实现中落实并预期后续会产出更多同类决策记录。从仓库的决策记录索引 docs/decision_records/decision_records.md 可以看到状态存储相关的决策记录形成了一个系列API-001State store API design、API-005State store behavior、API-008Multi State store API design、API-011State Store APIs Parity。本文关注的 API-005 处于该系列的中枢位置——它承接 API-001 的接口设计为后续多状态存储与 API 对齐决策奠定了行为语义基础。该记录当前状态为Proposed提议中意味着其内容属于被采纳进入规范的设计方向其中多数行为语义已在当前仓库的 proto 定义与运行时实现中落地下文将逐一对照。二、并发模型ETag 与 first-write / last-write2.1 两种乐观并发策略API-005 规定 Dapr 支持两种乐观并发optimistic concurrency风格first-write wins首写胜出并发写入时最先提交的写入生效后续基于旧版本的写入被拒绝。该策略通过ETag实现——每次写入请求携带状态项的版本标识状态存储在校验版本不匹配时拒绝写入。last-write wins末写胜出并发写入时最后一次写入覆盖先前写入不做版本校验。文档同时明确了默认策略默认采用 last-write wins。这意味着在不显式携带 ETag、不声明并发意图的情况下Dapr 的写入行为是后写覆盖这与大多数 Key-Value 状态存储的朴素写入语义一致也保证了对并发要求不高的应用可以零成本接入。2.2 源码对照proto 中的并发枚举与 ETag 载体API-005 关于并发的决策已经在协议层落地。在 dapr/proto/common/v1/common.proto 中StateItem状态条目同时承载etag与options两个字段// StateItem represents state key, value, and additional options to save state. message StateItem { string key 1; bytes value 2; // The entity tag which represents the specific version of data. // The exact ETag format is defined by the corresponding data store. Etag etag 3; mapstring,string metadata 4; // Options for concurrency and consistency to save the state. StateOptions options 5; } // Etag represents a state item version message Etag { string value 1; }其中StateOptions用枚举显式定义了并发模型的取值与 API-005 中 first-write / last-write 的措辞完全一致message StateOptions { enum StateConcurrency { CONCURRENCY_UNSPECIFIED 0; CONCURRENCY_FIRST_WRITE 1; CONCURRENCY_LAST_WRITE 2; } StateConcurrency concurrency 1; StateConsistency consistency 2; }注意两点细节CONCURRENCY_UNSPECIFIED作为 0 值存在即未指定时由 Dapr 运行时按默认策略last-write wins处理。ETag 的具体格式由对应的数据存储定义The exact ETag format is defined by the corresponding data storeDapr 只将其视为不透明的版本字符串透传给组件这与 API-005first-write wins 通过 ETag 实现的决策一致——ETag 是跨存储的通用版本载体校验逻辑下放到各状态存储组件。2.3 请求侧如何表达并发意图proto 枚举在 gRPC 网关层被转换为组件接口使用的字符串值。见 pkg/api/grpc/util.gofunc stateConcurrencyToString(c commonv1pb.StateOptions_StateConcurrency) string { switch c { case commonv1pb.StateOptions_CONCURRENCY_FIRST_WRITE: return first-write case commonv1pb.StateOptions_CONCURRENCY_LAST_WRITE: return last-write } return }转换后的值被填充进组件层的state.SetStateOption见 pkg/api/grpc/grpc.go 中 SaveState/BulkSaveState 的映射逻辑req.Options state.SetStateOption{ Consistency: stateConsistencyToString(s.GetOptions().GetConsistency()), Concurrency: stateConcurrencyToString(s.GetOptions().GetConcurrency()), }也就是说从用户请求HTTP/gRPC到组件调用的完整链路为请求携带concurrency: first-write→ protoStateOptions.concurrency→stateConcurrencyToString映射 → 组件层SetStateOption.Concurrency→ 状态存储实现校验 ETag。上述字符串映射行为由 pkg/api/grpc/util_test.go 中的TestConcurrency用例覆盖。2.4 ETag 错误的不可重试语义当并发校验失败时组件会返回 ETag 类错误。从源码看Dapr 运行时将其区分为两种不可重试的永久性错误pkg/components/state/bulk.goETagMismatchETag 与存储中当前版本不一致典型 first-write 冲突场景ETagInvalidETag 本身非法。批量写入时如果所有失败项都源于 ETag 错误则整批操作被标记为backoff.Permanent永久错误不再重试因为版本冲突重试也不会成功反之只要存在非 ETag 的失败项对应项会被纳入重试列表。可插拔组件pluggable components侧也做了同样的错误码映射pkg/components/state/pluggable.goETagMismatch对应 gRPCFailedPreconditionETagInvalid对应InvalidArgument。这与 first-write wins 的语义严格自洽并发冲突是业务结果而非瞬时故障不应被重试掩盖。三、一致性模型eventual 与 strong3.1 两种一致性级别与默认值API-005 对一致性模型的决策如下Dapr 同时支持**最终一致性eventual consistency与强一致性strong consistency**两种级别Actor 的状态操作总是使用强一致性保证 Actor 单实例串行执行模型下读取到的状态是最新的除 Actor 外服务默认使用最终一致性。该决策在 proto 中同样以枚举固化dapr/proto/common/v1/common.protoenum StateConsistency { CONSISTENCY_UNSPECIFIED 0; CONSISTENCY_EVENTUAL 1; CONSISTENCY_STRONG 2; }gRPC 网关层将其转换为组件层字符串pkg/api/grpc/util.goCONSISTENCY_EVENTUAL → eventual、CONSISTENCY_STRONG → strong。转换逻辑同样有单测覆盖TestConsistency。3.2 在 HTTP API 中的表现一致性级别不仅是 gRPC 请求的字段也出现在 HTTP 状态 API 中。以读取状态为例pkg/api/http/http.go 的onGetState从 URL 查询参数读取一致性意图并构造组件请求consistency : r.URL.Query().Get(consistencyParam) req : state.GetRequest{ Key: k, Options: state.GetStateOption{ Consistency: consistency, }, Metadata: metadata, }也就是说HTTP 侧通过consistencystrong|eventual查询参数声明一致性级别省略时即落到 API-005 规定的默认策略。从实现角度看最终是否真正提供强一致性读/写取决于所接入的状态存储组件对consistency选项的实际支持程度——这是决策记录状态存储实例返回其当前实例的具体配置见第六节配置探测所要解决的问题运行时无法凭空制造一致性只能把意图透传给组件并感知组件的能力。3.3 Actor 强一致性的实现约束Actor 状态路径对一致性有额外硬性要求。从 pkg/actors/state/state.go 的stateStore()方法可见Actor 状态存储必须同时具备ETag 能力FeatureETag与事务能力FeatureTransactional否则直接报errStateStoreNotConfiguredstore, ok : storeS.(Backend) if !ok || !contribstate.FeatureETag.IsPresent(store.Features()) || !contribstate.FeatureTransactional.IsPresent(store.Features()) { return , nil, errors.New(errStateStoreNotConfigured) }这从实现层面印证了 API-005 的两条决策的组合效果Actor 需要强一致性ETag 校验 事务提交来支撑其状态模型因此只有同时支持这两类能力的存储才能作为 Actor 状态存储。这也解释了为什么文档将 Actor 事务单独列为一项决策见下节。四、Actor 事务ACID 要求与隔离级别弹性4.1 决策内容API-005 对 Actor 事务的规定Dapr 兼容的 Actor 状态存储必须支持 ACID 事务Dapr 当前不强制规定具体的事务隔离级别但当确有需要时可以很容易地通过Config annotation补充隔离级别等约束。必须支持 ACID是一个组件准入级别的强约束与上一节FeatureTransactional的运行时校验互为印证ACID 是 Actor 状态存储的必备能力而非可选项。而隔离级别被刻意留白是为了避免过早绑定特定存储的事务语义不同数据库对隔离级别的实现差异很大把弹性保留给 Config annotation 机制。4.2 事务操作的运行时路径Actor 事务对应的组件接口是TransactionalStore运行时通过store.Multi(ctx, stateReq)一次性提交多个状态操作增删改组合见 pkg/actors/state/state.go 中的事务执行逻辑。值得注意的两点实现细节若组件实现了TransactionalStoreMultiMaxSize接口Dapr 会读取其MultiMaxSize()当单次事务操作数超过上限时拒绝提交ErrTransactionsTooManyOperations这是对存储实例返回其具体配置配置探测在事务维度的一种补充形态事务调用同样被包裹在resiliency.ComponentOutboundPolicy(storeName, resiliency.Statestore)策略执行器中即 Actor 事务也受运行时弹性策略重试/超时/熔断管理。五、Config annotation请求级约束与策略表达5.1 决策内容API-005 提出用户请求 payload 可以携带一个可选的 config annotation配置注解/元素用于表达作用于本次调用的各种约束与策略包括类别可表达的策略/约束说明并发模型first-write/last-write覆盖默认的 last-write wins一致性模型strong/eventual覆盖默认的 eventualActor 除外重试策略interval重试间隔重试策略pattern重试模式linear线性或exponential指数原文写作 expotential重试策略circuit-breaker timeout熔断器在开启状态下被重置恢复前的超时时间这套设计的核心思想是**意图与调用绑定**同一应用的不同业务路径可能对状态操作有不同的并发/一致性诉求与其在组件级全局配置不如在请求级按需声明。文档同时指出未来版本可能通过 application channel 支持调用限流call throttling属于预留方向。5.2 与当前实现的关系需要说明的是API-005 中 Config annotation 描述的是一种覆盖并发/一致性/重试策略的统一注解设想在当前的 proto 与运行时实现中等价能力被拆解为两部分落地并发/一致性通过StateItem.optionsStateOptions.concurrency/StateOptions.consistency见第二节在每次状态操作上显式声明这正是config 附在请求上语义的协议化实现重试/熔断通过 Dapr 的 Resiliency 规范PolicyDefinition在组件出站策略层面配置运行时以resiliency.NewRunner执行见 pkg/components/state/bulk.go 与 pkg/api/grpc/grpc.go 中ComponentOutboundPolicy的调用方式。从决策落地角度看可以认为 API-005 关于请求级表达约束与策略的目标已经在协议与弹性策略两个层面得到满足而circuit-breaker timeout这类策略在 Resiliency 组件中被实现为熔断器配置项与决策记录所述语义一致。六、状态存储配置探测Configuration Probe6.1 决策内容API-005 为组件能力可被运行时发现设计了一项明确机制——配置探测Dapr 兼容的状态存储必须提供一个端点来应答配置探测请求返回内容除其他项外包括支持的并发模型supported concurrency model支持的一致性模型supported consistency model状态存储实例必须返回当前实例的具体配置而非仅返回类型级别的通用能力不在范围内的要求不要求状态存储动态应用新配置即探测返回的是静态声明的能力运行时不应指望组件热切换行为。6.2 设计意图与实现对照配置探测解决的是能力协商问题Dapr 面对异构状态存储生态Redis、Cosmos DB、PostgreSQL、Cassandra 等各有不同的并发与一致性支持运行时无法假设所有组件能力相同。通过探测端点运行时可以获知这个实例支持 first-write 吗支持 strong consistency 吗从而在调用前校验用户意图是否可被满足或在能力不满足时给出明确错误。从当前仓库看组件能力的静态声明已通过Features 机制部分落地组件通过Features()上报能力位如上文FeatureETag、FeatureTransactional见 pkg/actors/state/state.go运行时据此校验 Actor 状态存储的准入条件。这可以视为配置探测思想的实现雏形而 API-005 中返回支持的并发/一致性模型的完整探测端点属于决策记录中标注为后续落地的部分见下节落地路径需要结合各状态存储组件的实现持续对齐。决策记录将动态应用新配置明确划出范围意味着探测是一次性的能力快照组件行为在运行期内保持稳定这简化了运行时与组件之间的契约。七、落地路径与现状对照API-005 在Decisions / Dapr一节给出了两条落地任务更新状态存储 API 规范使规范反映上述全部决策创建 backlog issue逐项实现上述决策。对照当前仓库可以梳理出该决策的落地进度决策项当前仓库落地情况first-write / last-write 并发语义已落地StateOptions.StateConcurrency枚举dapr/proto/common/v1/common.protoETag 版本载体已落地StateItem.etagEtagmessage格式由存储定义eventual / strong 一致性语义已落地StateOptions.StateConsistency枚举字符串映射与 HTTP/gRPC 透传已落地pkg/api/grpc/util.go、pkg/api/grpc/grpc.go、pkg/api/http/http.goETag 冲突错误不可重试已落地pkg/components/state/bulk.go、pkg/components/state/pluggable.goActor 强一致 ACID 事务要求已落地FeatureETagFeatureTransactional准入校验pkg/actors/state/state.go请求级策略表达并发/一致性已落地StateItem.options按请求声明重试/熔断策略已落地ResiliencyPolicyDefinitionComponentOutboundPolicy完整配置探测端点部分落地组件 Features 机制已提供能力静态声明完整探测端点待推进此外测试资产也可以作为行为契约的佐证gRPC 层测试显式构造了Concurrency: first-write、Consistency: strong的请求见 pkg/api/grpc/grpc_test.go说明用户在请求中声明并发/一致性意图的链路是被测试锁定、可验证的。八、给开发者的实践要点基于 API-005 的决策与当前实现接入 Dapr 状态存储时建议关注以下要点默认行为普通服务状态操作默认last-write winseventual consistencyActor 状态操作始终strong consistency且要求存储支持 ACID 事务与 ETag。接入前先确认所选存储组件满足 Actor 准入能力。需要防止覆盖时用 first-write ETag先读取拿到etag写入时回传若并发修改发生会收到ETagMismatch/FailedPrecondition类错误。该错误不会被重试吞掉应作为业务冲突处理。需要读到最新时用 strong consistencygRPC 请求在StateOptions中声明HTTP 请求通过consistency查询参数声明注意组件对强一致性的实际支持程度决定其最终效果。重试与熔断不写在状态请求里在 Dapr 弹性Resiliency配置中针对 statestore 出站策略声明重试间隔、模式与熔断超时运行时统一执行。不要把动态切换组件行为当作前提API-005 明确配置探测不做动态应用组件能力在运行期内视为静态快照架构设计时应据此规划能力评估时机。作为一份仍处于 Proposed 状态的决策记录API-005 的价值在于它把状态存储的行为契约从隐式约定提升为显式规范为 API-001 的接口设计补充了语义层也为 API-008多状态存储与 API-011API 对齐的后续决策提供了基准。理解这份文档就理解了 Dapr 状态管理在并发、一致性、事务与能力协商四个维度上的设计取舍。【免费下载链接】daprDapr is a portable runtime for building distributed applications across cloud and edge, combining event-driven architecture with workflow orchestration.项目地址: https://gitcode.com/GitHub_Trending/da/dapr创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考