幂等性 :就是用户对于同一 *** 作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。
接口幂等性不只是分布式事务方便回滚的保证,同时也是SOA服务实现中摒除重复消息的保证。
那就是支付,用户购买商品使用约支付,支付扣款成功,但是返回结果的时候网络异常,此时钱已经扣了,用户再次点击按钮,此时会进行第二次扣款,返回结果成功,用户查询余额返发现多扣钱了,流水记录也变成了两条。
方法一、 单次支付请求,也就是直接支付了,不需要额外的数据库 *** 作了,这个时候发起异步请求创建一个唯一的ticketId,就是门票,这张门票只能使用一次就作废,具体步骤如下:
如果步骤4通信失败,用户再次发起请求,那么最终结果还是一样的
方法二、 分布式环境下各个服务相互调用
这边就要举例我们的系统了,我们支付的时候先要扣款,然后更新订单,这个地方就涉及到了订单服务以及支付服务了。
用户调用支付,扣款成功后,更新对应订单状态,然后再保存流水。
而在这个地方就没必要使用门票ticketId了,因为会比较闲的麻烦
(支付状态:未支付,已支付)
步骤:
1、查询订单支付状态
2、如果已经支付,直接返回结果
3、如果未支付,则支付扣款并且保存流水
4、返回支付结果
如果步骤4通信失败,用户再次发起请求,那么最终结果还是一样的
对于做过支付的朋友,幂等,也可以称之为冲正,保证客户端与服务端的交易一致性,避免多次扣款。
在微服务架构下,我们在完成一个订单流程时经常遇到下面的场景:
为了解决以上问题,就需要保证接口的幂等性 ,接口的幂等性实际上就是接口可重复调用,在调用方多次调用的情况下,接口最终得到的结果是一致的。 有些接口可以天然的实现幂等性 ,比如查询接口,对于查询来说,你查询一次和两次,对于系统来说,没有任何影响,查出的结果也是一样。
除了查询功能具有天然的幂等性之外,增加、更新、删除都要保证幂等性。 那么如何来保证幂等性呢?
多版本并发控制,该策略主要使用update with condition(更新带条件来防止)来保证多次外部请求调用对系统的影响是一致的。在系统设计的过程中,合理的使用乐观锁,通过version或者updateTime(timestamp)等其他条件,来做乐观锁的判断条件,这样保证更新 *** 作即使在并发的情况下,也不会有太大的问题。例如
在更新的过程中利用version来防止,其他 *** 作对对象的并发更新,导致更新丢失。为了避免失败,通常需要一定的重试机制。
在插入数据的时候,插入去重表,利用数据库的唯一索引特性,保证唯一的逻辑。
这种方法适用于在业务中有唯一标的插入场景中,比如在以上的支付场景中,如果一个订单只会支付一次,所以订单ID可以作为唯一标识。这时,我们就可以建一张去重表,并且把唯一标识作为唯一索引,在我们实现时,把创建支付单据和写入去去重表,放在一个事务中,如果重复创建,数据库会抛出唯一约束异常, *** 作就会回滚。
select for update,整个执行过程中锁定该订单对应的记录。注意:这种在DB读大于写的情况下尽量少用。
并发不高的后台系统,或者一些任务JOB,为了支持幂等,支持重复执行,简单的处理方法是,先查询下一些关键数据,判断是否已经执行过,在进行业务处理,就可以了。注意:核心高并发流程不要用这种方法。
在设计单据相关的业务,或者是任务相关的业务,肯定会涉及到状态机,就是业务单据上面有个状态,状态在不同的情况下会发生变更,一般情况下存在有限状态机,这时候,如果状态机已经处于下一个状态,这时候来了一个上一个状态的变更,理论上是不能够变更的,这样的话,保证了有限状态机的幂等。
这种方法适合在有状态机流转的情况下,比如就会订单的创建和付款,订单的付款肯定是在之前,这时我们可以通过在设计状态字段时,使用int类型,并且通过值类型的大小来做幂等,比如订单的创建为0,付款成功为100。付款失败为99
在做状态机更新时,我们就这可以这样控制
业务要求:页面的数据只能被点击提交一次
发生原因:由于重复点击或者网络重发,或者nginx重发等情况会导致数据被重复提交
解决办法:
处理流程:
token特点:要申请,一次有效性,可以限流
如银联提供的付款接口:需要接入商户提交付款请求时附带:source来源,seq序列号。source+seq在数据库里面做唯一索引,防止多次付款,(并发时,只能处理一个请求)
总结: 幂等性应该是合格程序员的一个基因,在设计系统时,是首要考虑的问题,尤其是在像支付宝,银行,互联网金融公司等涉及的都是钱的系统,既要高效,数据也要准确,所以不能出现多扣款,多打款等问题,这样会很难处理,用户体验也不好 。
如果使用全局唯一ID,就是根据业务的 *** 作和内容生成一个全局ID,在执行 *** 作前先根据这个全局唯一ID是否存在,来判断这个 *** 作是否已经执行。如果不存在则把全局ID,存储到存储系统中,比如数据库、redis等。如果存在则表示该方法已经执行。
从工程的角度来说,使用全局ID做幂等可以作为一个业务的基础的微服务存在,在很多的微服务中都会用到这样的服务,在每个微服务中都完成这样的功能,会存在工作量重复。另外打造一个高可靠的幂等服务还需要考虑很多问题,比如一台机器虽然把全局ID先写入了存储,但是在写入之后挂了,这就需要引入全局ID的超时机制。
使用全局唯一ID是一个通用方案,可以支持插入、更新、删除业务 *** 作。但是这个方案看起来很美但是实现起来比较麻烦,下面的方案适用于特定的场景,但是实现起来比较简单。
数据的对象和范围
你要考虑你的幂等的全局性:空间全局性和时间全局性。
空间全局性:比如是交易流水幂等还是用户ID幂等。是某种类型交易流水幂等,还是某个人|机构|渠道的交易流水幂等
时间全局性:是幂等几秒,还是几分钟,还是永远。
不同的要求,可以有不一样的解决方案、难度和成本。
幂等方案
对时间全局性要求高的,可能就必须选择DB这种持久化方案比较可靠,但是性能不够好啊(然后就要考虑loadmemory,以及数据同步的问题,就一步还要考虑实时性要求了)
在空间的要求中,根据不同的幂等范围,可以考虑分布式数据库(分布式集群全局流水号幂等)。还是某种少量数据幂等(可能只需要单台,做好主备)。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)