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);
}
壓力測試
初始數據
使用apache ab工具壓測
ab -n 100 -c 4 127.0.0.1/kill/1000/execution
執行結果
結果分析
在加上鎖之后所扣庫存與秒殺記錄的數量保持了一致,說明我們的鎖是生效的,你可以不加鎖的情況下進行壓測,看看結果就能看出差異。
可能會遇到的問題
如果你在加上了redis分布式鎖之后壓測結果有問題(所扣庫存與秒殺記錄不一致),那么有可能是鎖和mysql事務的提交順序的問題導致的,解決方法請參考文章java中鎖與@Transactional同時使用導致鎖失效的問題
總結
redis分布式鎖在springboot中的使用就介紹到這里,從一個案例入手進行了介紹。雖然我們還有其他的方式可以達到相同的效果,比如說使用同步鎖synchronized關鍵字,但是經過測試后發現synchronized鎖住整個方法的時候性能極差,並且synchronized在分布式系統中的表現不如redis分布式鎖。redis分布式鎖的強大之處在於分布式,因為線程是否能拿到鎖取決於redis的key和value, 這個key和value對於分布式系統是可以共享的,所以redis分布式鎖在分布式系統中極為方便和強大。