實現方式 功能要求 實現難度 學習成本 運維成本
mysql 的方案借助表鎖/行鎖實現 滿足基本要求 不難 熟悉 小量ok、大量影響現有業務、1主多從架構,不方便擴容
通過 zk 建立資料節點的方式實現 滿足要求 熟悉 zk api 即可 需要學習 重,需要堆機器,有跨機房請求
redis 使用 setnxex 基本要求 不難 熟悉 擴容方便、現有服務
mysql 單主架構,寫都會到 master,有瓶頸。zk 的方式需要自己搭建、運維,而且需要堆機器,利用率不高。最終採用了 redis 來實現,流量/儲存都可以擴容,運維也不需要自己。
mysql實現分布式鎖 主要是基於資料庫的排他鎖(也叫行級排他鎖), 採用樂觀鎖的方式去做。
我們可以通過乙個update語句是否成功來判斷執行緒搶占鎖是否成功,比如如下sql語句:
create table `t_schedule_cluster` (
`id` bigint(20) not null auto_increment comment '@cname:主鍵',
`execute` int(1) not null comment '@cname:執行狀態',
`version` int(11) not null comment '@cname:版本號 ',
`task_name` varchar(128) not null comment '@cname:任務名稱',
`execute_ip` varchar(32) default null comment '@cname:執行ip ',
`update_time` datetime default null comment '@cname:修改時間',
primary key (`id`),
key `index_series_id` (`execute`)
) engine=innodb auto_increment=8 default charset=utf8 comment='@cname:多機定時任務排程';
爭搶鎖的sql語句:update t_schedule_cluster set execute = 1 version = ?, execute_ip = ?, update_time = ? where task_name = ? and version = ?
實現原理入下圖:
但是資料庫的效能有限,如果在高併發的情況下會頻發的訪問資料庫,對資料庫會造成較大的壓力。
基於redis實現的分布式鎖其實很簡單,底層就是使用redis的setnx
指令來實現的加鎖,我們來看看官方對setnx的定義:
setnx key value
將 key 的值設為 value ,當且僅當 key 不存在。
若給定的 key 已經存在,則 setnx 不做任何動作。
setnx 是『set if not exists』(如果不存在,則 set)的簡寫。
返回值:
設定成功,返回 1 。
設定失敗,返回 0 。
redis> exists job # job 不存在
(integer) 0
redis> setnx job "programmer" # job 設定成功
(integer) 1
redis> setnx job "code-farmer" # 嘗試覆蓋 job ,失敗
(integer) 0
redis> get job # 沒有被覆蓋
"programmer"
以上內容來自於:
既然setnx這麼強大,那麼我們是不是可以高枕無憂直接使用了? 當然了,我們還要考慮一些極端場景。
2.1 死鎖問題
既然設定了value值,那麼我們肯定會想到過期時間,那麼就需要再使用setnx指令後繼續使用expire指令。但是這兩部操作必定不是原子性的,如果執行expire失敗怎麼辦?
其實redis官方也考慮到了這個問題,在redis2.8 之後,官方執行setnx 和 expire命令一起使用了。如下:
set lock_key lock_value nx px 30000
其中:1.lock_key:即鎖名稱,這個名稱應是公開的,在分布式環境中,對於某一確定的公共資源,所有爭用方(客戶端)都應該知道對應鎖的名字。對於 redis 而言,lock_name 就是 key-value 中的 key,具有唯一性。
2. lock_value:是由客戶端生成的乙個隨機字串,它要保證在足夠長的一段時間內在所有客戶端的所有獲取鎖的請求中都是唯一的,用於唯一標識鎖的持有者。
3. nx 表示只有當 lock_key(key) 不存在的時候才能 set 成功,從而保證只有乙個客戶端能獲得鎖,而其它客戶端在鎖被釋放之前都無法獲得鎖。
4. px 30000 表示這個鎖節點有乙個 30 秒的自動過期時間(目的是為了防止持有鎖的客戶端故障後,無法主動釋放鎖而導致死鎖,因此要求鎖的持有者必須在過期時間之內執行完相關操作並釋放鎖)。
具體操作如下圖:
2.2 鎖自動過期存在的隱患
例如我們有兩個執行緒a、b,此時執行緒a搶到了鎖,且設定自動過期時間為10s鐘,因為系統其他原因導致系統a發生阻塞。而此刻10s鐘後鎖自動過期,執行緒c獲取到了同乙個資源的鎖,執行緒a從阻塞中恢復,認為自己仍然持有鎖,繼續操作同一資源。這樣就使得加鎖的互斥性失效了。
解決方案:
我們在上面set lock_key lock_value 時講過,lock_value是乙個隨機生成的字串,在每次獲取鎖的時候都會重新生成。那麼我們在執行真正的業務邏輯(類似於和db進行互動的操作,同一時刻只能乙個執行緒操作的情況)時,判斷當前生成的隨機字串和lock_value是否一致,如果不一致則說明redis中的lock_value被修改過,也就說明此刻鎖已經被其他執行緒所占有。
具體操作流程如下圖:
主要使用的就是這兩種方案,在這裡只是做個簡單總結,其實還有其他一些可以實現分布式鎖,根據自己專案本身情況選擇最合適的。
另外 已經redis也有開源的框架可以很好地支援基於redis的分布式鎖,這裡推薦乙個:redission
分布式鎖實現原理
拜託,面試請不要再問我redis分布式鎖的實現原理!石杉的架構筆記 可重入鎖 為什麼不建議使用redis分布鎖 主從切換可能丟失鎖資訊 考慮一下這樣的場景 在分布式環境中,很多併發需要鎖來同步,當使用redis分布式鎖,通用的做法是使用redis的setnx key value px 這樣的命令,設...
分布式鎖 哨兵模式 分布式鎖實現原理
背景 記錄對分布式鎖的相關理解,不斷提公升自己 可重入鎖 為什麼不建議使用redis分布鎖 主從切換可能丟失鎖資訊 考慮一下這樣的場景 在分布式環境中,很多併發需要鎖來同步,當使用redis分布式鎖,通用的做法是使用redis的setnx key value px 這樣的命令,設定乙個字段,當設定成...
分布式鎖的實現原理
在介紹分布式鎖之前,我們先來簡單介紹下執行緒鎖 程序鎖。執行緒鎖 執行緒鎖,主要用來解決的問題是 保護臨界區域。使用方式 lock mutex unlock mutex 程序鎖 為了控制同一作業系統中多個程序訪問乙個共享資源。nginx中的accept鎖,就是一種程序鎖,它採用的實現方式是 共享記憶...