龙哥网

龙哥网

分布式锁实例教程之防止重复提交_java
2022-03-01

目录
  • 抛出一个问题
  • 正常的代码逻辑
    • 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;
    }
    

    测试结果:

    并不会出现重复注册情况了。

    总结

    防重提交不能全部交给数据库

    免责声明
    本站部分资源来源于互联网 如有侵权 请联系站长删除
    龙哥网是优质的互联网科技创业资源_行业项目分享_网络知识引流变现方法的平台为广大网友提供学习互联网相关知识_内容变现的方法。