本文章图片来自视频:
高并发下如何保证接口幂等性?_哔哩哔哩_bilibili高并发下如何保证接口幂等性?_哔哩哔哩_bilibili高并发下如何保证接口幂等性?_哔哩哔哩_bilibili
如果有不明白的看的可以去看视频详细了解一下,如有侵权,联系我删掉本文章
幂等性:
什么是幂等性:
在多次访问同一个接口是,要求返回结果相同
幂等性和
防重复主要是为了防止产生多条重复数据。而幂等性则是在防止重复的基础上还需要每次返回的结果一致。
等幂性数据回滚
数据回滚的方法,
场景:
a->b->c->d
方法1:使用seata注解,通过分布式事务注解达到数据的回滚
方法2:在每个方法调用sql进行新增或修改时将sql语句保存下来,不执行,通过mq将sql发送到消息队列中,当方法都执行完毕后,最后通过mq统一进行sql的新增和修改和回滚。
幂等性解决方案:
在insert之前,先select,再insert。
场景:新增数据时
该方案适用于防止产生重复数据,并不适用于并发场景。在并发场景中需要配个其他方案使用
唯一索引
场景:适用于新增场景
在数据库字段中添加唯一索引
这样在第一次新增时会正常添加,到重复添加时会因为唯一索引重复进行报错。在代码中为了保证幂等性需要捕获DuplicateKeyException异常,Spring还需要捕获SQLIntegrityConstraintViolationException异常
流程:
加悲观锁
场景:支付时,用户A余额-100 ,使用update进行修改
如果产生多次访问,会一直扣减余额。
方案:
对数据进行加锁:
start transaction select * from user where id = 123 for update; update user set amount = amount -100 where id = 123; commit
流程:
图上有一定的错误,在2、3没有获得锁的时候应该是直接返回失败或其他结果,不应该重新去获取锁。
主键需要时唯一索引,存在性能问题,性能较差
乐观锁
场景:也是修改
在表中添加字段version,在修改前先进行查询,获取数据和对应的version值,修改的时候根据id和version值作为条件记性修改,同时是version值+1。当重复请求进入时,由于version已经被更新,所以会找不到需要修改的数据,影响行数为0。这种情况下为了保证幂等性可以返回修改成功。因为这条数据肯定已经被修改了
流程:
建重复表
场景:一张表中,可能有些场景可以重复,只有某些特定场景才不允许重复。
方案: 在同数据库中添加一张重复表,在需要放值重复的场景下,先往放重复表中添加唯一值,添加成功才能继续执行下面的业务,在重复请求中,向放重复表里添加值就会产生冲突报错,进而直接结束方法,返回成功。
流程:
这个方法不适用于分库分表的情况。
状态机幂等性
场景:
在一些数据中存在一些状态。如订单中存在,未支付1,已支付2,以退款3等。这些状态的规律是可循的。
方案:
在修改时可以根据这些状态进行修改,第一次请求时修改状态为3 。修改成功,重复请求进行修改时会找不到修改的数据,影响行数为0. 返回成功
流程:
分布式锁
前面的表中加唯一索引和建立放重复表严格来说也属于分布式锁的一种(使用了数据库的分布式锁),但因为其性能不算太好。所以可以代用其他中间件进行分布式锁。例如redis:
redis分布式锁一共有三种方案:setNX ,set,redission框架 这里以setNx为例:
请求会先用redis用setNx生成一个锁,如果添加成功,表示第一次请求,可以正常进行数据处理,当重复请求进入方法后。再去通过setNx生成锁时,如果已经存在锁,则会提马甲
流程:
使用分布式事务锁一定要设置一个合理的过期时间,设置时间过短则无法起到防重复作用,时间过长则浪费redis空间。
获取token
方案:
需要两次请求,第一次获取token,将token放到缓存中,第二次请求业务。在请求业务时首先判断token在缓存中是否存在,存在则表示第一次请求,将toke删除并进行业务处理,在重复请求进来后,如果token不存在,则表示是重复请求,直接返回。
流程:
幂等:
token必须全局唯一。