接口幂等性操作学习笔记
1. 幂等性
- 简单理解:幂等性就是就是用户对于同一操作发起的请求或多次请求的结果是一致的,不会因为多次点击而使得数据库或缓存出现重复数据
- 比如:商城的订单服务,同一份订单,不能多次提交,通过幂等操作使得多次提交的最终结果是只有一条数据写进数据库中
2. 什么情况需要幂等
2.1 SQL 天然幂等的情况
- select 语句 是天然幂等的
- update 语句 在指定主键或唯一值的字段作为判断条件是是幂等操作
- delete 语句 和 update 一样
- insert 语句 插入数据的字段包含主键的时候幂等
2.2 需要幂等操作的情况
- 防止用户多次点击按钮
- 放在用户页面回退再次提交
- 微服务互相调用,由于网络问题,导致请求失败。feign触发重试机制
- 其他业务情况
- …
3. 解决方案
3.1 token机制
- 服务端提供发送 token 的接口,我们在分析业务的时候,哪些业务需要进行幂等操作的,就必须在执行业务初,先获取 token ,服务器会把 token 保存到 redis 中
- 然后调用业务接口请求时,把token带过去,一般放在请求头部
- 服务器判断 token ,
- 工作流程 :第一次跳转到指定页面(比如订单页)时,服务器计算发送一个令牌给页面,在提交数据时,将这个验证码带给后端然后进行令牌比较。删除redis中的令牌,然后比较通过继续执行页面
- 危险性 : 尽量将删除令牌的操作在比较之前进行操作,并且在服务器中:获取令牌,删除令牌,比较令牌需要保证原子性,可以使用 redis 的
3.2 各种锁机制
- 数据库操作主要是通过其 悲观锁 和 乐观锁 机制
- 悲观锁 具有强烈的独占和排他特性,使用时一般随事务一起使用,数锁定事件比较长,需要根据实际情况选用,另外要注意的是,id字段一定是主键或者唯一索引,不然可能再次锁表的结果,处理起来会非常麻烦
- SQL :select * from xxxx where id = 1 for update;
- 乐观锁 一般是在数据表中加上一个数据版本号 version 字段,在每次修改时先将 version 取出来,在进行第一次插入修改操作时,根据版本号和主键id进行数据修改,同时将 version +1。这样后续的重复操作时因为带的是旧 version 就无法再对数据进行修改
- SQL :update table set column=value ,version = version +1 where id = 1 and version = 1;
- 分布式锁 集群同一服务在同一时间处理相同的数据,可以使用分布式锁来进行数据处理
3.3 各种唯一约束
- 数据库唯一约束
- 利用数据库的主键唯一约束的特性,在数据库层面进行防重,可以解决 insert 时候的幂等问题
- 比如插入数据,应该按照唯一索引进行插入,比如主键id,这样在最后插入时就不会出现两条id相同的数据。
- 主键尽量不要使用自增的主键,尽量使用全局唯一id生成策略来生成一个全局唯一id,
- 这是为了适应分库分表的情况以及路由规则保证相同的情况下,不同的数据库和表主键不相关
- redis set 防重
- 将数据进行 MD5 处理,并保存到 redis 的 set 中,这样,每次处理数据,可以先判断是否已经 MD5 处理过了,有结果在 set 中,如果存在就不再处理数据。
3.4 防重表
- 在数据库单独创建一个表,这个表的功能时用于识别哪些数据已经存在不需要继续进行操作。
- 比如订单号:将作为订单唯一标识的订单号写进防重表中,这样,在每次进行插入操作的时候,可以在防重表中进行一次订单号插入操作,如果该已经存在,订单号插入操作就会失败,最终整个数据就不会重复插入,不存在单号就将单号先写进防重表中,后续在进行其他数据插入操作。
3.5 全局请求唯一id
- 调用接口时,生成一个唯一id,redis将数据保存到集合中(去重),存在即处理过,可以设置nginx设置每一个请求的唯一id
- 建议用于处理feign调用服务的唯一id
- proxy_set_header X-Request-Id $request_id;