ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Dapr 1.5.3 发布详解:修复 Actor 状态存储缺失导致客户端调用失败的问题

Dapr 1.5.3 发布详解:修复 Actor 状态存储缺失导致客户端调用失败的问题 Dapr 1.5.3 发布详解修复 Actor 状态存储缺失导致客户端调用失败的问题【免费下载链接】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导读本文基于 docs/release_notes/v1.5.3.md 发布说明深入剖析 Dapr 1.5.3 中修复的 Actor 状态存储配置行为缺陷修复前只要没有配置 actor state store 组件所有 Actor API 都会报错修复后仅作为调用方client的服务即使没有 actor state store 也能正常使用 Actor API而注册 Actor 的服务依旧强制要求提供状态存储。读完本文你将掌握该问题的现象、根因、修复方案以及从源码层理解 Actor runtime 初始化、Actor 状态存储解析与热重载的完整机制并学会如何正确配置 actorStateStore 属性。一、问题背景Actor API 与 Actor 状态存储的关系在 Dapr 中Actor 模型的运行依赖于两个核心基础设施Placement 服务负责 Actor 实例在多个节点间的分布与负载均衡Actor 状态存储actor state store负责持久化 Actor 的状态数据是 Actor 有状态特性的基石。按 Dapr 的设计一个服务可以扮演两种角色角色行为是否必须有 Actor 状态存储Actor 宿主host注册并承载 Actor 类型处理 Actor 方法调用必须因为要保存 Actor 状态Actor 客户端client仅通过 Actor API 发起对远端 Actor 的调用不需要状态由宿主侧存储修复之前Dapr 在初始化 Actor API 时采用了一刀切的策略只要运行时中没有可用的 Actor 状态存储组件无论该服务是否注册了 Actor所有 Actor API 都会直接返回错误。这导致一类非常常见的场景被误伤——纯客户端应用例如 e2e 测试中的actortestclient仅仅为了调用其他服务的 Actor 而启动 daprd却因为没有配置状态存储而无法使用InvokeActor等 API。二、根因分析Actor API 初始化对状态存储的过度依赖发布说明明确指出根因在于The code that initializes the Actor API raises an error when there is no actor state storage component available, regardless of whether or not the service registers actors.即 Actor API 的初始化逻辑在“没有 Actor 状态存储组件”时就抛错而没有区分“该服务是否注册了 Actor”。从当前仓库源码可以印证这一历史问题被修复后的演进形态。在 pkg/actors/actors.go 的Init方法中Actor runtime 初始化时通过GetStateStoreActorWithRevision()探测状态存储_, a.hostingName, a.hostingRev, a.hostingActive a.compStore.GetStateStoreActorWithRevision() if !a.hostingActive { log.Info(Actor state store not configured - actor hosting disabled until one is configured, but invocation enabled) }注意这一行日志——actor hosting disabled until one is configured,but invocation enabled。这正是修复后引入的关键语义托管hosting能力与调用invocation能力被解耦。没有状态存储时Actor runtime 依然可以正常初始化并对外提供调用 API只是暂时无法托管 Actor 类型。三、解决方案按需禁用 Actor 托管而不是禁用整个 Actor API发布说明中的修复思路可总结为当没有 actor state store 时只要服务不注册 ActorActor API 就正常初始化并可用供纯客户端调用当服务注册了 Actor 却没有提供 actor state store 时Actor API 继续保持不可用这是正确行为因为宿主必须有状态存储未注册 Actor 的服务无论有没有 actor state storeActor API 都保持可用。3.1 源码印证托管与调用的解耦在 pkg/actors/actors.go 中Init将hostingActive的状态传递给 Actor 表a.table table.New(table.Options{ ReentrancyStore: a.reentrancyStore, StartSuspended: !a.hostingActive, Timers: func() internaltimers.Storage { return a.timerStorage }, })StartSuspended: !a.hostingActive表示没有状态存储时Actor 表以“挂起”状态启动——不激活 Actor 实例、不注册到 placement但整个 runtime 照常运行。而Run阶段在 pkg/actors/actors.go 中也只在存在 Actor 状态存储时才等待宿主注册完成if _, _, ok : a.compStore.GetStateStoreActor(); ok { select { case -a.registerDoneCh: case -ctx.Done(): return ctx.Err() } } return a.placement.Run(ctx)也就是说纯客户端模式无状态存储下placement 客户端照常启动以接收 Actor 位置信息但不会注册任何宿主 Actor 类型。3.2 源码印证注册宿主时的校验在 pkg/actors/actors.go 的RegisterHosted方法中当HostedActorTypes为空即服务没有注册任何 Actor时直接返回if len(cfg.HostedActorTypes) 0 { return nil }这从 API 层面保证纯客户端服务无需等待状态存储即可完成初始化而一旦传入非空 Actor 类型列表注册 Actor 的宿主后续状态读写操作会通过状态存储解析层强制校验。四、状态存储解析层的强制校验当宿主服务确实要读写 Actor 状态时底层会走到 pkg/actors/state/state.go 的stateStore()方法func (s *state) stateStore() (string, Backend, error) { storeS, storeName, ok : s.compStore.GetStateStoreActor() if !ok { return , nil, messages.ErrActorRuntimeNotFound } store, ok : storeS.(Backend) if !ok || !contribstate.FeatureETag.IsPresent(store.Features()) || !contribstate.FeatureTransactional.IsPresent(store.Features()) { return , nil, errors.New(errStateStoreNotConfigured) } return storeName, store, nil }这段代码揭示了两层校验组件存在性GetStateStoreActor()返回 false 时直接返回ErrActorRuntimeNotFound。对应的错误文案定义在 pkg/messages/predefined.gothe state store is not configured to use the actor runtime. Have you set the - name: actorStateStore value: true in your state store component file?能力校验即使存在组件也必须同时支持ETag与事务Transactional特性否则返回errStateStoreNotConfigured定义于 pkg/actors/state/state.go。这是因为 Actor 状态读写依赖事务性操作如Multi批量提交见 pkg/actors/state/state.go与并发控制ETag。该解析层每次调用都实时从组件存储中解析因此天然支持状态存储的热重载见 pkg/actors/state/state_test.go 的Test_stateStore测试它在添加、删除、更换不同名称的 actor state store 后逐一验证解析行为。五、如何配置 Actor 状态存储actorStateStore 属性要在 Dapr 中把某个状态存储指定为 Actor 状态存储需要在 Component 配置的metadata中设置actorStateStore: true。该属性由状态组件处理器在初始化时解析见 pkg/runtime/processor/state/state.goif s.actorsEnabled { actorStoreSpecified : false for k, v : range props { if strings.ToLower(k) PropertyKeyActorStateStore { actorStoreSpecified kitstrings.IsTruthy(v) break } } if actorStoreSpecified { if err s.compStore.AddStateStoreActor(comp.Name, store); err ! nil { // ... } log.Info(Using comp.Name as actor state store) if s.actors ! nil { s.actors.OnActorStateStoreChanged() } } }关键细节属性名大小写不敏感strings.ToLower(k)与常量PropertyKeyActorStateStore actorstatestore比较见 pkg/runtime/processor/state/state.go值通过kitstrings.IsTruthy解析接受true等真值写法添加成功后调用OnActorStateStoreChanged()通知 Actor runtime 收敛托管状态见下文第六节。仓库自带的真实配置示例可以参考 tests/config/dapr_postgres_state_actorstore.yamlapiVersion: dapr.io/v1alpha1 kind: Component metadata: name: statestore-actors spec: type: state.postgres version: v2 metadata: - name: connectionString value: hostdapr-postgres-postgresql.dapr-tests.svc.cluster.local userpostgres passwordexample port5432 connect_timeout10 databasedapr_test - name: tablePrefix value: v2actor - name: metadataTableName value: dapr_metadata_v2actor - name: actorStateStore value: true scopes: # actortestclient is deliberately omitted to ensure that actor_features_test works without a state store - actor1 - actor2 - actorapp - actorfeatures同样tests/config/dapr_cosmosdb_state_actorstore.yaml 为 Cosmos DB 状态存储设置了actorStateStore: true。两处配置的scopes注释都刻意写明actortestclient纯客户端测试应用被有意排除在作用域之外以验证无状态存储时客户端调用测试仍然通过——这正是本文所述修复在 e2e 测试中的直接体现。组件存储层的注册与校验当多个状态存储被标记为 actor state store 时pkg/runtime/compstore/statestore.go 的AddStateStoreActor会拒绝重复注册func (c *ComponentStore) AddStateStoreActor(name string, store state.Store) error { if c.actorStateStore.store ! nil c.actorStateStore.name ! name { return fmt.Errorf(detected duplicate actor state store: %s and %s, c.actorStateStore.name, name) } c.states[name] store c.actorStateStore.name name c.actorStateStore.store store c.actorStateStore.rev return nil }同时每个槽位维护一个自增的revrevisionGetStateStoreActorWithRevision()pkg/runtime/compstore/statestore.go返回该版本号用于检测状态存储的“增删换”等迁移事件——包括同名删除后重新添加这种 rev 相同但语义不同的情况。六、热重载与托管收敛状态存储变化时的动态调整修复方案不仅覆盖启动阶段还覆盖运行期的动态变化。Actor runtime 通过storeKickCh通道接收状态存储变更通知pkg/actors/actors.gofunc (a *actors) OnActorStateStoreChanged() { select { case a.storeKickCh - struct{}{}: default: } }通知采用非阻塞的 coalesce 模式避免频繁通知堆积。随后convergeHostingpkg/actors/actors.go根据最新状态收敛托管行为func (a *actors) convergeHosting(ctx context.Context) { _, name, rev, ok : a.compStore.GetStateStoreActorWithRevision() if rev a.hostingRev { return } a.hostingRev rev if ok a.hostingActive name a.hostingName { log.Infof(Actor state store %s updated - actor hosting continues, name) return } if a.hostingActive { log.Info(Actor state store removed or replaced - draining hosted actors) if err : a.table.SuspendHosting(ctx); err ! nil { log.Errorf(Error draining hosted actors after actor state store change: %s, err) } } if ok { log.Infof(Actor state store %s configured - enabling actor hosting, name) a.table.ResumeHosting() } a.hostingActive ok a.hostingName name }该逻辑处理三类事件事件行为同名热更新如密钥轮换数据路径每次调用实时解析组件存储宿主无需排空继续服务状态存储被移除或替换挂起宿主并排空已托管的 Actor状态存储被配置恢复宿主开始托管 Actor七、总结与验证建议Dapr 1.5.3 的这项修复本质上是把“Actor 状态存储缺失”从Actor API 级错误降级为Actor 托管级限制纯客户端服务只调用不托管无状态存储也能正常使用 Actor API提升 Actor 客户端部署的轻量性宿主服务注册 Actor仍必须配置具备 ETag 与事务特性的状态存储保证状态一致性与并发安全运行期动态变化通过 revision 驱动的convergeHosting实现状态存储的增删换热收敛。如需在本地复现验证可参考 pkg/actors/state/state_test.go 的单元测试逻辑创建空组件存储 → 断言ErrActorRuntimeNotFound→ 添加 actor store 后断言解析成功 → 删除后再次断言报错即可完整覆盖“无存储/有存储”两种路径。仓库的 e2e 测试应用 tests/apps/actorfeatures 与刻意不配置状态存储的 tests/apps/actorclientapp 则从集成层面验证了修复效果。【免费下载链接】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),仅供参考
返回列表