redis分佈式鎖在springboot中的實現

<meta name="referrer" content="no-referrer" /> ## 理論知識html

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

本案例簡介

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

加鎖與解鎖核心代碼

<small>該段代碼能夠解決理論知識中的各類問題,包括鎖住的時候出現異常,死鎖等(經過比較時間戳的方式)</small>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分佈式鎖

<small>只有當線程拿到鎖的時候纔可執行try中的業務代碼,而且在finally中咱們對鎖進行釋放</small>spring

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/executionsql

image

執行結果

image

image

結果分析

   在加上鎖以後所扣庫存與秒殺記錄的數量保持了一致,說明咱們的鎖是生效的,你能夠不加鎖的狀況下進行壓測,看看結果就能看出差別。apache

可能會遇到的問題

  若是你在加上了redis分佈式鎖以後壓測結果有問題(所扣庫存與秒殺記錄不一致),那麼有多是鎖和mysql事務的提交順序的問題致使的,解決方法請參考文章java中鎖與@Transactional同時使用致使鎖失效的問題安全

總結

  redis分佈式鎖在springboot中的使用就介紹到這裏,從一個案例入手進行了介紹。雖然咱們還有其餘的方式能夠達到相同的效果,好比說使用同步鎖synchronized關鍵字,可是通過測試後發現synchronized鎖住整個方法的時候性能極差,而且synchronized在分佈式系統中的表現不如redis分佈式鎖。redis分佈式鎖的強大之處在於分佈式,由於線程是否能拿到鎖取決於redis的key和value, 這個key和value對於分佈式系統是能夠共享的,因此redis分佈式鎖在分佈式系統中極爲方便和強大。springboot

相關文章
相關標籤/搜索