被 Netty 搞昏了头,先学一下幂等性压压惊吧,java 程序员面试宝典 pdf
理解
所谓幂等性,就是无论执行多少次,都会只执行一次,多次的影响和一次是一样的,比如我们新增用户,无论执行多少次,都只会保存一次,避免了重复提交
幂等性运用的范围也是很多,比如以下几个:
保存用户信息,前端重复提交相同的数据,后端接口对于这个数据只会保存一次,无论重新提交多少次,也只会完成一次
用户支付,无论提交多少次,他只能有一次成功,只能扣一次钱
**验证码,**相同的的验证码只能发送一次,不能重复发送 等等
三、实现幂等性
=======
1、实现的方式
mysql 的唯一索引,如果索引存在,就会抛出异常,也就保证了重复提交问题
悲观锁
乐观锁
redis 实现,将生成的 token 保存在 redis 中,提交之后,将 token 删除,重复发起请求,获取不到 redis 存储的 token 就无法重复提交,直接提示用户:
重复保存
2、redis 和 lua 实现幂等性
我们这里使用 redis 和 Lua 的方式实现,为什么使用 lua?
因为 lua 是原子操作,原子操作从开始就要一直到执行结束,中间不会有线程切换,所以可以解决高并发的问题
2.1 不使用幂等性存在的问题
我们先看一下用户保存操作:
controller
@Log("用户创建")
@ApiOperation(value = "用户创建",notes = "用户创建")
@PostMapping("/createUser")
public GoflyResponse createUser(@Valid UserDto userDto) throws GoflyException {
User user = new User();
BeanUtils.copyProperties(userDto,user);
iUserService.createUser(user);
return new GoflyResponse().code(HttpStatus.OK).message("成功");
}
postman 多次提交用户保存操作
提交三次之后,新增了同样的三个用户,所以这个是错误的,需要修改
| USER_ID | USERNAME | PASSWORD | NAME
| 9 | yangyangyang | fcea920f7412b5da7be0cf42b8c93759 | 杨羽茉
| 10 | yangyangyang | fcea920f7412b5da7be0cf42b8c93759 | 杨羽茉
| 11 | yangyangyang | fcea920f7412b5da7be0cf42b8c93759 | 杨羽茉
2.2 redis 和 lua 解决问题
lua 脚本----> checkidem.lua
放在 resources 目录下
//获取 token----> redis: get token
local current = redis.call('GET',KEYS[1])
//如果不存在返回-1
if current == false then
return '-1'
end
//访问一次之后,删除 token
local isdel = redis.call('DEL',KEYS[1])
//如果删除成功就返回'1'
if isdel == 1 then
return '1'
else
//删除失败,说明 token 不存在
return '0'
end
redis 工具类------> RedisLuaUtil.java
用于运行 lua 脚本
/**
redis 解析 lua 工具类
*/
@Component
public class RedisLuaUtil {
//注入 stringRedisTemplate
@Resource
private StringRedisTemplate stringRedisTemplate;
//运行 lua 脚本
public String runLuaScript(String luaFileName, List<String> keys){
DefaultRedisScript<String> redisScript = new DefaultRedisScript<>();
//读取 lua 脚本文件
redisScript.setScriptSource(
new ResourceScriptSource(
new ClassPathResource(luaFileName)
)
);
//lua 返回值类型
redisScript.setResultType(String.class);
//值参数
String argson = "none";
//执行 lua 脚本,获取返回值,lua 返回的内容主要看
String result = stringRedisTemplate.execute(redisScript, keys, argson);
return result;
}
/**
校验 token
@param request
*/
public String checkToken(HttpServletRequest request) {
//获取 token,从 request 中获取
String token = request.getParameter("token");
//将 token 存储到 list 中
List<String> keys = new ArrayList<>();
keys.add(token);
//调用运行 lua 脚本的方法,并且传入 lua 文件的名称及 token 集合
String result = this.runLuaScript("checkidem.lua", keys);
//根据自定义的 lua 脚本,会返回三种返回值:
//1. GET token 不存在时返回-1
//2. DEL token 成功时,返回 1
//3. DEL token 不成功,返回 0
if(result.equals("1")){
return "保存成功";
}else{
return "请勿重复提交";
}
}
评论