明明一次提交却创建多个之幂等问题

73 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第5天,点击查看活动详情

一、前言

在日常的业务系统开发中,部分人和大多数新手都会犯错的点,明明在提交表单、创建属性时点了一次突然卡了一下,回过头一刷新咔咔出来一堆相同的数据,ID 却还不同,不得不手动去删除多余的数据。

这就是由于网络,用户操作的问题多次发送请求,服务端对于请求没有进行判断,以为每个请求都是用户需要的,重复执行的结果。这就是经常说的幂等性问题,下面一起梳理幂等相关的问题。

二、什么是幂等性,为什么需要幂等性

在上面讲的创建数据问题,其实也还好可以进行删除,但是如果放在转账的业务中,放在下单功能上呢。由于重复发了多个请求,明明转账100块,变成了200块,明明库建库存是需要一次,缺扣除了两次。在这写业务场景中,会造成严重的后果,所以我们提出了一个名称幂等性,含义是不管掉多少次,都是一次的结果

上面讲的是用户操作中,在服务端也会出现类似的情况,例如微信支付的回调,消息发送到MQ,重复消费的问题。总而言之需要幂等性的地方还是蛮多的,下面我们介绍一下如何处理这些问题。

三、如何设计出幂等性方法

我们以日常开发中最常见的form表单提交为例。我们需要给平台创建一个商品,商品是通过一个大表单进行传递的。

1. 通过数据库唯一标识

在数据库中我们可以以商品的唯一code作为唯一标识,在我们执行多次添加时,当第二次插入到数据库时,数据库会做出校验,发现已经存在此code的数据,提示添加失败

2. 通过查询避免重复提交

用数据库的唯一索引作为幂等性方法的实现往往都是最后的一步保障,也就是说一般的在插入数据库之前就需要判断出是否已经存在。所以有了下面的方法。

因为我们每次都是无脑插入才导致的数据重复,那我们在插入时先去查询数据是否已经涵盖此商品的标识,若已经存在,则不插入的方式来达到我们的幂等性。

3. 令牌桶

上述通过加一次查询来判断数据的有效性,但是在并发大的情况下这种方式并不推荐,因为多一次查询就会对数据库多一次的压力,在实际生产中,若像是后台管理系统是可以通过查询来避免,若是互联网项目,并发需求大并不推荐这种方法,所以我们又有了令牌桶的方式。

何为令牌桶,顾名思义,每次请求带着桶里的令牌,他可以是UUID,也可以是数字,只要在桶里没有重复的数据即可。在这种方法中不需要唯一标识,我们在提交请求的时候带着桶内的令牌,在我们执行实际的业务逻辑前,去桶里看一下有没有此令牌,若有则删除桶内相应的令牌,执行方法;若不存在,说明这个令牌已经用过或者不符合规范,业务逻辑直接跳过,返回错误。

令牌桶适合在并发教大,接口需要幂等性时使用,获取令牌时方法是默认掉一次的,如果有人恶意获取令牌,去刷请求也是不能避免的。

4. 乐观锁

乐观锁实现方式还是很简单的,而且效率也不错,在面向一般请求量时有很好的效果。

乐观锁在操作数据时,则非常乐观,认为别人不会同时在修改数据,因此乐观锁不会上锁。只是在执行更新的时候判断一下,在此期间别人是否修改了数据。

如何去实现呢,那就是在表中添加一个version版本字段,每次修改时使这个版本自增,每次修改时,把之前的版本写入SQL的where条件中。

update goods set status="已完成", version=version+1 where id="1001" and version=1;

更新成功之后才可以处理业务逻辑,如果更新失败,默认标记为重复请求,直接返回。

5. 悲观锁

有乐观就会有悲观锁。

通俗点讲就是很悲观,每次去操作数据时,都觉得别人中途会修改,所以每次在拿数据的时候都会上锁。官方点讲就是,共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程。

简单讲就是在SQL语句中加上... for update,这样这条数据就会被锁定,只有当前线程可以修改。虽然可行,但是在实际生产中并不会使用,因为这样会导致其他用户只能等待,用户体验不好,而且耗时比较长,就很影响接口性能。

6. 其他方式

例如在提交按钮上做文章,点击后做一个loading,方式误操作等。

四、总结

说了这些方法,没有一个是最好的,只有最适合的,在不同的业务场景中会有不同的要求,并发、速度、正确性、用户体验等,甚至是开发速度,挑选一个在当前业务场景满足的方法进行开发,后期在进行维护升级优化即可,毕竟业务系统还是有相应的开发进度要求的。