幂等性

幂等,这个词来源自数学领域。幂等性衍生到软件工程中,它的语义是指:函数/接口可以使用相同的参数重复执行, 不应该影响系统状态,也不会对系统造成改变。

举一个简单的例子:正常设计的查询接口,不管调用多少次,都不会破坏当前的系统或数据,这就是一个幂等操作。

由于分布式天然特性的时序问题以及网络的不可靠性(机器、机架、机房故障、电缆被挖断等等), 重复请求很常见,接口幂等性设计就显得尤为重要。

幂等需要考虑的场景有很多,例如系统A是处理用户客户端发送过来的请求,无论是前端bug、脚本恶意发包、用户重复点击又或是网络超时导致的网络重发,都会造成系统A收到相同参数的网络请求。

保障幂等性的原理

对于分布式系统来说,在JVM层面的锁已经失去作用,所以保证系统幂等性需要满足3个条件:

  1. 请求唯一标识:每一个请求必须有一个唯一标识。
  2. 处理唯一标识:每次处理完请求之后,必须有一个记录标识这个请求处理过了。
  3. 逻辑判断处理:每次接收请求需要进行判断之前是否处理过的逻辑处理。根据请求唯一标识查询是否存在处理唯一标识。

幂等性实现方案

1. token机制

针对客户端重复连续多次点击的情况,例如用户购物提交订单,提交订单的接口就可以通过token机制实现防止重复提交。

img

主要流程就是:

  1. 服务端提供生成请求token的接口。在存在幂等问题的业务执行前,向服务器获取请求token,服务器会把token保存到Redis中。
  2. 然后调用业务接口请求时,把请求token携带过去,一般放在请求头部。
  3. 服务器判断请求token是否存在redis中:存在则表示第一次请求,这时把Redis中的token删除,继续执行业务;如果判断token不存在redis中,就表示是重复操作,直接返回重复标记给client,这样就保证了业务代码,不被重复执行。

这里要结合业务考虑这种场景:如果请求处理失败,前端是否需要重新申请token进行重试(因为此时token在服务端已经被删除)。

2. 数据库唯一索引

往数据库表里插入数据的时候,利用数据库的唯一索引特性,保证唯一的逻辑。唯一序列号可以是一个字段,例如订单的订单号,也可以是多字段的唯一性组合。

事务中包含多表数据的更新,业务要考虑处理事务回滚的问题。

3. Redis实现

Redis实现的方式就是将唯一序列号作为Key存入Redis,在请求处理之前,先查看Key是否存在。唯一序列号可以是一个字段,例如订单的订单号,也可以是多字段的唯一性组合。当然这里需要设置一个key的过期时间,否则Redis中会存在过多的key。具体校验流程如下图所示:

img

如果想要基于Redis实现幂等性防重框架,需要考虑如下两个问题:

  1. 如果第一次请求失败了,客户端重试,是否需要放行?
  2. 网络请求可能是get或者post(内部rpc协议除外),唯一序列号参数可能在url或是在body体里。则使用防重框架的新接口以及之前老业务接口能否做到版本兼容性?

建议业务使用方最好针对指定业务进行Redis的幂等方案。

Zookeeper同样也能实现上述功能,但由于Zookeeper是CP模型,性能不如Redis,另外针对防重场景,也并不需要Zookeeper高可靠性,所以优先推荐Redis。