1幂等性1.1定义幂等性的概念来源于数学,意思是对数据源进行N次变换的结果与一次变换的结果相同。在工程上,幂等性是指用户对同一操作发起一次请求或多次请求的结果是一致的,不会因为多次点击而产生副作用。幂等性包括在发出第一个请求时对资源产生副作用,但后续请求将不再对资源产生副作用。幂等性关注的是后续多次请求是否对资源产生副作用,而不是结果。网络超时等问题不在幂等范围内。幂等性是系统服务对外的一种承诺,而不是实现。它承诺只要调用接口成功,多次外部调用对系统的影响是一致的。声明为幂等的服务将假设外部调用失败是正常的,并且失败后必须重试。1.2场景业务开发过程中,可能会遇到网络震荡导致无法接收请求,触发重试机制,或者前端抖动导致表单重复提交。例如,在交易系统中,用户提交的购物请求已经被服务器正确处理,但是由于网络等原因导致服务器的返回结果丢失,导致客户端无法得知处理结果。如果是在网页上,一些不恰当的设计可能会让用户认为上次操作失败,然后刷新页面,导致扣款被调用两次,账户也被再次扣款。此时需要引入一个幂等接口。我们以MySQL为例。只有第三种场景需要开发者使用其他策略来保证幂等性:SELECTcol1FROMtab1WHERcol2=2;--无论执行多少次都不会改变状态,是天然的幂等性。UPDATEtab1SETcol1=1WHEREcol2=2;--无论执行多少次,状态都是一致的,所以也是幂等操作。UPDATEtab1SETcol1=col1+1WHEREcol2=2;--每次执行结果都会变化,这个不是幂等的。重复提交和幂等的区别:重复提交是在第一次请求成功后,人为的进行多次操作,导致不满足幂等性要求的服务多次改变状态。更幂等的用例是第一次请求不知道结果(比如超时)或者异常失败,发起多次请求。目的是多次确认第一次请求成功,但不会被多次请求影响。有多个状态变化。1.3关于幂等性的思考幂等性的引入会使服务器的逻辑更加复杂。满足幂等性的服务在逻辑上至少需要包含两点:第一,查询最后一次执行状态,如果没有,考虑第一次询问。在改变服务状态的业务逻辑之前保证防重复提交的逻辑。幂等性可以简化客户端的逻辑处理,但增加了服务提供者的逻辑和成本。所以是否使用需要根据具体场景具体分析。因此,除了业务有特殊要求外,尽量不要提供幂等接口。.增加了控制幂等性的业务逻辑,使业务功能复杂化。将并行执行的功能改为串行执行,降低了执行效率。2幂等解决方案2.1前端设置用户点击提交按钮后,我们可以设置按钮不可用或者隐藏。前端限制比较简单,但是有个致命错误。如果有知识的用户通过模拟网页请求重复提交请求,则前端限制被绕过。2.2唯一索引防止订单多次插入最简单直接的方法就是创建一个唯一索引,然后插入的时候语句可能会略有不同。但目的是保证数据库中只有一条相同的记录。方法一:在数据库中添加唯一索引,如果在执行过程中捕获到DuplicateKeyException,就会明白是重复插入导致的,继续执行业务。方法二:使用MySQL自带的关键字ONDUPLICATEKEYUPDATE实现不存在则插入,存在则更新的操作。该关键字不会删除原来的记录。方法三:replaceinto的主要作用和INSERT类似。replaceinto底层是先删除再插入数据,会破坏索引,重新维护索引。需要注意的是必须有主键或者唯一索引才有效,否则replaceinto只会被添加。2.3去重表去重表的机制是基于mysql唯一索引的特点。大体流程:客户端先向服务器发起请求,服务器先将请求信息存储在一个mysql去重表中。一个表需要根据这个请求的某个特殊字段创建一个唯一索引或者主键索引。判断是否插入成功,如果插入成功,则继续进行后续的业务请求。如果插入失败,说明当前请求已经执行完毕。2.4悲观锁方法一:简单的使用Java自带的syn或者lock锁来实现幂等。核心点是将重要的执行部分从并行切换到串行。缺点是这个锁不能用于分布式场景,因为它是跨JVM的!这时候就需要引入分布式锁。依靠MySQL内置的forupdate操作数据库实现序列化。这里的重点是更新。简单说明一下:当线程A执行update时,数据会锁定当前记录。当其他线程执行这行代码时,会等待线程A释放锁,然后再获取锁。继续跟进。当事务提交时,forupdate获取的锁会自动释放。这种模式的缺点是,如果业务处理比较耗时并发,后续线程会长时间处于等待状态,占用大量线程,使这些线程处于无效等待状态,并且数量Web服务中的线程数通常是有限的。如果大量的线程都在等待forupdate锁,不利于系统的并发运行。2.5乐观锁在每一行数据中加入一个version字段。这其实和秒杀设计中的思路类似,使用当前MySQL自带的读取和更新操作。更新数据时,先查询获取对应的版本号,然后尝试更新操作,根据返回值是否为0判断是否为重复提交。selectid,name,account,versionfromuserwhereid=1412;//假设获取version=10updateusersetaccount=account+10,version=version+1whereid=1412andversion=10;2.6分布式锁使用Redis中的setnx操作来保证幂等性分布式锁中设置了Barriers。如果setnx成功,说明这是第一次插入数据,继续执行SQL语句即可。如果setnx失败,它已经被执行。2.7Token方案本方法分为申请token阶段和支付阶段两个阶段。第一阶段:在进入订单提交页面之前,订单系统需要根据用户信息向支付系统发起token申请请求,支付系统将token保存在Redis缓存中,供第二阶段支付使用。第二阶段:订单系统使用申请的token发起支付请求,支付系统会检查redis中是否存在该token。如果存在,则表示第一次发起支付请求,删除缓存中的token后开始支付逻辑处理;如果缓存中不存在,则说明是非法请求。其实这里的token可以看成是token,支付系统根据token来确认插入的唯一性。token方式的缺点是需要两个系统交互,过程比上述方式复杂。Token参考MySQL操作:https://blog.csdn.net/qq_18975791/article/details/107285455苏三说幂等:https://mp.weixin.qq.com/s/v4CamZlqHP8gEmubzPme4g幂等实现:https://blog.csdn.net/wanglei303707/article/details/88298211
