# 使用Redis來進行分散式鎖 [TOC] 透過上一篇[好像要鎖一下ㄟ(悲觀鎖、樂觀鎖)](https://hackmd.io/@UTRxSLfpRa6ds1oeI2U7Lw/H1SFq3CFs)中提到了透過DB的方式來解決賣超的方法,今天就來記錄一下如果不用DB那要怎麼解決哩? ## 悲觀鎖 - 上次使用的是悲觀鎖,底下的原理是透過資料庫行鎖來達成,這樣的話會有以下這些困擾的問題: 1. 效能問題,在資料庫層面會一直阻塞,直到commit 2. 注意設定事務的隔離級別是`Read Commit`,否則併發情況下,另外的事物無法看到提交的資料,導致賣超問題 3. 容易產生deadlock,如果多個加鎖控制不好,就會容易發生 ## 簡單介紹分散式鎖 - 我們可以透過Redis、ZooKeeper來實現 - 特點 1. `互斥性`。在任意時刻,只有一個人持有鎖 2. `鎖超時`。即使一個人持有鎖的期間發生異常,而沒有主動釋放鎖,也需要保證其他人能正常獲取鎖 3. 獲得鎖和解鎖必須是`同一個人`,不能把別人的鎖給釋放了 - 這樣的話我們就可以不需要使用悲觀鎖,來解決賣超、一致性等問題 ## 分散式鎖的觸發流程 ```mermaid graph TD; A拿到鎖-->A做某些事情; A做某些事情-->A解鎖; A解鎖-->換下一個人拿鎖; 換下一個人拿鎖-->A拿到鎖; ``` - 從圖中我們可以知道分散式鎖的`獲得鎖和解鎖必須是同一個人`,一次只給一個人拿到鎖,同一時間不會有其他人拿到相同的鎖 ## 實作 - 透過Redis來實作分散式鎖,同時有10個人搶同樣的東西 ### 拿到鎖 - `acquire_timeout`:每個人能獲得鎖的上限時間為`30s` - `lock_timeout`:鎖的過期時間為`10s` - 其中`acquire_timeout`必須==大於== `lock_timeout`,因為如果==小於==的話就不符合上面提到的`鎖超時`了 ```python= def acquire_lock(lock_name, p, acquire_timeout=30, lock_timeout=10): """ 獲取鎖 :param lock_name: :param p: :param acquire_timeout: 每個人能獲得鎖的上限時間 :param lock_timeout: 鎖的過期時間 :return: """ identifier = str(uuid.uuid4()) # 設定當前用戶在特定時間內一定要拿到鎖,若超過acquire_timeout時間都還沒拿到鎖,則return False end = time.time() + acquire_timeout while time.time() < end: # nx: 如果不存在才創建、ex: 過期時間 # 設置鎖的過期時間,防止deadlock,並返回uuid當作唯一值 if r.set(lock_name, identifier, ex=lock_timeout, nx=True): print(f"進程{p} 獲得鎖") return identifier return False ``` ### 解鎖 - 當下的redis儲存的uuid必須跟當下的user是同樣的才能正常解鎖 => `同一人才能解鎖` ```python= def release_lock(lock_name, identifier): unlock_script = """ if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end """ unlock = r.register_script(unlock_script) result = unlock(keys=[lock_name], args=[identifier]) if result: return True else: return False ``` ### 執行結果 - 我們可以看到一次只會有一個進程獲得鎖和解鎖,不會有A進程獲得鎖、B進行解鎖的情況 ```python= if __name__ == '__main__': for i in range(10): Process(target=exec_test, args=("lock:test", i)).start() # 進程2 獲得鎖 # 876146d3-0502-43a5-a93c-d136e1281623 # 進程2 成功解鎖 # 進程4 獲得鎖 # b3682b7c-07e6-41b9-af2a-50f189ed77c2 # 進程4 成功解鎖 # 進程3 獲得鎖 # e292c337-4e51-48d3-bc4c-a98445628942 # 進程3 成功解鎖 # 進程5 獲得鎖 # 6a820501-04a8-4263-815a-34633722fc5a # 進程5 成功解鎖 # 進程6 獲得鎖 # 8a0fdc15-35d9-45c5-9421-c2b988b736d9 # 進程6 成功解鎖 # 進程0 獲得鎖 # 4abb0888-0624-401f-8c92-971792246f0b # 進程0 成功解鎖 # 進程7 獲得鎖 # 5231363d-6ee4-47a7-9546-deecbe51a8a3 # 進程7 成功解鎖 # 進程8 獲得鎖 # e177da52-67fb-4a6c-8fe4-271f53a126f2 # 進程8 成功解鎖 # 進程1 獲得鎖 # 25279d1e-6b09-46c9-b8da-6e2f37143a1b # 進程1 成功解鎖 # 進程9 獲得鎖 # 133dca70-3bda-4942-ac78-ea3ee9f39bbd # 進程9 成功解鎖 ``` ### 結論 寫法上應該還有其他種,原理是相同的。另外`Zookeeper`也可以實作出分散式鎖,看別人的分享在實作上是比Redis更簡易的,有興趣的可以自己玩玩看~ ## 參考資料 1. [隔離級別介紹](https://openhome.cc/Gossip/HibernateGossip/IsolationLevel.html) 2. https://www.gushiciku.cn/pl/ggMl/zh-tw 3. https://www.cnblogs.com/tracydzf/p/15629987.html 4. [redis、zookeeper面試介紹](https://github.com/doocs/advanced-java/blob/main/docs/distributed-system/distributed-lock-redis-vs-zookeeper.md)
×
Sign in
Email
Password
Forgot password
or
Sign in via Google
Sign in via Facebook
Sign in via X(Twitter)
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
Continue with a different method
New to HackMD?
Sign up
By signing in, you agree to our
terms of service
.