前言
分布式鎖,其實原理是就是多台機器,去爭搶一個資源,誰爭搶成功,那么誰就持有了這把鎖,然后去執行后續的業務邏輯,執行完畢后,把鎖釋放掉。
可以通過多種途徑實現分布式鎖,例如利用數據庫(mysql等),插入一條記錄(唯一索引),誰插入成功,誰就持有鎖;還可通過zookeeper來實現分布式鎖,誰創建節點成功,誰就持有鎖。本文介紹通過redis來實現分布式鎖。
本文使用springboot提供的RedisTemplate來操作redis,可以參考我之前的文章【快學springboot】13.操作redis之String數據結構,這里對使用RedisTemplate來操作redis做了介紹。當然也可以直接使用jedis來操作redis,大家可以參考下jedis的文檔,使用上都是大同小異的。
實現分布式鎖的步驟
第一步:通過redis的setnx方式(不存在則設置),往redis上設置一個帶有過期時間的key,如果設置成功,則獲得了分布式鎖。這里設置過期時間,是防止在釋放鎖的時候出現異常導致鎖釋放不掉。
第二步:執行完業務操作之后,刪除該鎖。
實現
新建一個DistributedLock.class,注入StringRedisTemplate。
@Component
public class DistributedLock {
@Autowired
private StringRedisTemplate redisTemplate;
}
獲得鎖
/**
* 獲得鎖
*/
public boolean getLock(String lockId, long millisecond) {
Boolean success = redisTemplate.opsForValue().setIfAbsent(lockId, "lock",
millisecond, TimeUnit.MILLISECONDS);
return success != null && success;
}
setIfAbsent方法,就是當鍵不存在的時候,設置,並且該方法可以設置鍵的過期時間。該方法對應到redis的原生命令就是:
SET lockId content PX millisecond NX
至於設置多少的過期時間合適,這個是沒有定論的,需要根據真是的業務場景來衡量。
釋放鎖
當處理完業務邏輯后,需要手動的把鎖釋放掉。
public void releaseLock(String lockId) {
redisTemplate.delete(lockId);
}
釋放鎖的操作比較簡單,直接刪除之前設置的鍵即可。其實,基於redis實現分布式鎖的方式,在釋放鎖的時候,是存在釋放失敗的風險的(比如網路抖動什么的),這也是為什么在設置鎖的時候需要設置過期時間的原因,可以防止在出現異常的時候,鎖會自動的消失掉。同時,我們也可以增加幾次失敗之后的重試機制。
測試
新建一個BusinessTask.java,代碼如下:
@Component
public class BusinessTask {
private final static String LOCK_ID = "happyjava";
@Autowired
DistributedLock distributedLock;
@Scheduled(cron = "0/10 * * * * ? ")
public void doSomething() {
boolean lock = distributedLock.getLock(LOCK_ID, 10 * 1000);
if (lock) {
System.out.println("執行任務");
distributedLock.releaseLock(LOCK_ID);
} else {
System.out.println("沒有搶到鎖");
}
}
}
這里使用了springboot的Scheduled注解來實現定時任務,該cron表達式的意思是每10秒鍾,執行一次任務,然后我們啟動兩次該項目,觀察一段時間執行結果:
第一個springboot任務:
第二個springboot任務:
兩個任務在交替的執行任務,證明了同一時刻只有一個應用持有了鎖。
總結
本文主要介紹了如何使用Java代碼(springboot的restTemplate)實現Redis分布式鎖,對於加鎖和解鎖也分別給出了示例代碼。其實我們還可以嘗試使用Redisson實現分布式鎖,這是Redis官方提供的Java組件,這個后續再介紹吧。