分佈式鎖的幾種實現原理

分佈式鎖主流有三種模式:

實現方式 功能要求 實現難度 學習成本 運維成本
MySQL 的方案藉助表鎖/行鎖實現 知足基本要求 不難 熟悉 小量OK、大量影響現有業務、1主多從架構,不方便擴容
經過 ZK 建立數據節點的方式實現 知足要求 熟悉 ZK API 便可 須要學習 重,須要堆機器,有跨機房請求
Redis 使用 setnxex 基本要求 不難 熟悉 擴容方便、現有服務html

MySQL 單主架構,寫都會到 master,有瓶頸。ZK 的方式須要本身搭建、運維,並且須要堆機器,利用率不高。最終採用了 Redis 來實現,流量/存儲均可以擴容,運維也不須要本身。java

1、基於Mysql實現分佈式鎖 (樂觀鎖)

Mysql實現分佈式鎖 主要是基於數據庫的排他鎖(也叫行級排他鎖), 採用樂觀鎖的方式去作。
咱們能夠經過一個update語句是否成功來判斷線程搶佔鎖是否成功,好比以下sql語句:mysql

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 = ?git

實現原理入下圖:
github

可是數據庫的性能有限,若是在高併發的狀況下會頻發的訪問數據庫,對數據庫會形成較大的壓力。redis

二,基於redis的分佈式鎖實現

基於Redis實現的分佈式鎖其實很簡單,底層就是使用redis的setnx指令來實現的加鎖,咱們來看看官方對setnx的定義:
SETNX key value
將 key 的值設爲 value ,當且僅當 key 不存在。
若給定的 key 已經存在,則 SETNX 不作任何動做。
SETNX 是『SET if Not eXists』(若是不存在,則 SET)的簡寫。
返回值:
設置成功,返回 1 。
設置失敗,返回 0 。sql

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"

以上內容來自於:http://redisdoc.com/string/setnx.html數據庫

既然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,具備惟一性。併發

  1. lock_value:是由客戶端生成的一個隨機字符串,它要保證在足夠長的一段時間內在全部客戶端的全部獲取鎖的請求中都是惟一的,用於惟一標識鎖的持有者。
  2. NX 表示只有當 lock_key(key) 不存在的時候才能 SET 成功,從而保證只有一個客戶端能得到鎖,而其它客戶端在鎖被釋放以前都沒法得到鎖。
  3. 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 https://github.com/redisson/redisson

PS:2019 繼續努力加油學習更多知識,讓本身在技術這條道路上越走越遠!

相關文章
相關標籤/搜索