redis分布式鎖在springboot中的實現


## 理論知識

  redis分布式鎖的實現方案請參考文章 如何優雅地用redis實現分布式鎖

本案例簡介

  以秒殺活動為例子,在多線程高並發的情況下需要保證秒殺業務的線程安全性,確保秒殺記錄與所扣庫存數量想匹配。

加鎖與解鎖核心代碼

該段代碼可以解決理論知識中的各種問題,包括鎖住的時候出現異常,死鎖等(通過比較時間戳的方式)



import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

/**
 * Redis分布式鎖的實現
 * @author : wang zns
 * @date : 2019-05-10
 */
@Component
@Slf4j
public class RedisLock {


    @Autowired
    private StringRedisTemplate redisTemplate;
    
    /**
     * 加鎖
     * @param key    seckillId
     * @param value  當前時間+超時時間
     * @return
     */
    public boolean lock(String key, String value) {
        // 可以設置返回true
        Boolean isLock = redisTemplate.opsForValue().setIfAbsent(key, value);
        if (isLock) {
            return true;
        }
        String currentValue = redisTemplate.opsForValue().get(key);
        // 如果鎖已經過期
        if (!StringUtils.isEmpty(currentValue)
                && Long.valueOf(currentValue) < System.currentTimeMillis()) {
            // 獲取上一個鎖的時間,並設置新鎖的時間
            String oldValue = redisTemplate.opsForValue().getAndSet(key, value);
            if (!StringUtils.isEmpty(oldValue)
                    && oldValue.equals(currentValue)) {
                log.info("鎖過期並返回true");
                return true;
            }
        }
        return false;
    }

    /**
     * 解鎖
     * @param key
     * @return
     */
    public void unlock(String key, String value) {
        try {
            String currentValue = redisTemplate.opsForValue().get(key);
            if (!StringUtils.isEmpty(currentValue)
                    && currentValue.equals(value)) {
                redisTemplate.opsForValue().getOperations().delete(key);
            }
        } catch (Exception e) {
            log.error("redis分布式鎖,解鎖異常, {}",e.getMessage());
        }

    }


}

在業務代碼中使用redis分布式鎖

只有當線程拿到鎖的時候才可執行try中的業務代碼,並且在finally中我們對鎖進行釋放

        long currentTimeMills = System.currentTimeMillis();
        String redisLockValue = String.valueOf(currentTimeMills + RedisConstants.LOCK_EXPIRE_TIME);
        final boolean lock = redisLock.lock(String.valueOf(seckillId), redisLockValue);
        if (!lock) {
            throw new RuntimeException("人數過多,請稍后再試");
        }
        try {
            SeckillExecution execution = secPressureTestService.executeSeckill(seckillId);
            return Result.success(execution);
        } catch (Exception e) {
            log.error("【執行秒殺錯誤】,message={}", e.getMessage());
            return Result.error(e.getMessage());
        } finally {
            redisLock.unlock(String.valueOf(seckillId), redisLockValue);
        }

壓力測試

初始數據

image

image

使用apache ab工具壓測

ab -n 100 -c 4 127.0.0.1/kill/1000/execution

image

執行結果

image

image

結果分析

   在加上鎖之后所扣庫存與秒殺記錄的數量保持了一致,說明我們的鎖是生效的,你可以不加鎖的情況下進行壓測,看看結果就能看出差異。

可能會遇到的問題

  如果你在加上了redis分布式鎖之后壓測結果有問題(所扣庫存與秒殺記錄不一致),那么有可能是鎖和mysql事務的提交順序的問題導致的,解決方法請參考文章java中鎖與@Transactional同時使用導致鎖失效的問題

總結

  redis分布式鎖在springboot中的使用就介紹到這里,從一個案例入手進行了介紹。雖然我們還有其他的方式可以達到相同的效果,比如說使用同步鎖synchronized關鍵字,但是經過測試后發現synchronized鎖住整個方法的時候性能極差,並且synchronized在分布式系統中的表現不如redis分布式鎖。redis分布式鎖的強大之處在於分布式,因為線程是否能拿到鎖取決於redis的key和value, 這個key和value對於分布式系統是可以共享的,所以redis分布式鎖在分布式系統中極為方便和強大。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM