幂等性
幂等这个词原自数学,某一元运算为幂等时,其作用在任一元素两次后会和其作用一次的结果相同。在编程中,一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。第一次请求的时候对资源产生了副作用,但是以后的多次请求都不会再对资源产生副作用。这里的副作用是不会对结果产生破坏或者产生不可预料的结果。
比如,某服务记录关键数据 X,当前值为 100。A 请求需要将 X 增加 200;同时,B 请求需要将 X 减去 100。在理想的情况下,A 先读取到 X=100,然后 X 增加 200,最后 X=300。B 请求接着从读取 X=300,减去 100,最后 X=200。然而在真实情况下,如果不做任何处理,则可能会出现:A 和 B 同时读取到 X=100;假如 A 比 B 先执行完,那么最后 X=0,如果 B 比 A 先执行完,那么最后 X=300。不管是那种情况发生了,都产生了副作用或者说是产生了不可预料的结果,并且是不可以接受的异常。这种情况就是我们提供的方法或者接口不满足幂等性,导致的不可预料的结果。
保证幂等性的方法
1. 建立唯一索引,防止新增脏数据
这个可以限制重复插入数据,当重复时,数据库会抛异常,保证不会出现脏数据。但体验不好,并且实用场景有限制。
2. 利用 token 机制,防止页面重复提交
核心思想是为每一次操作生成一个唯一性的凭证,也就是 token。一个 token 在操作的每一个阶段只有一次执行权,一旦执行成功则保存执行结果。对重复的请求,返回同一个结果。
3. 状态机幂等
在有状态的数据中可以使用,如果状态机已经处于下一个状态,这时候来了一个上一个状态的变更,理论上是不能够变更的,这样的话,保证了有限状态机的幂等。如果状态是顺序的,不可逆,那么就不会出现 ABA 问题,否则会出现 ABA 问题。
4.select + insert
这种情况在没有并发的系统中可以解决幂等问题,在单 JVM 有并发的时候可以加锁来保证幂等性,在分布式环境它是没发保证幂等的,这时候需要用到分布式锁来保证。
5. 分布式锁
在进入方法时,先去获取锁,假如获取到锁,就继续后面的流程。假如没有获取到锁,就等待锁的释放直到获取到锁。当执行完方法时,释放锁。当然,锁要设个超时时间,防止意外没有释放到锁。它用来解决分布式系统的幂等性,常用的实现方案是 redis 和 zookeeper 等工具。
6. 对外提供幂等的接口
通过 source 来源 +seq 序列号来判断请求是否重复,在并发时只能处理一个请求。其它相同并发请求要么返回请求重复,要么等待前面请求执行完成在执行。
幂等性的不足
1. 增加了额外控制幂等的业务逻辑,复杂化了业务功能。
2. 把并行执行的功能改为串行执行,降低了执行效率。
最后,幂等性虽然复杂化了业务功能和降低了执行效率,但为了保证系统的正确性,是必要的。就上面更新 X 的例子,在单台服务器上,给那段代码加上锁,并给 X 设为 volatile,就保证来数据的正确性了。在分布式环境下并且 X 是从数据库或者文件里查询出来的,用上面加锁的方式实现就不能保证数据的正确性了,这时候就需要用到分布式锁了。所以,保证方法或接口的幂等性是非常有必要的,因为数据是不能出现任何问题的。
PS:
清山绿水始于尘,博学多识贵于勤。
微信公众号:「清尘闲聊 」。
欢迎一起谈天说地,聊代码。