1 幂等性介绍
很多时候大家接触的这个词常在消息队列听到,如何解决MQ幂等性也是面试的一个重点,但是你正着理解这个词吗?比如现如今很多系统都会基于分布式或微服务思想完成对系统的架构设计。那么在这一个系统中,就会存在若干个微服务,而且服务间也会产生相互通信调用。那么既然产生了服务调用,就必然会存在服务调用延迟或失败的问题。当出现这种问题,服务端会进行重试等操作或客户端有可能会进行多次点击提交。如果这样请求多次的话,那最终处理的数据结果就一定要保证统一,如支付场景。此时就需要通过保证业务幂等性方案来完成。
举个例子:支付,用户购买商品后支付,支付扣款成功,但是返回结果的时候网络异常,此时钱已经扣了,用户再次点击按钮,此时会进行第二次扣款,返回结果成功,用户查询余额发现多扣钱了,流水记录也变成了两条。在以前的单应用系统中,我们只需要把数据操作放入事务中即可,发生错误立即回滚,但是再响应客户端的时候也有可能出现网络中断或者异常等等。
在增删改查4个操作中,尤为注意就是增加或者修改,查询对于结果是不会有改变的,删除只会进行一次,用户多次点击产生的结果一样,修改在大多场景下结果一样,增加在重复提交的场景下会出现。
2 restful风格调用产生的问题
GET:用于获取资源,多次操作不会对数据产生影响,具有幂等性。注意不是结果。
POST:用于新增资源,对同一个URI进行两次POST操作会在服务端创建两个资源,不具有幂等性。
PUT:用于修改资源,对同一个URI进行多次PUT操作,产生的影响和第一次相同,具备幂等性。
DELETE:用于删除资源,对同一个URI进行多次DELETE操作,产生的影响和第一次相同,具备幂等性。
综上所述,这些仅仅只是HTTP协议建议在基于RESTFUL风格定义WEB API时的语义,并非强制性。同时对于幂等性的实现,肯定是通过前端或服务端完成。
3 接口幂等性问题解决
对于幂等的考虑,主要解决两点前后端交互与服务间交互。这两点有时都要考虑幂等性的实现。从前端的思路解决的话,主要有三种:前端防重、PRG模式、Token机制。
3.1 前端防重
通过前端防重保证幂等是最简单的实现方式,前端相关属性和JS代码即可完成设置。可靠性并不好,有经验的人员可以通过工具跳过页面仍能重复提交。主要适用于表单重复提交或按钮重复点击。
3.2 PRG模式防止表单重复提交
PRG模式即POST-REDIRECT-GET。当用户进行表单提交时,会重定向到另外一个提交成功页面,而不是停留在原先的表单页面。这样就避免了用户刷新导致重复提交。同时防止了通过浏览器按钮前进/后退导致表单重复提交。
网络延迟时,重复点击提交按钮,有可能发生重复提交表单问题。
解决方案:
1.数据库主键唯一。
2.提交成功后重定向。
3.使用 JavaScript 解决,使用标记位,提交后隐藏或不可用提交按钮。 使用
Session 解决: 生成唯一的 Token 给客户端,客户端第一次提交时带着这个 Token,后台与 Session 中的 进行对比。一样则提交成功并清除 Session 中的 Token。不一样则提交失败。
4 常用Token机制解决服务幂等性
基于以下过程图,实现的话很简单就不进行代码的编写了
4.1 方案介绍
通过token机制来保证幂等是一种非常常见的解决方案,同时也适合绝大部分场景。该方案需要前后端进行一定程度的交互来完成。
以上的图片现在有一个问题,当前是先执行业务再删除token。在高并发下,很有可能出现第一次访问时token存在,完成具体业务操作。但在还没有删除token时,客户端又携带token发起请求,此时,因为token还存在,第二次请求也会验证通过,执行具体业务操作。对于这个问题的解决方案的思想就是并行变串行。会造成一定性能损耗与吞吐量降低。
第一种方案:对于业务代码执行和删除token整体加线程锁。当后续线程再来访问时,则阻塞排队。这样效率不行
第二种方案:借助redis单线程和incr(自增)是原子性的特点。当第一次获取token时,以token作为key,对其进行自增。然后将token进行返回,当客户端携带token访问执行业务代码时,对于判断token是否存在不用删除,而是对其继 续incr。如果incr后的返回值为2。则是一个合法请求允许执行,如果是其他值,则代表是非法请求,直接返回。
那如果先删除token再执行业务呢?其实也会存在问题,假设具体业务代码执行超时或失败,没有向客户端返回明确结果,那客户端就很有可能会进行重试,但此时之前的token已经被删除了,则会被认为是重复请求,不再进 行业务处理。
这种方案无需进行额外处理,一个token只能代表一次请求。一旦业务执行出现异常,则让客户端重新获取令牌,重新发起一次访问即可。
推荐使用先删除token方案但是无论先删token还是后删token,都会有一个相同的问题。每次业务请求都回产生一个额外的请求去获取token。但是,业务失败或超时,在生产环境下,一万个里最多也就十个左右会失败,那为了这十来个请求,让其他九千九百多个请求都产生额外请求,就有一些得不偿失了。虽然redis性能好,但是这也是一种资源的浪费。
5 服务幂等性
其实我也不想套娃,发现现在写的东西都以前写过,只不过就是换一个名词而已。有时候面试的时候会问道,你们是怎么解决接口幂等性,服务幂等性。其实换种说法就是是否产生重复数据,是否对现有数据产生影响,是并发高的情况下还是并发一般的情况下产生的,如何去解决。
5.1 并发不高和高并发的情况加锁解决
举个例子服务幂等了,就是超时的一个重试机制造成的重复扣库存
解决方案:Redis高并发场景下秒杀超卖解决