<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()); } } }
<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); }
ab -n 100 -c 4 127.0.0.1/kill/1000/executionsql
在加上鎖以後所扣庫存與秒殺記錄的數量保持了一致,說明咱們的鎖是生效的,你能夠不加鎖的狀況下進行壓測,看看結果就能看出差別。apache
若是你在加上了redis分佈式鎖以後壓測結果有問題(所扣庫存與秒殺記錄不一致),那麼有多是鎖和mysql事務的提交順序的問題致使的,解決方法請參考文章java中鎖與@Transactional同時使用致使鎖失效的問題安全
redis分佈式鎖在springboot中的使用就介紹到這裏,從一個案例入手進行了介紹。雖然咱們還有其餘的方式能夠達到相同的效果,好比說使用同步鎖synchronized關鍵字,可是通過測試後發現synchronized鎖住整個方法的時候性能極差,而且synchronized在分佈式系統中的表現不如redis分佈式鎖。redis分佈式鎖的強大之處在於分佈式,由於線程是否能拿到鎖取決於redis的key和value, 這個key和value對於分佈式系統是能夠共享的,因此redis分佈式鎖在分佈式系統中極爲方便和強大。springboot