动态每日更新算法,想要一起学习的关注一下
文章目录
一、接口幂等性
1.接口幂等性的概念
幂等性原本是数学上的概念,用在接口上就可以理解为:同一个接口,多次发出同一个请求,必须保证操作只执行一次。 调用接口发生异常并且重复尝试时,总是会造成系统所无法承受的损失,所以必须阻止这种现象的发生。
比如下面这些情况,如果没有实现接口幂等性会有很严重的后果: 支付接口,重复支付会导致多次扣钱 ;订单接口,同一个订单可能会多次创建。
2.什么时候会发生接口幂等性
- 网络波动, 可能会引起重复请求
- 用户重复操作,用户在操作时候可能会无意触发多次下单交易,甚至没有响应而有意触发多次交易应用
- 使用了失效或超时重试机制(Nginx重试、RPC重试或业务层重试等)
- 页面重复刷新
- 使用浏览器后退按钮重复之前的操作,导致重复提交表单
- 使用浏览器历史记录重复提交表单
- 浏览器重复的HTTP请求
- 定时任务重复执行
- 用户双击提交按钮
二、防止接口幂等性
1.token机制(防重令牌)(推荐)
如上图所说:
- 服务器发放一个token令牌,这个令牌可以是uuid或者别的什么验证码生成的,且服务器将这个token令牌存入Redis。
- 当客户端再次访问服务端时会在请求头中带上token令牌
- 服务端收到令牌与Redis进行比较,若不存在则认为是重复提交,若存在则进行lua脚本进行原子性删除,然后进行业务代码。
这里特别说明一下,为什么使用lua脚本
先看下面代码
String script= "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Long execute = redisTemplate.execute(new DefaultRedisScript<>(script,Long.class), Arrays.asList(OrderConstant.USER_ORDER_TOKEN_PREFIX + memberResponseVo.getId()), submitVo.getOrderToken());
if (execute == 0L) {
//1.1 防重令牌验证失败
return ;
}else {
//2. 进行业务代码
这是使用lua脚本的,如果不使用lua脚本,在服务器收到令牌时,服务器在和Redis进行交互时可能会出现网络延迟或者中断,来不及删除令牌,而客户端在此时已经重复提交,这样服务端会进行两次查询请求,且都能查询到令牌。
2. 数据库锁机制
数据库锁机制,但是都存在一些弊端,悲观锁存在锁定时间过长的问题。
而乐观锁和ElasticSearch一样,在每次更新操作时,都会带上version版本号,但是这种操作更适合更新操作,在查询操作上补起作用。
3. 分布式锁
分布式锁的介绍在Redis缓存穿透、雪崩、击穿以及分布式锁和本地锁
此文中介绍,可以借鉴一下,但是究其本质还是操作Redis
4. 唯一约束(md5加密)
数据库约束没啥好说的。这里详细介绍一下MD5盐值加密。
MD5加密就是对字符串进行加密,每一个字符串得到加密后的MD5字符串都是相同的,其实也就是和哈希表一样,这个字符串作为key,而生成的字符串作为value,他俩就是对应的。
但是这种做法容易被破解(可以使用穷举法),所以采用随机盐值加密,在MD5加密时使用 Spring Security。
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
String encode = bCryptPasswordEncoder.encode(vo.getPassword());
memberEntity.setPassword(encode);
memberEntity.setMobile(vo.getPhone());
memberEntity.setGender(0);
memberEntity.setCreateTime(new Date());
5.防重表
以支付为例: 使用唯一主键去做防重表的唯一索引,比如使用订单号作为防重表的唯一索引,每一次请求都根据订单号向防重表中插入一条数据,插入成功说明可以处理后面的业务,当处理完业务逻辑之后删除防重表中的订单号数据,后续如果有重复请求,则会因为防重表唯一索引原因导致插入失败,直接返回操作失败,直到第一次请求返回结果,可以看出防重表作用就是加锁的功能。
注: 最好结合状态机幂等先判断一下
6.全局id
比如通过source来源 + 唯一序列号传入给后端,后端来判断请求是否重复,在并发时只能处理一个请求,其他相同并发请求要么返回请求重复,要么等待 前面请求执行完成后再执行。