要分析分布式鎖這個問題,我們根據黃金圈法則來分析。
黃金圈法則是由美國營銷顧問西蒙·斯涅克(Simon Sinek)提出的一種思維模型,用于幫助人們更好地理解和傳達信息。黃金圈法則由三個圈組成,分別是:
使用3w分析問題思路來分析分布式鎖,可以從以下幾個方面進行分析:
分布式鎖是控制分布式系統之間同步訪問共享資源的機制。在分布式系統中,常常需要協調他們的動作。如果不同的系統或是同一個系統的不同主機之間共享了一個或一組資源,那么訪問這些資源的時候,往往需要互斥來防止彼此干擾來保證一致性,在這種情況下,便需要使用到分布式鎖。
回答時:先說一下概念。分布式鎖是用于在分布式系統中控制對共享資源的訪問,以避免數據競爭和并發問題。
分布式鎖的實現方法有很多,常見的有以下幾種:
回答時:說一下分布式鎖以上的實現方式。
一些需要分布式鎖的場景:
回答時:說一下分布式鎖的應用場景。
Redisson 是一個基于 Redis 的 Java 分布式框架。Redisson 提供了豐富的功能,包括分布式鎖、分布式集合、分布式隊列等。
以下是使用 Redisson 實現分布式鎖的示例:
@Autowired private RedissonClient redissonClient; public String lock() { // 獲取鎖 RLock lock = redissonClient.getLock("lock"); boolean acquired = lock.tryLock(10,-1,TimeUnit.SECONDS); if (acquired) { // 獲取鎖成功,執行業務邏輯 return "獲取鎖成功,執行業務邏輯..."; } else { // 獲取鎖失敗,重試 return "獲取鎖失敗,重試..."; } } public String unlock() { // 釋放鎖 RLock lock = redissonClient.getLock("lock"); lock.unlock(); return "釋放鎖成功..."; }
另外,redisson支持鎖續期。即在鎖鍵值過期后任務還沒執行完成,此時需要把鎖鍵值的時間自動延長。
Redisson提供了的續期機制,只要客戶端加鎖成功,就會啟動一個Watch Dog。可以看到源代碼的實現leaseTime不設置為-1時開啟監聽。如果任務沒完成就調用scheduleExpirationRenewal續期方法。
tryLock() 方法用于嘗試獲取分布式鎖,該方法有三個參數:
waitTime 參數表示客戶端最多等待多長時間來獲取鎖。如果在 waitTime 時間內沒有獲取到鎖,則會返回 false。
leaseTime 參數表示鎖的過期時間。如果鎖在 leaseTime 時間內沒有被釋放,則會自動釋放。如果 leaseTime 設置為 -1,則表示鎖的過期時間由 renew() 方法來控制。這樣,在業務邏輯執行過程中,可以定期調用 lock.renew() 方法來續期鎖的過期時間。
tryLock() 方法的返回值是一個 Boolean 值,表示是否成功獲取到鎖。如果成功獲取到鎖,則返回 true。否則,返回 false。
在 SpringBoot 2.7 中,可以通過spring-boot-starter-data-redis 默認依賴是Lettuce。那么Lettuce是如何實現分布式鎖呢
以下是 SpringBoot+Lettuce 實現分布式鎖的完整代碼:
@Componentpublic class RedisLock { private static final String LOCK_SCRIPT = "if redis.call('exists', KEYS[1]) == 0 then/n" + " redis.call('set', KEYS[1], ARGV[1], 'NX', 'PX', ARGV[2]);/n" + " return 1/n" + "else/n" + " return 0/n" + "end"; private final RedisTemplate<String, String> redisTemplate; public RedisLock(RedisTemplate<String, String> redisTemplate) { this.redisTemplate = redisTemplate; } //獲得鎖 public boolean acquireLock(String key, long timeout) { String uuid = UUID.randomUUID().toString(); Object result = redisTemplate.execute(new DefaultRedisScript(LOCK_SCRIPT, Long.class), Arrays.asList(key), uuid, timeout); return result != null && (long) result == 1; } //釋放鎖 public void releaseLock(String key, String uuid) { redisTemplate.delete(key); }}
@Servicepublic class TestServcieImpl implements TestServcie{ @Autowired private StringRedisTemplate stringRedisTemplate; // 加鎖腳本 private static final String LOCK_SCRIPT = "if redis.call ('setnx', KEYS[1], ARGV[1]) == 1 then return redis.call ('expire', KEYS[1], ARGV[2]) else return 0 end"; // 解鎖腳本 private static final String UNLOCK_SCRIPT = "if redis.call ('get', KEYS[1]) == ARGV[1] then return redis.call ('del', KEYS[1]) else return 0 end"; // 加鎖方法 public boolean lock(String key, String value, Long expire) { RedisScript<Long> redisScript = new DefaultRedisScript<>(LOCK_SCRIPT, Long.class); Long result = stringRedisTemplate.execute(redisScript, Collections.singletonList(key), value, String.valueOf(expire)); return result.equals(Long.valueOf(1)); } // 解鎖方法 public boolean unlock(String key, String value) { RedisScript<Long> redisScript = new DefaultRedisScript<>(UNLOCK_SCRIPT, Long.class); Long result = stringRedisTemplate.execute(redisScript, Collections.singletonList(key), value); return result.equals(Long.valueOf(1)); }}
在上述 demo 中,我們使用 RedisLock 類來封裝分布式鎖的相關操作。acquireLock() 方法用于獲取分布式鎖,releaseLock() 方法用于釋放分布式鎖。
在 ServcieImpl 類中,我們使用 RedisLock 類來獲取和釋放分布式鎖。lock() 方法用于獲取鎖,unlock() 方法用于釋放鎖。不像Redisson封裝好了相應的方法,Lettuuce如果要實現鎖續期就需要自己寫監聽器及相應的lua腳本。
可以看到不論是Redisson還是Lettuce實現分布式鎖都使用的Lua腳本,那我們先來了解一下什么是Lua腳本語言。
Lua 是一個小巧的腳本語言,由巴西里約熱內盧天主教大學(Pontifical Catholic University of Rio de Janeiro)里的一個研究小組于 1993 年開發的。Lua 使用標準 C 語言編寫并以源代碼形式開放,幾乎在所有操作系統和平臺上都能編譯運行。Lua 腳本可以調用 C/C++ 的函數,也可以被 C/C++ 代碼調用,所以 Lua 在應用程序中可以被廣泛應用。
Lua 的特點如下:
Lua 在游戲開發、Web 開發、嵌入式系統等領域都有廣泛的應用。
以下是 Lua 的一些典型應用:
Lua 是一款非常實用的腳本語言,在眾多領域都有廣泛的應用。
使用 Lua 腳本可以將判斷和刪除鎖的操作合并為一個原子操作,避免了這些問題。Lua 腳本在 Redis 服務器端執行,不會受到網絡延遲或者客戶端故障的影響,也不會被其他命令打斷,因此可以保證操作的原子性。
因此,Redis 命令沒有原子性是指多個命令組合起來執行時沒有原子性。
以下是使用 Lua 腳本實現分布式鎖的示例:
-- 獲取鎖function acquire_lock(key, uuid, timeout) local value = redis.call("GET", key) if value == nil then redis.call("SET", key, uuid, "NX", "PX", timeout) return 1 else return 0 endend-- 釋放鎖function release_lock(key, uuid) redis.call("DEL", key)end
上述腳本實現了簡單的 SETNX 和 DEL 操作,可以保證同一時刻只有一個客戶端可以獲取到鎖。
在實際使用中,可以根據具體的業務場景來調整 Lua 腳本的實現。
本文鏈接:http://www.tebozhan.com/showinfo-26-19895-0.html面試官必問的分布式鎖面試題,你答得上來嗎?
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com
上一篇: 給正在使用Lombok的朋友一些建議
下一篇: 你可能聽說過雪花算法