What
使用Redis 對於分佈式服務進行加鎖, 防止一個服務多個部署實例,對資源搶佔發生衝突。
Why
在單體應用時,對於併發場景,讀取公共資源如扣庫存,買車票等,使用簡單的加鎖即可實現,Java本身提供了很多併發處理的API,如Synchronized。
但是對於分佈式服務來說, 一個服務可能被部署在多臺機器中,形成多個實例,之前的單進程多線程變成了多進程多線程 Java提供的API就不足以解決此類問題
單體應用(沒問題)
單個鎖實現單進程多線程併發訪問問題
@GetMapping("/getNumber")
public Integer getNumber(){
Integer redPacketNumber;
synchronized (this){
log.info("《idea redPacket》 請求紅包數量");
redPacketNumber = Integer.valueOf(stringRedisTemplate.opsForValue().get(RED_PACKET_NUMBER));
if(redPacketNumber > 0){
stringRedisTemplate.opsForValue().set(RED_PACKET_NUMBER, String.valueOf(--redPacketNumber));
log.info("扣除成功,剩餘庫存 {}", redPacketNumber);
} else {
log.warn("扣除失敗, 數量爲{}", redPacketNumber);
}
}
return redPacketNumber;
}
分佈式應用
一個服務多個實例
如果還用synchronized關鍵字 因爲在不同服務, synchronized只能鎖住單個實例的代碼, 對於其他服務的代碼不起作用。
How
Redis分佈式鎖
使用redis當作分佈式鎖
原理
setnx命令 是redis的一條原生命令 大意爲 set if not exists, 在指定的key不存在的情況下,爲key設置值 使用如下
redis 127.0.0.1:6379> SETNX KEY_NAME VALUE
複製代碼
setIfAbsent方法
使用 Redis的setIfAbsent方法可以達到setnx命令同樣的效果。 如果key對應的value爲空,則設置值, 返回true 否則返回false
注意:
-
使用過期時間 爲了保證萬一服務報錯, 鎖過一會自動解鎖
-
使用隨機Value值進行存儲 防止 鎖的持續時間小於 業務執行時間, 導致鎖自動解鎖,使鎖失效 下一個請求就會直接進入。 所以使用隨機Value 使得這個鎖唯一,只能自己解開 (實踐中出了問題, 鎖解開的沒訪問的快, 還沒解開,請求都發送完了)
使用
public Integer getNumberByDistributed(){
String redPacket = "redPacket";
Integer redPacketNumber;
String vauleId = UUID.randomUUID().toString();
try{
Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent(redPacket, vauleId, 5, TimeUnit.SECONDS);
//已存在,相當於已加鎖
if(!aBoolean){
return -1;
}
log.info("《idea redPacket》 請求紅包數量");
redPacketNumber = Integer.valueOf(stringRedisTemplate.opsForValue().get(RED_PACKET_NUMBER));
if(redPacketNumber > 0){
stringRedisTemplate.opsForValue().set(RED_PACKET_NUMBER, String.valueOf(--redPacketNumber));
log.info("扣除成功,剩餘庫存 {}", redPacketNumber);
} else {
log.warn("扣除失敗, 數量爲{}", redPacketNumber);
}
} finally{
//釋放鎖(爲當前鎖)
if(stringRedisTemplate.opsForValue().get(redPacket).equals(vauleId)){
stringRedisTemplate.delete(redPacket);
}
}
return redPacketNumber;
}
Redisson客戶端
高性能的異步,無鎖的redis客戶端 成熟的分佈式鎖解決方案, 可以減少開發人員的工作量,且性能優異。 這個方法可以保證較高的可用性 推薦使用
1. maven依賴
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.13.2</version>
</dependency>
2. 注入實例
@Bean
public Redisson redisson(){
Config config = new Config();
config.useSingleServer().setAddress("redis://yuanbaojian.xyz:6379").setPassword("****").setDatabase(0);
return (Redisson) Redisson.create(config);
}
3. 使用鎖
@Autowired
Redisson redisson;
public Integer getNumberByRedisson(){
String redPacket = "redPacket";
Integer redPacketNumber;
RLock redissonLock = redisson.getLock(redPacket);
String vauleId = UUID.randomUUID().toString();
try{
redissonLock.lock(30, TimeUnit.SECONDS);
log.info("{}進入redisson分佈式鎖的接口中",Thread.currentThread().getName());
log.info("《idea redPacket》 請求紅包數量");
redPacketNumber = Integer.valueOf(stringRedisTemplate.opsForValue().get(RED_PACKET_NUMBER));
if(redPacketNumber > 0){
stringRedisTemplate.opsForValue().set(RED_PACKET_NUMBER, String.valueOf(--redPacketNumber));
log.info("扣除成功,剩餘庫存 {}", redPacketNumber);
} else {
log.warn("扣除失敗, 數量爲{}", redPacketNumber);
}
} finally{
redissonLock.unlock();
}
return redPacketNumber;
}
作者:少安
鏈接:https://juejin.im/post/5f0414155188252e937be22b