使用Redis 對于分布式服務進行加鎖, 防止一個服務多個部署實例,對資源搶占發(fā)生沖突。
在單體應用時,對于并發(fā)場景,讀取公共資源如扣庫存,買車票等,使用簡單的加鎖即可實現(xiàn),Java本身提供了很多并發(fā)處理的API,如Synchronized。
但是對于分布式服務來說, 一個服務可能被部署在多臺機器中,形成多個實例,之前的單進程多線程變成了多進程多線程
Java提供的API就不足以解決此類問題
單體應用
單個鎖實現(xiàn)單進程多線程并發(fā)訪問問題
@GetMapping("/getNumber")
public Integer getNumber(){
Integer redPacketNumber;
synchronized (this){
log.info("《idea redPacket》 請求紅包數(shù)量");
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("扣除失敗, 數(shù)量為{}", redPacketNumber);
}
}
return redPacketNumber;
}
分布式應用
一個服務多個實例
如果還用synchronized關鍵字 因為在不同服務, synchronized只能鎖住單個實例的代碼, 對于其他服務的代碼不起作用。
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值進行存儲,防止 鎖的持續(xù)時間小于 業(yè)務執(zhí)行時間, 導致鎖自動解鎖,使鎖失效,下一個請求就會直接進入。所以使用隨機Value 使得這個鎖唯一,只能自己解開
(實踐中出了問題, 鎖解開的沒訪問的快, 還沒解開,請求都發(fā)送完了)
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》 請求紅包數(shù)量");
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("扣除失敗, 數(shù)量為{}", redPacketNumber);
}
} finally{
//釋放鎖(為當前鎖)
if(stringRedisTemplate.opsForValue().get(redPacket).equals(vauleId)){
stringRedisTemplate.delete(redPacket);
}
}
return redPacketNumber;
}
Redisson客戶端
高性能的異步,無鎖的redis客戶端 成熟的分布式鎖解決方案, 可以減少開發(fā)人員的工作量,且性能優(yōu)異。 這個方法可以保證較高的可用性 推薦使用
Redisson 架設在 redis 基礎上的 Java 駐內(nèi)存數(shù)據(jù)網(wǎng)格(In-Memory Data Grid),基于NIO的 Netty 框架上,利用了 redis 鍵值數(shù)據(jù)庫。功能非常強大,解決了很多分布式架構中的問題。
Github的wiki地址:github.com/redisson/re…
- maven依賴
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.13.2</version>
</dependency>
- 注入實例
@Bean
public Redisson redisson(){
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379").setPassword("****").setDatabase(0);
return (Redisson) Redisson.create(config);
}
- 使用鎖
@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》 請求紅包數(shù)量");
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("扣除失敗, 數(shù)量為{}", redPacketNumber);
}
} finally{
redissonLock.unlock();
}
return redPacketNumber;
}