分佈式鎖實例(一)——防止重複提交

拋出一個問題

需求:現在有一個常見的場景——用戶註冊,但是如果出現重複提交的情況,則會出現多條註冊數據,因此這裏如何做好防止重複提交這是我們需要解決的問題。

正常的代碼邏輯

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;
}

測試結果:

在這裏插入圖片描述
並不會出現重複註冊情況了。

總結

防重提交不能全部交給數據庫

發佈了134 篇原創文章 · 獲贊 37 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章