# 什么是幂等性
任意多次执行所产生的影响均与一次执行的影响相同。
简述:某个操作多次重复执行,最终结果是一致的,不会因为多次重复执行对系统造成影响。
# 什么操作会出现幂等性问题
项目里主要处理**添加接口**的幂等和**特殊情况**下的**编辑接口**幂等
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)
- 学习地址
- MySQL
- 查询优化
- SQL优化
- 关于or、in、not in、!=等走不走索引的说明
- 千万级数据查询优化
- MySQL 深度分页问题
- 嵌套循环 Block Nested Loop 导致索引查询慢
- MySQL增加日志统计表优化各种日志表的统计功能
- MySQL单机读写QPS(性能)优化
- sqlMode 置 select 的值可以比 group 里的多
- drop、delete、truncate的区别
- 尚硅谷MySQL数据库高级学习笔记
- MySQL架构
- 事务部分
- MySQL知识点
- mysql索引
- Linux docker安装 mysql 8.0.25
- docker 安装mysql 5.7
- mysql Field ‘xxx’ doesn’t have a default value
- mysql多实例
- docker中的sql文件导入
- mysql进阶知识
- mysql字符集
- 连接的原理
- redo日志
- InnoDB存储引擎
- InnoDB的数据存储结构
- B+树索引
- 文件系统-表空间
- Buffer Pool
- 亿级数据导入到es
- MySQL数据复制
- MySQL缺少主键的表数据
- mysql update 其中更新的字段根据另一个更新字段作为条件去更新
- MySQL指定字段值排序(将指定值排在前面)
- 设置MySQL连接数、时区
- Navicat15右键删除数据刷新就又恢复了
- MySQL替换字段部分内容
- Java和MySQL统计本周本月本季和年
- 分页时order by 排序数据重复,丢失
- mysql同一张表根据某个字段删除重复数据
- mysqldump定时全量热备
- 专题总结
- 事务
- MySQL事务
- spring事务
- spring事务本类调用
- spring事务传播行为
- spring事务失效问题
- 锁和Transactional注解一块使用的问题
- 数据安全
- 敏感数据
- SQL注入
- 数据源
- XSS
- 接口设计
- 缓存设计
- 限流
- 自定义注解实现根据用户做QPS限流
- 架构
- 高可用
- Java
- Unsatisfied dependency expressed through field ‘baseMapper‘
- mybatisplus多数据源
- 单个字母前缀的java变量
- spring
- spring循环依赖解决
- 事务@Transactional
- yml 文件配置信息绑定到java工具类的静态变量上
- @Configuration @Component 区别
- springboot启动yml文件报错
- spring方法重试注解Retryable
- spring读取yml集合数据
- spring自定义注解
- 获取resource下的图片资源
- 手机号和电话号的正则验证
- 获取字符串中的数字
- mybatis
- mybatis多参数添加数据并返回主键
- 统一异常处理
- 分组校验
- Java读取Python json.dumps 函数保存的redis数据
- springboot整合springCache
- 若依mybatis值为null的字段没有返回
- 若依
- 接口白名单
- @JsonFormat时区问题
- RequestParam.value() was empty on parameter 0
- jdk8和hutool请求第三方的https报错
- springMVC
- springMVC与vue使用post传数组
- elementUI 时间组件报错问题
- vue具名插槽slot
- springboot配置maven的profiles(配置微服务多环境切换打包)
- resources 配置文件读取顺序
- Windows的cmd部署jar注意事项
- Java基础
- JUC(锁-并发-线程池)
- CAS
- Java 锁简介
- synchronized和Logk有什么区别?用新的ock有什么好处
- synchronized锁介绍
- CompletableFuture
- 多线程
- 线程池
- 集合类
- map见过的小问题
- 退出双层循环
- StringBuilder和StringBuffer核心区别
- 日志打印
- 打印log日志
- log日志文件生成配置
- 日期时间
- 时间戳转为时间
- 并发工具
- 连接池
- http调用
- 内网访问天地图
- 判等问题
- 数值计算
- null问题
- 异常处理
- 文件IO
- 序列化
- 内存溢出OOM
- 子线程的错误, 全局异常处理捕获不到
- vue同一个项目访问多个不同ip地址接口
- Autowired注解导入为null
- shiro
- UnavailableSecurityManagerException错误
- Windows服务器80端口被占用
- java图片增加水印
- springcloud
- Feign方法配置错误导致jar包启动失败
- feign调用超时
- 定时任务quartz
- JavaPOI导出Excel
- 合并行和列
- 设置样式
- 设置背景色
- docker
- Linux 安装
- docker命令
- docker网络
- docker数据卷
- dockerfile
- docker安装ping命令
- docker-compose
- docker-compose文件内容介绍
- Linux关闭docker开机启动
- jar打包为镜像
- 迁移docker容器存储位置
- Nginx
- Linux在线安装Nginx
- nginx.conf 核心配置文件
- vue 和 nginx 刷新页面会报404
- nginx 转发给三个集群的tomcat
- ServerName匹配规则
- Nginx负载均衡策略
- location 匹配规则
- Nginx 搭建前端调用后台接口的集群
- alias与root
- nginx 拦截 post 请求, 带参数转发到前端页面
- 防盗链配置
- Nginx的缓存
- 通用Nginx配置
- nginx配置文件服务器
- 后台jar包得不到正确ip,nginx代理时要处理
- 升级使用websocket协议
- 设置IP黑/白名单
- Redis
- 缓存数据一致性
- 内存淘汰策略
- Redis数据类型
- gmt6
- Linux安装GMT6
- GMT6配置中文
- GMT文件修改Windows版本到Linux版本
- 注意GMT不同字体导致符号不同的问题
- GMT绘制南海诸岛小图
- GMT生成中文图例
- elasticsearch
- 安装配置
- Linux安装配置elasticsearch7.6.2
- Linux 安装 kibana 7.6.2
- 安装7.6.2中文分词器
- docker 安装elasticsearch7.6.2
- 安装Logback7.6.2
- springboot使用
- 0. elasticsearch账号密码模式访问
- 1. 配置连接
- 2. 索引
- 3. 批量保存更新
- Result window is too large 10000
- elasticsearch 分词的字段做排序 fielddata, 设置fielddata=true 无效果
- elasticsearch 完全匹配查询(精确查询)
- 模糊搜索
- 日期区间查询
- 6.x基础知识
- 自定义词库
- elasticsearch集群
- 搜索推荐Suggester
- 查询es保存的数组
- 亿级mysql数据导入到es
- es 报错 ORBIDDEN/12/index read-only
- es核心概念
- es的分布式架构原理
- 优化大数据量时的ES查询性能
- canal
- 1. mysql的Binlog
- 2. Canal 的工作原理
- 3. canal同步es
- JVM
- 1 类的字节码
- 2. 类的加载
- JVM知识点
- Maven
- 依赖冲突
- xxl-job
- docker 安装配置 xxl-job
- idea
- springboot启动报错命令过长
- services统一启动微服务各模块
- 云服务器安装宝塔面板
- 突然出现启动或者运行特别慢
- 有导入依赖但是显示红色同时点击进去也有依赖
- Linux
- sh文件执行报错: command not found
- 使用vagrant安装虚拟机
- Linux 开启端口
- 开放端口
- 复制文件夹及其文件到另一个文件夹
- 两个服务器之间映射端口
- TCP协议
- 分层模型
- TCP概述
- 支撑 TCP 协议的基石 —— 首部字段
- 数据包大小对网络的影响 —— MTU 与 MSS 的奥秘
- 端口号
- 三次握手
- TCP 自连接
- 四次挥手
- TCP 头部时间戳
- 分布式
- 分布式脑裂问题
- 分布式事务
- 基础知识
- 实现分布式事务的方案
- 阿里分布式事务中间件seata
- 幂等性问题
- 其他工具
- webstorm git提交代码后project目录树不显示
- 消息队列
- 如何保证消费的顺序
- 数据结构
- 漫画算法:小灰的算法之旅