摘要:用的方法做分布式鎖這個方案的背景主要是在和的方案上針對可能存在的死鎖問題,做了一些優化。下面是用代碼實現的分布式鎖,關于部分使用的是偽代碼,請根據自己的情況用連接對象替代其中的偽代碼。
在系統中,當存在多個進程和線程可以改變某個共享數據時,就容易出現并發問題導致共享數據的不一致性。即多個進程同時獲取到了對數據的操作權限并對數據進行了更新,很典型的場景就是在線銷售系統在售賣熱銷商品時遇到多個并發請求在同一時間提交訂單的情況則極有可能造成商品超賣的現象。只要訪問流量不錯的系統都有可能遭遇并發請求造成數據庫中數據重復寫入的情況。
針對程序塊被多個進程并發執行問題的解決方案是確保同一個時刻同一個程序塊只能有一個進程可執行,其他進程等待當前進程執行完成才能獲取程序塊的執行權對數據進行更新,以此類推將并發執行變為串行順序執行。為了讓獲取執行權的進程不被其他干擾,就需要設置一個所有進程都能讀取到的標記,當標記不存在時可以設置該標記,其余后續進程發現已經有標記了則等待擁有標記的進程結束執行程序塊取消標記后再去嘗試設置標記。這個標記可以理解為鎖,設置標記的過程就是我們通常說的加鎖。
用redis 的 setnx、expire 方法做分布式鎖setnx()
setnx 的含義就是 SET if Not Exists,其主要有兩個參數 setnx(key, value)。該方法是原子的,如果 key 不存在,則設置當前 key 成功,返回 1;如果當前 key 已經存在,則設置當前 key 失敗,返回 0。
expire()
expire 設置過期時間,要注意的是 setnx 命令不能設置 key 的超時時間,只能通過 expire() 來對 key 設置。
具體步驟
1、setnx(lockKey, 1) 如果返回 0,則說明占位失敗;如果返回 1,則說明占位成功
2、expire() 命令對 lockKey 設置超時時間,為的是避免死鎖問題。
3、執行完業務代碼后,可以通過 delete 命令刪除 key。
這個方案其實是可以解決日常工作中的需求的,但從技術方案的探討上來說,可能還有一些可以完善的地方。比如,如果在第一步 setnx 執行成功后,在 expire() 命令執行成功前,發生了宕機的現象,那么就依然會出現死鎖的問題,所以如果要對其進行完善的話,可以使用 redis 的 setnx()、get() 和 getset() 方法來實現分布式鎖。
用 redis 的 setnx()、get()、getset()方法做分布式鎖這個方案的背景主要是在 setnx() 和 expire() 的方案上針對可能存在的死鎖問題,做了一些優化。
getset()
這個命令主要有兩個參數 getset(key,newValue)。該方法是原子的,對 key 設置 newValue 這個值,并且返回 key 原來的舊值。假設 key 原來是不存在的,那么多次執行這個命令,會出現下邊的效果:
getset(key, "value1") 返回 null 此時 key 的值會被設置為 value1
getset(key, "value2") 返回 value1 此時 key 的值會被設置為 value2
依次類推!
使用步驟
setnx(lockKey, 當前時間+過期超時時間),如果返回 1,則獲取鎖成功;如果返回 0 則沒有獲取到鎖,轉到步驟 2。
get(lockKey) 獲取值,值是當前lockKey的過期時間用oldExpireTime代表 ,并將這個 oldExpireTime與當前的系統時間進行比較,如果早于當前系統時間,則認為這個鎖已經超時,可以允許別的請求重新獲取,轉向 步驟3,否則等待指定時間后返回步驟2重新開始判定。
計算 newExpireTime = 當前時間+過期超時時間,然后 getset(lockKey, newExpireTime) 會返回當前 lockKey 之前設置的舊值currentExpireTime。
判斷 currentExpireTime 與 oldExpireTime 是否相等,如果相等,說明當前進程getset 設置鎖成功,獲取到了鎖。如果不相等,說明這個鎖已經被別的進程獲取走了,那么當前請求可以根據具體需求邏輯直接返回失敗,或者返回步驟2繼續重試。
在獲取到鎖之后,當前進程可以開始自己的業務處理,當處理完畢后,比較當前理時間和對鎖設置的超時時間,如果小于鎖設置的超時時間,則直接執行 delete 釋放鎖;如果大于鎖設置的超時時間,鎖可能已由其他進程獲得,這時執行 delete釋放鎖的操作會導致把其他進程已獲得的鎖釋放掉。
下面是用PHP代碼實現的Redis分布式鎖,關于Redis部分使用的是偽代碼,請根據自己的情況用Redis連接對象替代其中的偽代碼。
/** * 獲取Redis分布式鎖 * * @param $lockKey * @return bool */ function getRedisDistributedLock(string $lockKey) : bool { $lockTimeout = 2000;// 鎖的超時時間2000毫秒 $now = intval(microtime(true) * 1000); $lockExpireTime = $now + $lockTimeout; $lockResult = Redis::setnx($lockKey, $lockExpireTime); if ($lockResult) { // 當前進程設置鎖成功 return true; } else { $oldLockExpireTime = Redis::get($lockKey); if ($now > $oldLockExpireTime && $oldLockExpireTime == Redis::getset($lockKey, $lockExpireTime)) { return true; } } return false; } /** * 串行執行程序 * * @param string $lockKey Key for lock * @param Closure $closure 獲得鎖后進程要執行的閉包 * @return mixed */ function serialProcessing(string $lockKey, Closure $closure) { if (getRedisDistributedLock($lockKey)) { $result = $closure(); $now = intval(microtime(true) * 1000); if ($now < Redis::get($lockKey)) { Redis::del($lockKey); } } else { // 延遲200毫秒再執行 usleep(200 * 1000); return serialProcessing($lockKey, $closure); } return $result; }
上面serialProcessing方法里當前進程設置鎖成功,獲取了代碼塊的執行權后就會執行閉包參數$closure里的代碼塊,通過傳遞閉包給方法,讓我們可以在項目任何需要確保程序串行執行的地方使用serialProcessing方法給程序加分布式鎖解決并發請求的問題。
上面代碼實現用面向過程的方式是為了能簡單明了的描述怎么設置分布式鎖,讀者可以針對自己的情況執行設計實現代碼。針對于大型系統使用集群Redis的情況,設置分布式鎖的步驟更復雜,有興趣的可以查看Redlock 算法和redissonredis分布式鎖組件。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/31005.html
摘要:解決冪等問題的三部曲,也是作者的思考框架。這是解決冪等問題的第二部曲列出并減少副作用的分析維度。所以在并發執行的維度,將并發重復執行變成串行重復執行是最好的冪等解決方案。 綱要 文章目的:本文旨在提煉一套分布式冪等問題的思考框架,而非解決某個具體的分布式冪等問題。在這個框架體系內,會有一些方案舉例說明。文章目標:希望讀者能通過這套思考框架設計出符合自己業務的完備的冪等解決方案。文章內容...
摘要:廢話不多說,首先分享一個業務場景搶購。下面就是分布式鎖的解決方法。首先要加入的依賴,該類只有兩個功能,加鎖和解鎖,解鎖比較簡單,就是刪除當前的鍵值對。這時繼續執行,由于所以該線程獲取到鎖。 廢話不多說,首先分享一個業務場景-搶購。一個典型的高并發問題,所需的最關鍵字段就是庫存,在高并發的情況下每次都去數據庫查詢顯然是不合適的,因此把庫存信息存入Redis中,利用redis的鎖機制來控制...
摘要:原文鏈接解決了什么問題使用模型來克服傳統面向對象編程模型的局限性,并應對高并發分布式系統所帶來的挑戰。在某些情況,這個問題可能會變得更糟糕,工作線程發生了錯誤但是其自身卻無法恢復。 這段時間由于忙畢業前前后后的事情,拖更了很久,表示非常抱歉,回歸后的第一篇文章主要是看到了Akka最新文檔中寫的What problems does the actor model solve?,閱讀完后覺...
摘要:比如需要用多線程或分布式集群統計一堆用戶的相關統計值,由于用戶的統計值是共享數據,因此需要保證線程安全。如果類是無狀態的,那它永遠是線程安全的。參考探索并發編程二寫線程安全的代碼 線程安全類 保證類線程安全的措施: 不共享線程間的變量; 設置屬性變量為不可變變量; 每個共享的可變變量都使用一個確定的鎖保護; 保證線程安全的思路: 1. 通過架構設計 通過上層的架構設計和業務分析來避...
摘要:為程序員金三銀四精心挑選的余道面試題與答案,歡迎大家向我推薦你在面試過程中遇到的問題我會把大家推薦的問題添加到下面的常用面試題清單中供大家參考。 為Java程序員金三銀四精心挑選的300余道Java面試題與答案,歡迎大家向我推薦你在面試過程中遇到的問題,我會把大家推薦的問題添加到下面的常用面試題清單中供大家參考。 前兩天寫的以下博客,大家比較認可,熱度不錯,希望可以幫到準備或者正在參加...
閱讀 2351·2021-11-25 09:43
閱讀 2864·2021-11-24 09:39
閱讀 2926·2019-08-30 11:10
閱讀 1130·2019-08-29 16:34
閱讀 595·2019-08-29 13:25
閱讀 3358·2019-08-29 11:21
閱讀 2861·2019-08-26 11:39
閱讀 2394·2019-08-26 11:34