多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
# 什么是幂等性 任意多次执行所产生的影响均与一次执行的影响相同。 简述:某个操作多次重复执行,最终结果是一致的,不会因为多次重复执行对系统造成影响。 # 什么操作会出现幂等性问题 项目里主要处理**添加接口**的幂等和**特殊情况**下的**编辑接口**幂等 1. 重复提交问题:用户多次连续点击提交按钮或者网络波动再次点击提交 2. 重复点赞 3. Mq重复消费消息 4. 更新库存:增减商品库存,领取优惠券 # 常用解决方案 Token令牌,版本号,业务唯一标识 ## Token令牌 主要是解决表单重复提交问题 简单方式:每次进入表单页面,前端去后台获取生成的全局唯一Token,后台以token为key放到redis中,点击提交时一起发给后台,同时前端将按钮置灰,存在则删除 token,执行方法。 这个存在的问题是:先删除token,还是先处理业务。 先删除token:业务数据校验失败或其他异常,需要重新修改提交,则此token已删除了。 后删除token:网络波动,业务复杂可能会出现这种情况:先处理业务,那么未删除token前,前端再次点击请求过来又将执行方法了。 复杂版本:token对应的value 有状态,value 状态为 1 是可提交,2是执行中,3是执行完成。点击提交时一起发给后台,同时前端将按钮置灰,后台验证token以判断后续流程。 ![](https://img.kancloud.cn/19/55/195512114f4c3381cc41265f1f8de384_830x365.png) ## 版本号 数据库增加数字类型的版本号字段,当读取数据时,会将 version 字段的值一同读出,更新时版本号加1,提交更新时会用当前数据里的版本号和数据库中的版本号比对,如果不一致则表示有人修改过了。适合读多写少的业务。 ## 唯一标识 优惠券重复领取:优惠券主键和用户id做联合唯一校验,如果存在了则表示已领取 点赞:用户id和被点赞id做联合唯一校验,如果存在了则表示已点赞 ## 实际案例1(token解决表单重复提交) 非洲的后台管理项目,那边的网速比较慢,也没有并发,就是网络差 当时只是前端页面按钮加loading 效果的,没使用token机制,超时之后,就又能点击了,导致成功了俩条。 ### 需要解决的问题 1. 接口幂等性问题 2. 用户环境网速差,超时之后提交问题,单纯token无法处理 ### 解决方式 大概内容如下:细节记不清了 使用token: 前端进入表单页面后, 向后台请求一个唯一token,后台以token为key放到redis中了(过期时间不要太短), value默认1, 用于在点击提交时传给后台, 后台通过此token在redis中的值来判断是不是执行接口方法。 value有三个值, 1 初始值, 2 正在执行, 3 执行完成 第一次获取token提交时, 在aop的前置通知里判断token在redis中是否存在, 存在且等于1, 则将此token改为2,如果token 为2 则表示方法正在执行中, 如果为3, 表示方法已执行完成,也就是只有1时才继续执行 在aop的后置返回通知(在目标方法返回之后执行)里,判断返回的状态码是200吗, 是修改value值为 3, 否则改为1.(因为有些参数校验失败, 也是在这里捕获) 在aop的异常通知里修改value值为 1 这样用户第一次提交, value是1, 正常执行, 如果是超时以后, 方法value是2或者3就提示用户相应信息。 如果第一次提交报错了或者表单数据校验没通过, 那么value初始化为1了, 用户再次点击提交就相当于第一次提交 ### 实际案例2 库存更新 [电商库存系统的防超卖和高并发扣减方案](https://xie.infoq.cn/article/741d651fdc8b4daf824fb8b31) 当时是订单下单时预扣库存,支付后才实际减库存。锁定库存字段+真实库存字段 #### MySQL直接更新 流量小,无并发,直接用java层面的锁或者MySQL行锁解决,减库存是在一条sql里完成, 也会判断库存字段-具体扣减数>0才执行(就是update 表 库存数量=库存数量-具体扣减数 where 库存数量-具体扣减数要>0)防止扣成负数,更新完成记录流水日志 #### Redis + MySQL 先在redis中扣减, 在异步落库 #### 实际案例3(唯一标识解决点赞) 使用唯一标识方式:用户id和被点赞业务id做唯一标识 简单版本:流量小且可以接受适度重复点赞。点赞后前端直接修改为不能点击,后台加锁查询下是否已点赞, 点赞和查询都直接在数据库。三无项目我们就这么应付, 这个可以实时显示点赞数量, 开发简单。 复杂:redis + mysql 在redis中点赞, 然后异步落库(mq或者定时任务), 如果redis宕机,会有丢失数据风险 Redis 使用 set 类型, set集合有方法判断 value 元素是否存在于集合 key 中(list没有) Key是业务标识:用户id value是 被点赞业务id 列表 ``` // 视频都有哪些用户点赞 格式 video:likeuser:videoId1 userId1 userId2 userId3 userId4 sadd video:likeuser:videoId1 userId1 用户1给视频1点赞了 ``` 可以获取当前用户是否点赞(set可以判断value是否在key中)、当前视频点赞数量 // 用户给哪些视频点赞 这个根据业务确定是否需要 ``` sadd video:userlike:userId1 videoId1 用户1给视频1点赞了 ``` Set 支持取交集并集 ![](https://img.kancloud.cn/a4/6b/a46bf88e31e405d6c8691a2738a9e304_831x190.png)