好吧,每當我們在本地系統中工作時,一切都像黃油一樣工作。這就是為什麼我們稱之為“沒有比 127.0.0.1 更好的地方了”,但醒醒吧,面對現實
嗯,在生產中事情並不總是按預期進行。大多數情況下,當您執行應用程式的多個實例時。
🚀 正如您所看到的,如果我們的應用程式的多個實例正在執行,並且假設我們的客戶端發出請求,將用戶標記為我們資料庫中的付費用戶。
客戶端會請求我們的伺服器
請求將到達我們的負載平衡器
其中一個實例將收到請求並向我們的資料庫發出寫入查詢
看起來好像還可以吧?到現在為止都沒問題吧。
嗯,是的,到目前為止還沒有問題。但是如果我們想寫這樣的業務邏輯怎麼辦:-
從資料庫中獲取用戶
檢查用戶是否為免費用戶或已付費用戶
如果免費,則將其標記為付費並保存在資料庫中
如果已付款,請在回覆中發送「已付款」。
⚡️ 眾所周知(假設我們在這裡使用 MySQL)MySQL 資料庫符合 ACID,這意味著任何查詢都將是原子的和隔離的。這意味著 MySQL 查詢將以原子方式執行,要么通過,要么失敗。但它不會在中間退出。
🤔 但這裡有個問題。想想,想想…
第 1 步:我們正在獲取使用者(原子事務)
步驟2:在程式碼中執行一些業務邏輯
步驟 3:如果使用者未付款則更新 MySQL 記錄(原子事務)
如果在第 2 步,又一個取消付款的請求,然後該查詢首先執行並將用戶標記為免費,然後執行第 3 步並將用戶標記為已付費,會發生什麼情況。
🕺🏻萬歲,用戶甚至無需付費即可存取我們的產品。
✅ 救世主Locks來了
🔐 鎖是一種結構,一次只允許一個執行緒進入臨界區(不應該被多個工作執行緒存取的程式碼區塊)
因此,我們將在操作完成之前獲取鎖定並在操作完成後釋放:-
步驟0:try-acquire()鎖
第 1 步:如果已獲取,我們將獲取使用者(原子事務)
步驟2:在程式碼中執行一些業務邏輯
步驟 3:如果使用者未付款則更新 MySQL 記錄(原子事務)
第四步:release()鎖
現在,問題來了,如果我們使用一些記憶體鎖資料結構或任何基於記憶體的鎖,它將適合我們的應用程式的一個實例。執行相同程式碼並在資料庫中更新的其他實例怎麼樣?
那麼這裡就涉及到分散式鎖的概念了
在這裡,鎖充當一種集中式服務,如果我們的服務的一個實例獲取了鎖,那麼其他實例就無法使用同一個金鑰。
支付服務中可能有什麼密鑰?
🔓 對於進行付款的用戶,金鑰可以是 = "PAYMENT_" + user_id + amount 的組合
這對於每個用戶來說都是唯一的。並且當用戶付款或取消付款時,該密鑰將保持不變。因此,當一個操作發生時,其他操作無法繼續,因為這兩個操作將嘗試取得相同的金鑰。
💭Key到底是什麼,取得鎖,釋放鎖。最重要的是,redis 是如何使用的?
但單一 redis 實例存在以下幾個問題:-
單一實例可能會失敗且取得的鎖定可能無法釋放
如果使用兩個實例(主副本),則一個客戶端將取得一個實例上的鎖
主伺服器必須與副本進行相同的通訊才能同步。此通信本身是異步通信
🚀 因此,如果在主伺服器上取得了鎖,並且在與副本通訊時,如果主伺服器在與副本同步之前發生故障。副本將成為主伺服器,其中相同金鑰上的鎖將可用於取得先前在主伺服器上取得的鎖定。
即使有兩個實例(主副本),我們服務的兩個實例也將能夠取得 Redis 上的鎖。
取得鎖:- 我們將嘗試在具有鎖定過期時間的多個 Redis 實例上取得鎖
鎖定驗證:如果主要 Redis 實例為用戶端取得了鎖,則將被視為已取得鎖定
釋放鎖:-釋放鎖時,所有實例都釋放鎖
是的,就是這樣。
❤️感謝您的閱讀,並訂閱我們的電子報以獲取更多此類文章:- https://www.serversidedigest.com/
欲了解更多資訊:-
Java 中的 Jedis:- https://redis.io/docs/latest/develop/connect/clients/java/jedis/
Golang 中的 Redis 用戶端:- https://github.com/redis/go-redis
原文出處:https://dev.to/ssd/how-to-implement-a-distributed-lock-using-redis-he