2019/07/31 Understanding Design with Idempotency

幂等设计是架构设计中需要考虑的重要环节。幂等设计可以分两个层面考虑:接口定义和幂等实现方案。

接口定义

请求幂等是说请求一次和请求多次结果一样。其实这个说法有一点不准确,因为如果是看请求结果的话,那么在读请求在数据被修改之后,得到的结果也是不同的。所以准确的说应该是看请求是否有副作用,比如每次请求都会将数据修改成不一样的值。按照这个定义,读请求不会修改数据本身,所以读请求是幂等的。而写请求是会修改数据本身的,所以写请求不是幂等的。大多数情况下,我们接口的设计都是遵守 RESTful 规范。在 RESTful 中定义了 7 种方法:

  1. OPTIONS,获取服务器信息,幂等。
  2. HEAD,请求资源的头信息,幂等。
  3. GET,获取资源信息,幂等。
  4. POST,新建资源,非幂等。
  5. PUT,全量更新,幂等。
  6. DELETE,删除资源,幂等。
  7. PATCH,局部更新,非幂等。

所以在设计接口的时候需要和 RESTful 的规范保持一致。

幂等实现方案

在由于写请求不是天然幂等的,所以我们在架构设计的时候需要考虑幂等性。我们可以尝试通过如下几种思路来解决幂等性问题。

  1. 利用数据库约束
    由于请求最终都是操作数据库,所以最终都会反映到数据库的 CRUD 操作,所以我们可以利用数据库主键或唯一索引来保证唯一。
  2. 去重表
    单独维护一张表保存去重字段,比如订单号,在业务操作之前先插入订单号,插入订单号和业务处理在同一个事务中,同时成功提交或者回滚。这种方案是第一种方式的变形,好处是业务无关,多个业务可以共享一张表。
  3. 乐观锁
    在进行更新和删除操作时比较常用。可以利用 version 字段,如
    update t1 set age=age+1, version=version+1 where id=1 and version=1
    
    也可以使用业务字段,如
    update t1 set age=age+1 where id=1 and age=19
    

    在进行更新和删除操作时,最好使用绝对值,不要使用相对值。

  4. 使用全局唯一 ID

  5. token 方式
    1) 客户端首先向服务器发送请求获取 token ,服务器生成 token 存到缓存中,服务器返回 token 给客户端。
    2) 客户端业务请求同时带着 token ,如果服务端存在 token 说明是第一次操作,可以进行业务处理,如果 token 不存在,说明业务已经执行过。
    3) 业务执行完成删除 token 。
    这种方式可以防止客户端重复提交,缺点是多了一次获取 token 的请求。

  6. 分布式锁

  7. MQ 消息去重

  8. 状态机
    状态机是有限状态自动机的简称,是现实事物运行规则抽象而成的一个数学模型。状态机有起始、终止、现态、次态(目标状态)、动作、条件 6 中元素。比如一个账号支付状态只能从未支付到支付,且只能进行一次。

  9. 通过资源条件保证幂等
    比如,有一个资源总量是 10 ,进行增加操作,每次增加都先判断资源总量,如果没到 10 就增加,如果到 10 就退出,这样不管执行多少次都是不会改变事件整体的幂等性。

虽然我们列出几种出里方式,但是并没有一种方式是完美的,需要根据业务和架构进行选择。