接口的幂等性分为两大类,第一种是有业务单据号的操作,第二种是没有业务单据号的时候。(利用Token来解决无业务单据号时候的操作)
Delate操作的幂等性
- 先查询然后再删除保证删除操作只执行一次
User user = userMapper.selectByPrimaryKey(userId);
if(user != null){
log.info("用户存在,用户ID为:"+userId);
int i = userMapper.deleteByPrimaryKey(userId);
return i;
}
log.info("用户不存在,用户ID为:"+userId);
return 0;
Update操作的幂等性
- 利用乐观锁的方式来进行幂等性的控制
public int updateUser(User user) {
return userMapper.updateUser(user);
}
<update id="updateUser">
update t_user
<set>
<if test="username != null">
username = #{username,jdbcType=VARCHAR},
</if>
<if test="sex != null">
sex = #{sex,jdbcType=INTEGER},
</if>
<if test="age != null">
age = #{age,jdbcType=INTEGER},
</if>
update_count = update_count + 1,
version = version + 1
</set>
where id = #{id,jdbcType=INTEGER}
and version = #{version,jdbcType=INTEGER}
</update>
Insert操作的幂等性
- 有业务号的insert操作,例如:秒杀,商品ID + 用户ID。
- 采用分布式锁的形式来进行幂等性的控制。
- 业务执行完毕之后分布式锁也不必要释放,自动过期释放即可。
- 没有业务单据号的insert操作,比如用户注册,点击多次。
- 使用Token操作,保证幂等性。
- 进入注册页的时候,后台统一生成一个token,然后放在前台的隐藏域当中。
- 用户在页面进行提交的时候,将Token一起提交后台。
- 在后台通过Token获取分布式锁,完成insert操作。
- 执行成功以后,不释放锁,等待过期自动释放。
1.有业务单据号(采用ZK官方推荐分布式锁 Curator)
@Configuration
public class ZKConfig {
@Bean(initMethod="start",destroyMethod = "close")
public CuratorFramework getCuratorFramework(){
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
CuratorFramework client = CuratorFrameworkFactory.newClient("localhost:2181", retryPolicy);
return client;
}
}
@Autowired
private CuratorFramework zkclient;
public int insertUser(User user) throws Exception {
InterProcessMutex lock = new InterProcessMutex(zkclient, "/"+user.getUsername());
boolean isLock = lock.acquire(30, TimeUnit.SECONDS);
if(isLock){
return userMapper.insertSelective(user);
}
return 0;
}
2.没有业务单据号(采用token的方式)
@RequestMapping("/register")
public String register(ModelMap map){
String token = UUID.randomUUID().toString();
tokenSet.add(token);
map.addAttribute("user",new User());
map.addAttribute("token",token);
return "user/user-detail";
}
- 初始化的页面的时候生成一个token,放入前台隐藏域,后台也保存一份,分布式部署的时候,后台存放token的容器使用分布式缓存Redis即可。
@RequestMapping("/updateUser")
public String updateUser(User user,@RequestParam String token) throws Exception {
Thread.sleep(5000);
if (user.getId() != null){
System.out.println("更新用户");
userService.updateUser(user);
}else{
if(tokenSet.contains(token)){
System.out.println("添加用户");
userService.insertUser(user,token);
}else {
throw new Exception("token不存在");
}
}
return "redirect:/user/userList";
}
- 将前台隐藏的token传到后台,并和之前的后台存token的容器中判断是否存在
public int insertUser(User user,String token) throws Exception {
InterProcessMutex lock = new InterProcessMutex(zkclient, "/"+token);
boolean isLock = lock.acquire(30, TimeUnit.SECONDS);
if(isLock){
return userMapper.insertSelective(user);
}
return 0;
}
- 将传过来的token作为分布式锁的key
混合操作的幂等性
- 混合操作,一个接口包括多种操作
- 同样可以使用Token的操作