一文詳解如何用MySQL/Redis/ZooKeeper實現分散式鎖

来源:https://www.cnblogs.com/yidengjiagou/archive/2022/06/19/16391365.html
-Advertisement-
Play Games

一個挺著啤酒肚,身穿格子衫,髮際線嚴重後移的中年男子,手拿著保溫杯,胳膊夾著MacBook向你走來,看樣子是架構師級別。 面試開始, 直入正題。 面試官: 你有沒有參與過秒殺系統的設計? 我: 沒有,我平時都是開發後臺管理系統、OA辦公系統、內部管理系統,從來沒有開發過秒殺系統。 面試官: 嗯... ...


一個挺著啤酒肚,身穿格子衫,髮際線嚴重後移的中年男子,手拿著保溫杯,胳膊夾著MacBook向你走來,看樣子是架構師級別。

面試開始, 直入正題。

面試官: 你有沒有參與過秒殺系統的設計?

我: 沒有,我平時都是開發後臺管理系統、OA辦公系統、內部管理系統,從來沒有開發過秒殺系統。

面試官: 嗯...,小伙子很實誠。今天就先到這裡吧,後面有消息會主動聯繫你。

後面還可能有消息嗎?你們啥時候主動聯繫過我?
實話實說的被拒,八股文背的溜反而被錄取。
好吧,等我看看一燈怎麼總結的秒殺系統的八股文。

我: 參與過秒殺系統,並獨立負責過秒殺系統的架構設計(【狗頭】是的,都是我設計的)。

面試官: 這樣才對,這樣我才能接著往下問。你在設計秒殺系統的時候,怎麼防止商品超賣?比如活動中只有一臺iPhone,最終賣出100台,肯定不行,平臺要虧錢。

我: 肯定要加鎖,不過由於秒殺系統請求量較大,一般使用分散式集群。而Java自帶Synchronized、ReentrantLock鎖只能用在單機系統中,這時候就需要用到分散式鎖。

面試官: 你提到分散式鎖,分散式鎖都有哪些作用?

八股文這就開始了。

我:我覺得分散式鎖主要有兩個作用:

保證數據的正確性:
比如:秒殺的時候防止商品超賣,表單重覆提交,介面冪等性。

避免數據重覆處理:
比如:調度任務在多台機器重覆執行,緩存過期所有請求都去載入資料庫。

總結八股文,還得是一燈。

面試官: 小伙子總結的挺全,你知道設計一個分散式鎖,要具有哪些特性?

我: 我覺得分散式鎖要具有以下這些特性:

互斥:同一時刻只能有一個線程獲得鎖。
可重入:當一個線程獲取鎖後,還可以再次獲取這個鎖,避免死鎖發生。
高可用:當小部分節點掛掉後,仍然能夠對外提供服務。
高性能:要做到高併發、低延遲。
支持阻塞和非阻塞:Synchronized是阻塞的,ReentrantLock.tryLock()就是非阻塞的
支持公平鎖和非公平鎖:Synchronized是非公平鎖,ReentrantLock(boolean fair)可以創建公平鎖

面試官: 小伙子,有點東西。你是怎麼設計一個分散式鎖?

我: 有幾種常用的工具都可以實現分散式鎖。
比如:關係型資料庫(例如:MySQL)、分散式資料庫(例如:Redis)、分散式協調服務框架(例如:zookeeper)

使用MySQL實現分散式鎖比較簡單,建一張表:

CREATE TABLE `distributed_lock` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主鍵ID',
  `resource_name` varchar(200) NOT NULL DEFAULT '' COMMENT '資源名稱(唯一索引)',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_resource_name` (`resource_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='分散式鎖';

獲取鎖的時候,就插入一條記錄。插入成功就代表獲取到鎖,插入失敗就代表獲取鎖失敗。

INSERT INTO distributed_lock (`resource_name`) VALUES ('資源1');

釋放鎖的時候,就刪除這條記錄。

DELETE FROM distributed_lock WHERE resource_name = '資源1';

實現比較簡單,不過還不能用於實際生產中,有幾個問題沒有解決:

  1. 這把鎖不支持阻塞,insert失敗立即就返回了。當然可以用while迴圈直到插入成功,不過自旋也會占用CPU。
  2. 這把鎖不是可重入的,已經獲取到鎖的線程再次插入也會失敗,我們可以增加兩列,一列記錄獲取到鎖的節點和線程,另一列記錄加鎖次數。獲取鎖,次數加一,釋放鎖,次數減一,次數為零就刪除這把鎖。
  3. 這把鎖沒有過期時間,如果業務處理失敗或者機器宕機,導致沒有釋放鎖,鎖就會一直存在,其他線程也無法獲取到鎖。我們可以增加一列鎖過期時間,再啟動一個非同步任務掃描過期時間大於當前時間的鎖就刪除。

就是這麼麻煩,我們看一下優化之後的鎖變成什麼樣了:

CREATE TABLE `distributed_lock` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主鍵ID',
  `resource_name` varchar(200) NOT NULL DEFAULT '' COMMENT '資源名稱(唯一索引)',
  `owner` varchar(200) NOT NULL DEFAULT '' COMMENT '鎖持有者(機器碼+線程名稱)',
  `lock_count` int NOT NULL DEFAULT '0' COMMENT '加鎖次數',
  `expire_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鎖過期時間',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_resource_name` (`resource_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='分散式鎖';

這下應該完美了吧?不行,還有個問題:

業務邏輯沒處理完,鎖過期了怎麼辦?

假如我們設置鎖過期時間是6秒,正常情況下業務邏輯可以在6秒內處理完成,但是當JVM發生FullGC或者調用第三方服務出現網路延遲,業務邏輯還沒處理完,鎖已經過期,被刪掉,然後被其他線程獲取到鎖,豈不是要出問題?

這就引入了另一個知識點“鎖續期”:

獲取鎖的同時,啟動一個非同步任務,每當業務執行到三分之一時間,也就是6秒中的第2秒的時候,就自動延長鎖過期時間,繼續延長到6秒,這樣就能保證業務邏輯處理完成之前鎖不會過期。

面試官: 小伙子,分散式鎖算是讓你玩明白了。我還想繼續問,生產中一般很少用MySQL做分散式鎖,因為MySQL併發性能跟不上。剛纔提到Redis也可以實現分散式鎖,你知道該怎麼實現嗎?

我當然知道,八股文就要背全套。

我: 使用Redis實現分散式鎖,跟使用MySQL類似,也需要解決實現過程中遇到的各種問題,不過解決方案稍有不同。

最簡單的獲取鎖方式:

// 1. 獲取鎖
redis.setnx('resource_name1', 'owner1')
// 2. 釋放鎖
redis.del('resource_name1')

當“resource_name1”不存在時,set成功,也就是獲取鎖成功。

不過還需要加上過期時間,防止沒有釋放鎖。

// 1. 獲取鎖
redis.setnx('resource_name1', 'owner1')
// 2. 增加鎖過期時間
redis.exprire('resource_name1', 6, TimeUnit.SECONDS)

又引入新問題了,兩條命令不是原子的,可能獲取鎖之後還沒來得及設置過期時間就宕機了,這該怎麼辦?

好辦,在Redis 2.6.12之後,提供一條複合命令:

redis.set('resource_name1', 'owner1',"NX" "EX", 6)

還有一個問題,釋放鎖的時候,並沒有判斷鎖的持有者,有可能把其他線程持有的鎖給釋放了,這可不行,可以這樣做:

// 釋放鎖
if ('owner1'.equals(redis.get('resource_name1'))){
	redis.del('resource_name1')
}

這樣行不行呢?還不行,因為get和del兩條命令不是原子操作,需要引入Lua腳本把兩條命令打包成一條發給Redis執行:

String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
redis.eval(script, Collections.singletonList('resource_name1'), Collections.singletonList('owner1'))

這樣總行了吧?還不行,還有個“鎖續期”的問題沒有解決。

更簡單了,Redis客戶端Redisson已經幫我們實現續期的功能,叫“WatchDog”(看門狗),在我們調用lock自動喚醒“看門狗”。

面試官: 小伙子,你可真行啊。你再講一下使用zookeeper怎麼實現分散式鎖?

我: zookeeper採用樹形節點,類似Linux目錄文件結構,同一目錄下的節點名稱不能重覆。

節點有分為四種類型:

持久節點: 一旦創建,永久存儲在伺服器上,除非手動刪除。
臨時節點: 生命周期與客戶端綁定,客戶端斷開連接,節點就被自動刪除。
持久順序節點: 特性同持久節點,只是在節點名稱後面追加自增有序數字。
臨時順序節點: 特性同臨時節點,只是在節點名稱後面追加自增有序數字。

zookeeper還有個監聽-通知機制,客戶端可以在資源節點上創建watch事件。當節點發生變化,會通知客戶端,客戶端可以根據變化做相應的業務處理。

我們可以利用臨時順序節點的特性創建分散式鎖,分以下三步:

  1. 在資源/resource1目錄下創建臨時順序節點node
  2. 獲取/resource1目錄下的所有節點,如果當前節點序號最小,代表加鎖成功
  3. 如果不是,就是watch監聽序號最小的節點

實現邏輯很簡單,我們來分析一下zookeeper實現分散式鎖的優點:

  1. 由於創建的臨時節點,斷開連接後自動刪除,所以無需設置鎖超時時間,也就不用考慮不釋放和鎖續期
  2. 由於節點上存儲的創建人信息,鎖也就支持可重入
  3. 由於可以監聽節點,也就實現了可阻塞

面試官: 小伙子,升級加薪的機會就是留給你這樣的人。薪資double,明天就來上班吧。

總結:

關於分散式鎖的所有知識點,雖然很多,但都已經總結在這張圖上了,歡迎點贊收藏轉發評論。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 昨天太晚就沒來得及更新,今天是spu管理界面,這個界面一共有三個界面需要切換,完成了兩個界面,而且今天的難度在於最後兩個章節,富有一定的邏輯性,當然中間也有很多需要註意的,比如ElementUI的照片牆需要添加list屬性而且值為你的數據並且必須是一個數組必須有name、url屬性 一.spu管理 ...
  • 本章是系列文章的第七章,終於來到了鼎鼎大名的SSA,SSA是編譯器領域最偉大的發明之一,也是影響最廣的發明。 本文中的所有內容來自學習DCC888的學習筆記或者自己理解的整理,如需轉載請註明出處。周榮華@燧原科技 7.1 控制流圖回顧 對下麵的c代碼保存成7.1.cc: 1 int max(int ...
  • 樣才能拿到大廠的offer,沒有掌握絕對的技術,那麼就要不斷的學習 他是如何拿下阿裡等大廠的offer的呢,今天分享他的秘密武器,美團資深架構師整理的Java核心知識點,面試時面試官必問的知識點,篇章包括了很多知識點,其中包括了有基礎知識、Java集合、JVM、多線程併發、spring原理、微服務、 ...
  • 引言:今天看MicrosoftDoc關於CFileDialog的doModal函數返回值的部分,提到了實際上MFC提供了錯誤信息顯示。 個人技術博客(文章整理+源碼): https://zobolblog.github.io/LearnWinAPI/ 1.用法: CFileDialog::DoMod ...
  • 一、MybatisSpring的使用 1.創建 Maven 工程。 2.添加依賴,代碼如下 <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.7-ybe</version ...
  • C++ 標準庫提供了原子操作。(我已經懶得寫序言了) 先來說原子操作的概念: 原子操作是多線程當中對資源進行保護的一種手段,主要作用是和互斥量(Mutex)一樣,避免對資源的併發訪問、修改。 互斥量的粒度衡量是作用域(哪怕作用域內只有一個變數),而原子的粒度衡量則是以一個變數或對象為單位。因此,原子 ...
  • 介紹 這是很久之前的一個項目了,最近剛好有些時間,就來總結一下吧! 推薦初步熟悉項目後閱讀本文: https://gitee.com/smalldyy/easy-msg-cpp 從何而來 這要從我從事Qt開發的那些日子說起了,項目說大不大,說小也不小,人倒是一茬又一茬,需求也換了又換,後來的事情大家 ...
  • 引言:沒想到2022年還有很多工業軟體公司依然使用MFC,微軟也一直在更新MFC的庫,這次使用MFC封裝的CFileDialog類,寫一個獲得選定文件路徑,名稱,擴展名的程式。 個人技術博客(文章整理+源碼): https://zobolblog.github.io/LearnWinAPI/ 最終效 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...