数据幂等

在系统设计的时候,操作幂等设计是一点需要考虑的点。

幂等(idempotent、idempotence)是一个数学与计算机学概念,常见于抽象代数中。在编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。

用数学表达式来表达的话:

f(x) = f (f (x))

1、数据库幂等

幂等性是后续多余的调用不会对系统数据的一致性进行破坏。在数据库操作一般会有增、删、查、改 4 类操作。下面我们来看这 4 种操作的幂等性:

  • select : 查询操作天生幂等,不管做一次查询还是多次查询都是幂等
  • insert : 添加数据天生不幂等,多次添加就会造成脏数据
  • delete : 对于主键删除或者条件删除一般都是幂等的
  • update : 修改操作可能是幂等(update table set x = 3 where x = 2),也可能是不幂等的(update table set x = x + 1 where id = 1)

所以针对系统处理,如果需要考虑幂等设计。只需要考虑添加操作以及修改操作。

2、添加操作幂等

因为添加操作不是幂等的,如果需要保证操作幂等就需要系统编码的时候自己考虑幂等。下面就给出一个具体的场景,这样才能更好的理解。如果需要提供接口给第三方调用,凭证系统 就是一个常见的系统。凭证系统主要是以下几个作用:

  • 保证调用方的幂等,防止重复调用
  • 保存调用方的原始数据,发生争执可以利用凭证数据
  • 外部订单号转化为内部订单号

可以添加一张记录表根据商户号与外部订单号做为数据库的唯一主键来保证数据的幂等。

在保存数据的时候要考虑两个异常:

  • 多次保存商户凭证由于违背了数据库的唯一主键而报错
  • 操作业务的出错

第一次出错,数据库事务能够保证不会保存脏数据。如果保存了商户凭证后然后业务操作进行了出错。返回错误信息给商户,如果商户再次尝试就会出错。所以必须保证,保存商户凭证与业务操作必须在一个事务里面。
用伪代码可以如下表示:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 0、开始事务start transaction;try {    // 1 保存凭证    save voucher;    // 2 业务操作    func(biz);} catch (Exception e) {    // 3、回滚事务    roll back;}// 4、提交事务commit stransaction;

这样就可以根据数据库的唯一主键来保证添加操作幂等。

3、修改操作幂等

对于修改操作,很多情况下是不幂等的。比如,当一个点餐订单的时候,支付成功即是这个订单完成了。支付一般是调用第三方支付平台,如:微信、支付宝等。当调用支付的时候支付成功通知地址传递给第三方支付平台。

支付成功后,第三方平台就会回调这个支付成功地址。调用的方式分为同步与异步 ,不论是同步还是异步都可能存在超时的情况。为了支付重试,回调接口必须保证幂等。

  • 根据传入的订单号来查询业务订单并锁定。
  • 首先检测订单状态如果状态是已经支付就会直接发起退款
  • 如果订单是待支付状态,修改支付状态并且保存支付订单号与支付方式到订单中

这里面就是通过数据库的悲观锁来做这件事,具体伪代码如下:

1
2
3
4
5
6
7
8
// 1、开始事务begin transaction;// 2、锁住数据库记录select * from order where order_id = ? for update;// 3、业务操作bizOperate(); // 修改订单状态,支付订单号与支付方式// 4、提交事务commit transaction;

在使用悲观锁来做数据幂等的时候需要考虑以下几点:

  • 需要注意的是锁数据库的查询语句必须是在索引上。如果查询语句没有在索引上就会锁定整个表,这个是一件非常恐怖的事
  • 当使用 for update 的时候,就相当于 Java 里面的 synchronized 关键字,它是独占的。所以在其它数据来访问的时候就会阻塞,这对于高并发系统来说是不可忍受的。所以可以使用 for update nowait,如果其它资源来查询的时候就会抛出异常。

4、总结

业务系统实现幂等性的方式基本确定。系统关键接口的幂等性为以后系统的长期发展,特别是往分布式方向发展打下了很好的根基,可以大大简化分布式应用的构建复杂度。

上一篇