状态管理高级特性之并发控制

Dapr状态管理高级特性之并发控制

设计分析

dapr state 目前要求操作的并发控制有两个: FirstWrite 和 LastWrite。

const (
	FirstWrite = "first-write"
	LastWrite  = "last-write"
)

LastWrite (Last Write Win模式)就简单了,每个写操作都只需要简单的执行即可,无需考虑是否并发。事实上就是不做并发控制。

FirstWrite (First Write Win模式)复杂一些,当有多个操作进行并发写时,只有第一个能成功。因此,必须有机制能够在执行写操作时判断从上次读到这次写,期间 state 数据没有被修改。也就是需要实现 CAS操作:CAS = Compare And Set。

Dapr state 的设计是引入一个名为 ETag 的机制:

  • ETag 是一个整型,每个状态都会关联一个 ETag
  • 每次创建或修改 state 时,ETag都会递增
  • 进行写操作时:先读取现有state,拿到当前的ETag;在提交写操作时,传入之前的ETag。底层 state store的实现应该在执行写操作之前检查ETag是否匹配。

具体到各个操作:

  1. Save state
    • grpc API:在请求的SaveStateRequest中通过 etag 字段提供
    • HTTP API:在请求的json内容中通过etag字段提供
  2. Get state
    • grpc API:在应答的 GetStateResponse 中通过 etag 字段提供
    • HTTP API:在应答的 ETag header中提供
  3. Get Bulk
    • grpc API:在应答的 GetBulkStateResponse 中通过 etag 字段提供
    • HTTP API:在应答的json中通过 etag 字段提供
  4. Delete State
    • grpc API:在请求的 DeleteStateRequest 中通过 etag 字段提供
    • HTTP API:通过请求的 If-Match header提供

实现分析

Redis实现

redis 为了实现 state 要求的 etag,就必须在常规的key/value存储模型上增加 key/etag 的存储,实现方式就是 key / map as value,将一个 map 作为value(刚好redis本身也支持map结构)。然后在map中存储 data / version 等多个信息:

  • key=version,存储ETag需要的version
  • key=data,存储state的实际数据

读取state的时候将整个map as value读取,然后分别取data和version即可。

但写操作会比较麻烦, redis 本身不直接提供对多个字段的原子操作方式,因此在save和delete操作时需要通过LUA脚本来完成。

  • concurrency 设置为 first-write :需要通过 etag 实现 CAS (Compare And Set)
  • concurrency 设置为 last-write :忽略 etag,即使请求设置了也要重置