什么是幂等性
什么情况下需要幂等性
业务场景:
幂等性的核心思想:
通过唯一的业务单号保证幂等
非并发情况下,查询业务单号有没有操作过,没有则执行操作
并发情况下,整个操作过程加锁
增删改查幂等性
select操作:不会对业务数据有影响,天然幂等
delete操作:第一次已经删除,第二次也不会有影响
update操作:更新操作传入数据版本号,通过乐观所实现幂等性
update table set age=10 幂等
update table set age=age+1 非幂等
insert操作:此时没有唯一业务单号,使用token保证幂等性
混合操作:找到操作的唯一业务单号,有则使用分布式锁,没有可以通过Token保证幂等
update操作的幂等性
根据唯一业务号去更新数据的情况
使用乐观锁与update行锁,保证幂等
用户查询出要修改的数据,系统将数据返回页面,将数据版本号放入隐藏域
用户修改数据,点击提交,将版本号一起提交给后台
后台使用版本号作为更新条件
update set version=version+1,xxx=${xxx} where id=xxx and
version=${version}
更新操作没有唯一业务号
可使用token机制
insert操作的幂等性
有唯一业务号的insert操作,例如:秒杀,商品id+用户id
通过分布式锁,保证接口幂等。业务完成之后,不进行锁释放,
让其过期自动释放
没有唯一业务号的insert操作,比如,用户注册,点击多次
使用token机制,保证幂等,进入注册页时,后台统一生成token,
返回前台隐藏域中。用户点击提交时,将token一同传入后台,
使用token获取分布式锁,完成insert操作,业务完成之后,不进行
锁释放,让其过期自动释放
update幂等性代码实现
首先查询数据,然后将版本号查询放置在页面的隐藏域中,更新的时候,将版本号一起提交后台,只有页面的版本号和数据库中的版本号一致,才可以修改成功。同时,版本号+1。前端多次点击,到数据库更新的时候会有行锁,变成串行执行,第一次更新的时候版本号一致,更新成功,其他的更新,由于数据库中的版本号已经发生变化,更新失败。
修改页面
<form style="margin: 0 auto 0 300px" method="post" action="/user/updateUser">
<input type="hidden" name="id" th:attr="value=${user.id}" >
<input type="hidden" name="version" th:attr="value=${user.version}">
用户名:<input type="text" name="username" th:attr="value=${user.username}"> <br>
性别:
<select name="sex">
<option value="0" th:attr="selected=${user.sex == 0}">未填option>
<option value="1" th:attr="selected=${user.sex == 1}">男option>
<option value="2" th:attr="selected=${user.sex == 2}">女option>
select>
<br>
年龄:<input type="number" name="age" th:attr="value=${user.age}"><br>
更新次数:<span th:text="${user.updateCount}">span> <br>
<button type="submit">更新button>
form>
更新控制层
@RequestMapping("updateUser")
public String updateUser(User user,String token) throws Exception {
Thread.sleep(5000);
System.out.println("更新用户");
userService.updateUser(user);
return "redirect:/user/userList";
}
数据库语句
<update id="updateUser">
update t_user
<set>
username = #{username,jdbcType=VARCHAR},
sex = #{sex,jdbcType=INTEGER},
age = #{age,jdbcType=INTEGER},
update_count = update_count + 1,
version = version+1
set>
where id = #{id,jdbcType=INTEGER} and version=#{version,jdbcType=INTEGER}
update>
前台点击多次更新,页面更新次数为1
insert幂等性实现
用户名为唯一业务号
控制层
@RequestMapping("insertUser")
public String insertUser(User user) throws Exception {
Thread.sleep(5000);
System.out.println("添加用户");
userService.insertUser(user);
return "redirect:/user/userList";
}
service层
public int insertUser(User user) throws Exception {
InterProcessMutex lock = new InterProcessMutex(zkClient, "/"+user.getUsername());
// 尝试获取分布式锁
boolean isLock = lock.acquire(30, TimeUnit.SECONDS);
// 获取锁成功,则插入数据,获取失败,则直接返回0
if (isLock){
log.info(user.getUsername()+" 获取锁成功");
return userMapper.insertSelective(user);
}else{
log.info(" 获取锁失败");
}
return 0;
}
<insert>
insert into t_user id,username,sex,age
<trim prefix="values (" suffix=")" suffixOverrides=",">
#{id,jdbcType=INTEGER},#{username,jdbcType=VARCHAR},
#{sex,jdbcType=INTEGER},#{age,jdbcType=INTEGER}
trim>
insert>
前台点击4次提交按钮,只有最先到达的请求获取锁成功,然后成功插入数据,其他三次获取锁失败。
由于接口幂等性,只会插入一条数据
当没有唯一业务号的时候,
public int insertUser(User user,String token) throws Exception {
InterProcessMutex lock = new InterProcessMutex(zkClient, "/"+token);
boolean isLock = lock.acquire(30, TimeUnit.SECONDS);
if (isLock){
log.info(user.getUsername()+" 获取锁成功");
return userMapper.insertSelective(user);
}else{
log.info(" 获取锁失败");
}
return 0;
}