Files
kuboard-press/glossary/idempotent.md
huanqing.shao fcbdfb807a 错误字修正
2019-11-13 22:46:34 +08:00

127 lines
7.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
description: 在编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。幂等函数或幂等方法是指可以使用相同参数重复执行并能获得相同结果的函数。这些函数不会影响系统状态也不用担心重复执行会对系统造成改变。例如“getUsername()和setTrue()”函数就是一个幂等函数。
meta:
- name: keywords
content: 幂等
---
# 幂等
本文转载自 [关于高并发和分布式中的幂等处理,你真的知道吗?](https://www.jianshu.com/p/cea3675a590b)
#### **我们先来谈下幂等的概念**
**抽象概念**
> 幂等idempotent、idempotence是一个数学与计算机学概念常见于抽象代数中。
在编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。幂等函数或幂等方法是指可以使用相同参数重复执行并能获得相同结果的函数。这些函数不会影响系统状态也不用担心重复执行会对系统造成改变。例如“getUsername()和setTrue()”函数就是一个幂等函数。
用通俗的话讲:就是针对一个操作,不管做多少次,产生效果或返回的结果都是一样的
**举几个例子:**
1.比如前端对同一表单数据的重复提交,后台应该只会产生一个结果。
2.比如我们发起一笔付款请求应该只扣用户账户一次钱当遇到网络重发或系统bug重发也应该只扣一次钱。
3.比如发送消息,也应该只发一次,同样的短信如果多次发给用户,用户会崩溃。
4.比如创建业务订单,一次业务请求只能创建一个,不能出现创建多个订单。
还有很多诸如此类的,这些逻辑都需要幂等的特性来支持。
#### **实现幂等性的技术方案**
**查询操作**
> 查询一次和查询多次在数据不变的情况下查询结果是一样的select是天然的幂等操作。
**删除操作**
> 删除操作也是幂等的,删除一次和多次删除都是把数据删除。(注意可能返回结果不一样删除的数据不存在返回0删除的数据多条返回结果多个)
**唯一索引,防止新增脏数据**
> 拿资金账户和用户账户来说每个用户只能有一个资金账户怎么防止给用户创建资金账户多个那么给资金账户表中的用户ID加唯一索引在新增的时候只有一个请求成功剩下都会抛出唯一索引重复异常。比如`org.springframework.dao.DuplicateKeyException`,这时候再查询一次就可以了,数据存在,返回结果。
**token机制防止页面重复提交**
> 要求:页面的数据只能被点击提交一次
>
> 发生原因由于重复点击或者网络重发或者nginx重发等情况会导致数据被重复提交
>
> 解决办法:
>
> 集群环境采用token加redis
>
> 单JVM环境采用token加redis或token加jvm内存
>
> 处理流程:
>
> 数据提交前要向服务申请tokentoken放到redis或jvm内存每个token有自己的有效时间
>
> 提交后后台校验token同时删除token生成新的token返回
>
> token特点要申请一次有效性可以限流。
注意redis要用删除操作来判断token删除成功代表token校验通过如果用select+delete来校验token
存在并发问题,不建议使用
#### **悲观锁**
获取数据的时候加锁获取 select * from table_xxx where id=xxx for update;
注意id字段一定是主键或者唯一索引不然是锁表会出事的。悲观锁使用时一般伴随事务一起使用数据锁定时间可能会很长根据实际情况选用。
#### **乐观锁**
> 乐观锁只是在更新数据那一刻锁表其他时间不锁表所以相对于悲观锁效率更高。乐观锁的实现方式多种多样可以通过version或者其他状态条件
>
> 1.通过版本号实现 update table_xxx set name=#name#,version=version+1 where version=#version#
>
> 2.通过条件限制 update table_xxx set avai_amount=avai_amount-#subAmount# where avai_amount-#subAmount# >= 0
>
> 要求avai_amount-subAmount >=0
>
> 这个情景适合不用版本号,只更新是做数据安全校验,适合库存模型,扣份额和回滚份额,性能更高。
注意乐观锁的更新操作最好用主键或者唯一索引来更新这样是行锁否则更新时会锁表上面两个sql改成下面的两个更好。
update table_xxx set name=#name#,version=version+1 where id=#id# and version=#version#
update table_xxx set avai_amount=avai_amount-#subAmount# where id=#id# and avai_amount-#subAmount# >= 0
#### **分布式锁**
还是拿插入数据的例子,如果是分布是系统,构建全局唯一索引比较困难,例如唯一性的字段没法确定,这时候可以引入分布式锁,通过第三方的系统(redis或zookeeper),在业务系统插入数据或者更新数据,获取分布式锁,然后做操作,之后释放锁,其实就是为了控制多线程并发的操作,也是分布式系统中经常用到的解决思路。
#### **select + insert**
并发不高的后台系统或者一些任务JOB为了支持幂等支持重复执行简单的处理方法是先查询下一些关键数据判断是否已经执行过在进行业务处理就可以了。
**注意******核心高并发流程不要用这种方法。
#### **状态机幂等**
在设计单据相关的业务,或者是任务相关的业务,肯定会涉及到状态机(状态变更图),就是业务单据上面有个状态,状态在不同的情况下会发生变更,一般情况下存在有限状态机,这时候,如果状态机已经处于下一个状态,这时候来了一个上一个状态的变更,理论上是不能够变更的,这样的话,保证了有限状态机的幂等。
注意:订单等单据类业务,存在很长的状态流转,一定要深刻理解状态机,对业务系统设计能力提高有很大帮助。
#### **对外提供接口的api如何保证幂等**
如银联提供的付款接口需要接入商户提交付款请求时附带source来源seq序列号source+seq在数据库里面做唯一索引防止多次付款(并发时,只能处理一个请求)。
**重点:**
对外提供接口为了支持幂等调用接口有两个字段必须传一个是来源source一个是来源方序列号seq这个两个字段在提供方系统里面做联合唯一索引这样当第三方调用时先在本方系统里面查询一下是否已经处理过返回相应处理结果没有处理过进行相应处理返回结果。注意为了幂等友好一定要先查询一下是否处理过该笔业务不查询直接插入业务系统会报错但实际已经处理了。
#### **最后总结:**
幂等性应该是合格程序员的一个基因,在设计系统时,是首要考虑的问题,尤其是在像第三方支付平台,银行,互联网金融公司等涉及的网上资金系统,既要高效,数据也要准确,所以不能出现多扣款,多打款等问题,这样会很难处理,并会大大降低用户体验。