单行原子操作

v1.10.0版本开始,Pegasus支持单行原子操作。这里的单行是指同一HashKey下的所有数据。

原理

Pegasus采用Hash分片,同一个HashKey的数据总是存储在同一个Partition中,即相同的Replica中。同时,Pegasus实现时,同一个Replica的写操作在server端总是串行执行的。因此对于同一HashKey下的数据操作,可以很方便地实现原子的语义。

对于纯粹的写操作,譬如multiSetmultiDel,单个操作中对多个SortKey同时set或者del,原子语义很容易理解,要么同时成功,要么同时失败,所以这两个操作属于单行原子操作。

不过我们这里重点关注的是另一类操作:先读后写,并且写操作依赖读的结果。这类操作的特点就是:它们是非幂等的,即同一个操作如何多次重复执行,造成的结果(包括数据实际的更新情况、返回给用户的结果)可能是不同的。原子增减和CAS操作都属于这类。Pegasus能保证这类操作的原子性和一致性,因为:

  • 同一个HashKey的数据总是存储在同一个Replica中;
  • 同一个Replica的写操作在server端总是串行执行的;
  • 同一个操作保证执行且只会执行一次,即使发生数据迁移、宕机恢复等情况。

由于非幂等特性,这类操作会和Pegasus的另外一些特性冲突,譬如跨机房热备。所以我们在1.10.0版本中还提供了一个配置项,用于配置集群是否允许非幂等操作,如果配置为false,则所有非幂等操作都会返回ERR_OPERATION_DISABLED

[replication]
    allow_non_idempotent_write = false

原子增减

虽然Pegasus的value不支持schema,但是我们依然提供了原子增减操作,类似Redis的incr命令。接口参见incr

语义解释:

  • 首先server端存储的仍然是字节串,但是在incr的时候会先将字节串先转换为int64类型,转换方式就是简单的String-to-Int,譬如字节串12345就会转换为数字12345。完成incr操作后,得到的结果会重新转换为字节串,然后存储为新值。
  • 字节串转换为int64时可能出错,譬如不是合法的数字、超过int64的范围等,都会报错。
  • 如果原value不存在,则认为原始值为0,正常执行incr操作。
  • 操作数increment可以为正数也可以为负数,所以一个incr接口就可以实现原子增或者原子减。
  • TTL语义:如果原value存在,则新值和原值的TTL保持一致;如果原value不存在,则新值在存储时不设TTL。

CAS操作

另一类很有用的原子操作就是CAS(Compare-And-Swap),直译就是对比交换,最初是表示一条CPU的原子指令,其作用是让CPU先进行比较两个值是否相等,然后原子地更新某个位置的值。基于CAS操作,可以实现很多高级的并发特性,譬如锁。因此很多编程语言也原生地提供CAS操作。

Pegasus提供了check_and_set的CAS操作,其语义就是:根据HashKey的某一个SortKey的值是否满足某种条件,来决定是否修改另一个SortKey的值。我们将用于判断条件的SortKey称之为CheckSortKey,将用于设置值的SortKey称之为SetSortKey。对应地,CheckSortKey的value称之为CheckValue,SetSortKey要设置的value称之为SetValue。接口参见checkAndSet,以及其扩展版本checkAndMutatecompareExchange

语义解释:

  • 只有当CheckValue满足指定的条件时,才会设置SetSortKey的值。
  • 需要满足的条件类型通过CheckType指定,有的CheckType还需要指定操作数CheckOperand。目前支持的类型包括:
    • 判断CheckValue的appearance属性:是否存在、是否为空字节串等。
    • 字节串比较:将CheckValue与CheckOperand按照字节序比较,看是否满足<<===>=>=关系。
    • 数字比较:类似于incr操作,先将CheckValue转换为int64,再与CheckOperand比较,看是否满足<<===>=>=关系。
  • CheckSortKey和SetSortKey可以相同,如果相同,就是先判断旧值是否满足条件,满足的话就设置为新值。
  • 可以通过选项CheckAndSetOptions.returnCheckValue指定返回CheckValue的值。
  • 可以通过选项CheckAndSetOptions.setValueTTLSeconds指定SetValue的TTL。

为了方便使用,Pegasus Java Client还提供了compare_exchange接口:当HashKey的某个SortKey的value按照字节串比较等于用户指定的ExpectedValue时,就将其value更新为用户指定的DesiredValue。从语义上来看,compare_exchange更像是Compare-And-Swap的另外一种说法。接口参见compareExchange

compare_exchange其实是check_and_set的特化版本:

  • CheckSortKey和SetSortKey相同。
  • CheckType为CT_VALUE_BYTES_EQUAL。