抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >
  • 幂等性问题广泛出现在电商平台的购买商品和支付业务上

  • 首先,我们需要明确接口幂等性的含义,为什么需要保证接口的幂等性?

  • 然后,如何才能实现接口的幂等性,在分布式微服务架构下使用什么方案来实现呢?

Github issues:https://github.com/littlejoyo/Blog/issues/

1、什么是幂等性?

  • 含义:一个操作,不论执行多少次,产生的效果和返回的结果都是一样的。

  • 幂等函数,或幂等方法,是指可以使用相同参数重复执行,并能获得相同结果的函数。

  • 接口的幂等性:就是接口重复执行和一次执行的产生逻辑和结果都是一致的。

2、什么情况下需要保证接口的幂等性?

从数据库操作的增删改查来逐个说明情况

2.1 查询操作

  • 查询对于结果是不会有改变的,查询一次和查询多次,在数据不变的情况下,查询结果是一样的。

  • select是天然的幂等操作,无需再去实现幂等性操作。

2.2 删除操作

  • 删除一次和多次删除都是把数据删除。

  • 注意可能返回结果不一样,删除的数据不存在,返回0,删除的数据多条,返回结果多个

  • 在不考虑返回结果的情况下,删除操作也是具有幂等性的。

2.3 更新操作

  • 更新操作需要区分情况,如果是更新为固定值的话,每次执行的结果都是一致的

  • 如果是增量修改或者是减法操作,这种场景下是需要保证幂等性的

  • 情况一:update为固定值

  • 把表中id为XXX的记录的A字段值设置为1,这种操作不管执行多少次都是幂等的

  • UPDATE tab1 SET col1=1 WHERE col2=2

  • 无论执行成功多少次状态都是一致的,因此也是幂等操作。

  • 情况二:递增或者递减

  • 把表中id为XXX的记录的A字段值增加1,这种操作就不是幂等的

  • UPDATE tab1 SET col1=col1+1 WHERE col2=2

  • 每次执行的结果都会发生变化,这种不是幂等的。

2.4 新增操作

  • 增加在重复提交的场景下会出现幂等性问题

  • 举个最简单的例子,那就是支付,用户购买商品后支付,支付扣款成功,但是返回结果的时候网络异常,此时钱已经扣了

  • 用户再次点击按钮,此时会进行第二次扣款,返回结果成功,用户查询余额返发现多扣钱了,流水记录也变成了两条

  • 这就是没有保证接口的幂等性造成的结果,作为消费者你当然不可能满意

3.保证幂等性的方案

3.1 token机制

  • 1.原理上通过session token来实现的(也可以通过redis来实现)。

  • 2.当客户端请求页面时,服务器会生成一个随机数Token,并且将Token放置到session当中(或者redis中),然后将Token发给客户端(一般通过构造hidden表单)。

  • 3.下次客户端提交请求时,Token会随着表单一起提交到服务器端。

  • 4.服务器端第一次验证相同过后,会将session中的Token值更新下(如果是redis要更新键值),若用户重复提交,第二次的验证判断将失败

  • 5.因为用户提交的表单中的Token没变,但服务器端session中(或redis)的Token已经改变了。

  • 6.如果想要实现分布式服务的接口幂等性,需要使用redis来存储token值,基本原理一致,因为session仅存在单机服务器上,跨域共享要研究分布式session的实现。

3.1.1 先删除token还是后删除token

  • 后删除token:

  • 如果进行业务处理成功后,删除redis中的token却失败了

  • 下一次请求还是能对的上token值,再一次发生了接口的请求处理

  • 这样就导致了重复请求并处理的结果,因为token没有被删除

  • 先删除token:

  • 如果系统出现问题导致业务处理出现异常,业务处理没有成功,接口调用方也没有获取到明确的结果

  • 然后进行尝试重新请求,但token已经删除掉了,服务端判断token不存在,认为是重复请求,就直接返回了,无法进行业务处理了。

  • 先删除token可以保证不会因为重复请求,业务数据出现问题。

  • 如果出现业务异常,可以让调用方配合处理一下,重新获取新的token,再次由业务调用方发起重试请求就ok了,而不会导致接口重复请求得到不同的结果。

3.1.2 token机制的缺点

  • 增加了token验证的业务处理,每次请求都会额外增加进行验证的环节

  • 其实真实的生产环境中,1万请求也许只会存在10个左右的请求会发生重试,为了这10个请求,我们让9990个请求都发生了额外的请求。

  • 但是为了确保类似支付问题和订单问题的幂等性操作,这是值得的。

3.2 唯一ID

  • 调用接口的时候,内部生成一个唯一id作为调用成功的标志

  • 然后将唯一id存入redis的set集合里(去重)

  • 每次请求都会去redis中查询是否存在id,如果存在说明已经是重复请求,不存在是第一次请求,从而保证了接口的幂等性

3.3 乐观锁机制

  • 乐观锁机制主要应用在更新的场景中,在进行更新操作前先获取version版本号,然后操作的时候也要带上version号。

  • 例如:update t_goods set count = count -1 , version = version + 1 where good_id=2 and version = 1

  • 第一次操作库存时,得到version为1,调用库存服务version变成了2;

  • 但返回给订单服务出现了问题,订单服务又一次发起调用库存服务,当订单服务传如的version还是1,再执行上面的sql语句时,就不会执行;

  • 因为version已经变为2了,where条件就不成立。这样就保证了不管调用几次,只会真正的处理一次。

  • 乐观锁主要应用于读多写少的情况下。

3.4 唯一主键

  • 利用了数据库的唯一约束的特性,解决了在insert场景时幂等问题。

  • 要点:唯一索引或唯一组合索引来防止新增数据存在脏数据。

  • 防止新增脏数据。比如:支付宝的资金账户,支付宝也有用户账户,每个用户只能有一个资金账户,给资金账户表中的用户ID加唯一索引,保证一个用户只能有一条资金账户数据

  • 当表存在唯一索引,并发时新增报错时,再查询一次就可以了,数据已经存在了,返回存在的结果即可,避免了出现新增多条数据的情况;

3.5 建立防重复表

  • 例如使用订单号orderNo做为去重表的唯一索引,把唯一索引插入去重表,再进行业务操作,且他们在同一个事务中。

  • 这个保证了重复请求时,因为去重表有唯一约束,导致请求失败,避免了幂等问题。

  • 这里要注意的是,去重表和业务表应该在同一库中,这样就保证了在同一个事务,即使业务操作失败了,也会把去重表的数据回滚。这个很好的保证了数据一致性。

3.6 状态机幂等性

  • 在设计单据相关的业务,或者是任务相关的业务,肯定会涉及到状态机(状态变更图),就是业务单据上面有个状态,状态在不同的情况下会发生变更

  • 一般情况下存在有限状态机,这时候,如果状态机已经处于下一个状态,这时候来了一个上一个状态的变更,理论上是不能够变更的,这样的话,保证了有限状态机的幂等。

  • 注意:订单等单据类业务,存在很长的状态流转,一定要深刻理解状态机,对业务系统设计能力提高有很大帮助

  • 设置固定的状态类型,只有同时满足该状态类型下的多个状态,才能执行

  • 比如存在两张订单:

  • A:订单

待付款
待发货 contain 待发货
已发货 全部是已发货 已收货 退款 退货
已收货 全部已收货
退款中
退款成功
退款失败
退货中
退货成功
退货失败
已取消

  • B:订单(不存入库状态)

待付款
待发货(未确认、已确认)
已发货
已收货
退款中
退款成功
退款失败
退货中
退货成功
退货失败
已取消

4.总结

  • 接口的幂等性就是保证一次请求和重复请求后,获得的返回结果是一样的

  • 实现幂等性的方案有多种,具体选择要根据业务进行分析

  • 应用在分布式的架构下,token机制是目前使用较为广泛的其中一种方案

  • 幂等性应该是合格程序员的一个基因,在设计系统时,是首要考虑的问题,尤其是在像支付宝,银行,互联网金融公司等涉及的都是钱的系统,既要高效,数据也要准确,所以不能出现多扣款,多打款等问题,这样会很难处理,用户体验也不好。

微信公众号

扫一扫关注Joyo说公众号,共同学习和研究开发技术。

weixin-a

评论