分布式锁实例教程之防止重复提交
目录
- 抛出一个问题
- 正常的代码逻辑
- 1、注册controller
- 2、注册service
- 3、加入分布式锁,问题依旧
- 解决方法
- 分布式锁+防重操作
- 总结
抛出一个问题
需求:现在有一个常见的场景——用户注册,但是如果出现重复提交的情况,则会出现多条注册数据,因此这里如何做好防止重复提交这是我们需要解决的问题。
正常的代码逻辑
1、注册controller
/** * 用户注册请求 * @param userDto * @param bindingResult * @return */ @RequestMapping(value=prefix+"/db/register",method = RequestMethod.POST,consumes = MediaType.APPLICATION_JSON_UTF8_VALUE) public BaseResponse register(@RequestBody @Validated UserDto userDto, BindingResult bindingResult){ BaseResponse response=new BaseResponse(StatusCode.Success); try { log.debug("注册信息: {} ",userDto); //注册之前,我们先判断是否已经注册了。(正常逻辑) User user=userService.selectByUserName(userDto.getUserName()); if (user!=null){ return new BaseResponse(StatusCode.UserNameExist); } userService.register(userDto); }catch (Exception e){ e.printStackTrace(); response=new BaseResponse(StatusCode.Fail); } return response; }
在controller中判断用户是否已经注册,如果没有注册,则调用注册逻辑。
2、注册service
/** * 用户注册——最普通的操作,没有任何加锁,没有任何防止重复提交 * * @param userDto * @return * @throws Exception */ public int register(UserDto userDto) throws Exception { int result = 0; User user = new User(); BeanUtils.copyProperties(userDto, user); result = userMapper.insertSelective(user); return result; }
简单的增加一个用户信息。
问题也很明显,这样毕竟会出现问题,并发的问题,也会出现重复注册的情况。测试结果也很明显
一堆重复注册的,但是加入分布式锁就好了么?
3、加入分布式锁,问题依旧
分布式锁的实现方式
/** * 用户注册,基于redisson的分布式锁 * * @param userDto * @return */ public int registerLockRedisson(UserDto userDto) { int result = 0; RLock rLock = redissonLockComponent.acquireLock(userDto.getUserName()); try { if (rLock != null) { User user = new User(); BeanUtils.copyProperties(userDto, user); user.setCreateTime(new Date()); userMapper.insertSelective(user); } } catch (Exception e) { log.error("获取redisson分布式锁异常"); } finally { if (rLock != null) { redissonLockComponent.releaseLock(rLock); } } return result; }
加入分布式锁之后,再进行测试。
不好意思,依旧出现了重复注册的情况。何解?
问题分析,为了遵循单一职责,这里的读取数据(判断是否注册)与写入数据(用户注册)操作是分开的,分布式锁为了进一步细化,只是加在了写入数据阶段,并没有加在整个业务阶段,因此会出现数据重复提交的问题,解决方法有很多,最暴力的方法无非就是给数据库user表中的用户名字段加入唯一约束。但是这样随着业务规模扩大,数据库压力会越来越大。
解决方法
解决方法有几种,前面提到的给数据库增加唯一索引也是一种方法。但是为了减轻数据库的压力,这种操作可以直接在应用层处理。
分布式锁+防重操作
在分布式锁的基础上,加入redis存储key值,作为防重提交的判断。不想过多解释了,直接上代码吧。
/** * 用户注册,redisson分布式锁,redis防止重复提交 * * @param userDto * @return */ public int registerLockAvoidDupPost(UserDto userDto) { int result = 0; RLock rLock = redissonLockComponent.acquireLock(userDto.getUserName()); try { //redis中根据用户名存储作为key值 String key = lockKeyPrefix+userDto.getUserName(); if (!stringRedisTemplate.hasKey(key)) {//如果不存在key则进入注册阶段 stringRedisTemplate.opsForValue().set(key,UUID.randomUUID().toString(),10L,TimeUnit.SECONDS); User user = new User(); BeanUtils.copyProperties(userDto, user); user.setCreateTime(new Date()); userMapper.insertSelective(user); log.info("{},注册成功",userDto.getUserName()); }else{//如果存在,则提示不可重复提交 log.error("10秒内,请勿重复提交注册信息"); } } catch (Exception e) { log.error("获取redisson分布式锁异常"); } finally { if (rLock != null) { redissonLockComponent.releaseLock(rLock); } } return result; }
分布式锁的实现方式有多重,redis/redisson/zookeeper等,只需要在已经实现分布式锁的基础上引入防重提交的机制即可。
因此还有其他方式的实现,如下所示为zookeeper分布式锁+redis防重的方式
/** * 用户注册,redisson分布式锁,redis防止重复提交 * * @param userDto * @return */ public int registerLockAvoidDupPost(UserDto userDto) { int result = 0; InterProcessMutex mutex=new InterProcessMutex(client,zkPrefix+userDto.getUserName()+"-lock"); try { if (mutex.acquire(10L, TimeUnit.SECONDS)){ final String realKey=zkRedisKeyPrefix+userDto.getUserName(); if (!stringRedisTemplate.hasKey(realKey)){ stringRedisTemplate.opsForValue().set(realKey, UUID.randomUUID().toString()); User user=new User(); BeanUtils.copyProperties(userDto,user); user.setCreateTime(new Date()); userMapper.insertSelective(user); log.info("{},注册成功",userDto.getUserName()); }else{ log.error("10秒内,请勿重复提交注册信息"); } }else{ throw new RuntimeException("获取zk分布式锁失败!"); } }catch (Exception e){ e.printStackTrace(); throw e; }finally { mutex.release(); } return result; }
测试结果:
并不会出现重复注册情况了。
总结
防重提交不能全部交给数据库
到此这篇关于分布式锁实例教程之防止重复提交的文章就介绍到这了,更多相关分布式锁防止重复提交内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!
赞 (0)