【Redis】基礎4:作為分布式鎖

文章目錄

  • 1. 一些概念
  • 2. MySQL方案
    • 2.1 方案一:事務特性
      • 2.1.1 存在的問題
      • 2.1.2 解決方案
    • 2.2 方案二:樂觀鎖
    • 2.3 方案三:悲觀鎖
  • 3. Redis
    • 3.1 實現原理
    • 3.2 實現細節
      • 3.2.1 問題1:持有期間鎖過期問題
      • 3.2.2 問題2:判斷和釋放鎖間隙中鎖過期
  • 4. Zookeeper

1. 一些概念

分布式鎖的要求:互斥(同一時刻只能一個服務實例的一個線程持有),高可用(單節點掛了其他結點頂上),高性能(讀取速度快),安全性(避免死鎖)

分布式鎖的實現:mysql(本身具備互斥性,具備高可用,讀寫性能一般,斷開連接自動釋放鎖),redis(使用setnx實現互斥,具備高可用,讀寫性能高,使用過期時間來釋放鎖),zookeeper(使用結點唯一性或者順序性實現互斥,具備高可用,讀寫性能一般,斷開連接釋放鎖)。綜合來看redis性能好可用性高,安全性略差,mysql和zookeeper安全性好,性能略差。

2. MySQL方案

2.1 方案一:事務特性

MySQL默認開啟自動提交模式,即客戶端執行的每一條 SQL 語句都會被當作一個獨立的事務,一旦語句執行完畢,就會自動提交。事務具有ACID特性,因而可以使用MySQL實現分布式鎖。

create table lock_table
(id int auto_increment comment 'primary key',value varchar(64) null comment 'resource which need be locked',constraint lock_table_pk primary key (id)
);create unique index lock_table_value_uindex
on lock_table (value);-- 注意:value字段是臨界資源。插入成功則獲得鎖,刪除記錄則釋放鎖。一個事務在執行插入時,其他事務插入時失敗。
update lock_table set value = #{newValue} where id = #{id};

2.1.1 存在的問題

  1. 鎖不可重入。可重入鎖的場景:在遞歸代碼中訪問臨界資源會重復請求鎖,可重入鎖可以重復請求鎖阻塞;在復雜的調用關系中使用可重入鎖來防范死鎖問題。
  2. 沒有過期時間,當釋放鎖失敗時會帶來死鎖問題。
  3. 申請鎖失敗時不會阻塞。
  4. 高度依賴數據庫的可用性。

2.1.2 解決方案

  1. 可重入:給表lock_table新增count字段和請求id字段,當同樣的請求到來時只增加count而不是新增記錄。
  2. 給表lock_table新增expire_time字段,通過定時任務定期清理過期的lock
  3. 使用while循環來創造阻塞效果。
  4. 創建備用數據,避免單節點數據庫,提高可用性。

評價:大量事務經常競爭鎖影響性能,一般不使用這個方法。

2.2 方案二:樂觀鎖

樂觀鎖:假設取數據時其他人不會修改數據,修改數據時檢查是否已經有人修改數據。樂觀鎖可以使用CAS(Compare And Set)算法實現。

create table lock_table
(id int auto_increment comment 'primary key',value varchar(64) null comment 'locked resource',version int default 0 null comment 'version'constraint lock_table_pk primary key (id)
);
/*
update loss
MySQL默認隔離等級為repeatable read,同一事務內并發的其他事務不能修改數據,因而可以多次讀出相同的數據。但是修改數據時,其他并發事務可能搶先一步修改了數據(已經提交),這導致從"old_value"到“new_value”的修改實際上是從"unkonwn_value"到"new_value"的修改。
*/-- 樂觀鎖應對update loss
update lock_table set value = #{newValue}, version = #{version} + 1 where id = #{id} and version = #{version};

評價:不依賴數據庫本身的設計,性能差;需要version字段,造成數據庫冗余設計;高并發場景下version字段頻繁變動,系統可用性受到影響。適合于多讀少些低并發的場景。

2.3 方案三:悲觀鎖

# 注意關閉自動提交:autocommit=0# 實現1:使用S鎖,并發事務可以通過加S鎖實現共讀,臨界資源上有S鎖則不許加X鎖。并發事務更新臨界資源時需要加X鎖(update操作默認加X鎖),只有有一個事務得到X鎖。允許共讀,有讀不寫,寫不可讀,只有一寫。
select id, value from lock_table where id = #{id} lock in share mode;
update lock_table set value = #{newValue} where id = #{id};# 實現2:使用X鎖,只有拿到X鎖才能讀,只有拿到X鎖才能寫。每次只許一個事務讀寫。
select id, value from lock_table where id = #{id} for update;
update lock_table set value = #{newValue} where id = #{id};

評價:每個數據請求都加鎖,高并發環境下大量請求獲取不到鎖會陷入阻塞,影響系統性能。表數據量小,MySQL查詢不走索引,因而可能觸發表鎖而不是行鎖,影響并發性能。

3. Redis

3.1 實現原理

setnx只有在key不存在的時候才執行,key存在則執行失敗。這意味著多個并發執行只會有一個成功,這個特點適合用來實現分布式鎖。

# Set the string value of a key only when the key doesn't exist.
SETNX key value# setnx and set expire time are two operations, use set command instead can do all stuff with one operation
SET key value [EX seconds][PX milliseconds][NX|XX]# parameter type
EX seconds: Set expiration time in seconds
PX milliseconds: Set expiration time in milliseconds
NX: Set the value only when the key does not exist
XX: Set the value only when the key exists# release lock
DEL key

3.2 實現細節

3.2.1 問題1:持有期間鎖過期問題

例如,線程1獲取了鎖,其因業務阻塞而長時間持有,結果鎖超時釋放,線程2隨后成功獲取鎖,線程1業務指向完畢會釋放線程2的鎖。

解決1:延長鎖的expire time。使用redis工具Redisson,它可以使用watchdog技術來延時釋放鎖。

解決2:線程釋放鎖時先判斷要釋放的鎖是否是自己的鎖,是再釋放。

KEY_PREFIX = 'lock'
ID_PREFIX = uuid;
LOCK_NAME = bussiness_name  # 和業務相關key = KEY_PREFIX + ':' + LOCK_NAME  # lock和業務相關,建議key為lock:name形式def get_current_reqt_id():cur_reqt_id = get_thread_id()reqt_id = ID_PREFIX + '-' + thread_iddef try_lock(key):reqt_id= get_current_reqt_id(reqt_id)  # value為持有者唯一標識,此處用uuid + 線程id作為值r = redis_client.get(key)if r:return Truereturn Falsedef unlock(key):cur_reqt_id = get_current_reqt_id()reqt_id = redis_client.get(key)result = Falseif cur_reqt_id == reqt_id:redis_client.del(key)result = Truereturn result

3.2.2 問題2:判斷和釋放鎖間隙中鎖過期

例如,線程1要釋放鎖,先判斷鎖是否為線程1持有,判斷為真,然后線程1執行釋放鎖操作。判斷操作和釋放操作間隔較長,鎖自動釋放。線程2在線程1判斷操作后,且鎖被自動釋放后成功獲取了鎖。接著線程1執行釋放鎖操作,則線程1釋放掉線程2的鎖。

解決:使用lua腳本將判斷鎖和釋放鎖打包成原子操作。注意最好將lua腳本加載到內存,方便每次調用直接使用而不是讀文件后再操作。

# 使用redis的EVAL命令來執行lua腳本
# Executes a server-side Lua script.
EVAL script numkeys [key [key ...]] [arg [arg ...]]'''
腳本中
KEYS[1] 代表傳遞給腳本的第一個鍵名參數
ARGS[1] 代表傳遞給腳本的第一個參數值
命令中
1 指示參數個數
name 實際傳遞給腳本的第一個鍵名參數
xxx 實際代表傳遞給腳本的第一個
'''
EVAL "return redis.call('set', KEYS[1], ARGS[1])" 1 name xxx# 釋放鎖lua腳本
if(redis.call('get', KEYS[1]) == ARGS[1]) thenreturn redis.call('del', KEYS[1])
end
return 0

4. Zookeeper

待更新

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/78735.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/78735.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/78735.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

深度學習---框架流程

核心六步 一、數據準備 二、模型構建 三、模型訓練 四、模型驗證 五、模型優化 六、模型推理 一、數據準備:深度學習的基石 數據是模型的“燃料”,其質量直接決定模型上限。核心步驟包括: 1. 數據收集與標注 來源:公開數據集…

阿里云 OpenManus 實戰:高效AI協作體系

阿里云 OpenManus 實戰:高效AI協作體系 寫在最前面初體驗:快速部署,開箱即用 真實案例分享:從單體開發到智能良好提示詞過程展示第一步:為亞馬遜美國站生成商品描述第二步:為eBay全球站生成商品描述結果分析…

Kubernetes》》k8s》》explain查 yaml 參數

在創建json 和yaml 時,我們可能不知道具體的參數該怎么寫。同樣 我們可以通過explain這個 命令來查看 每個參數具體的作用與寫法 # 查看 pod類性有哪些參數 kubectl explain pod# 查看pod中 spec下面有哪些參數 kubectl explain pod.spec

從零構建Dagster分區管道:時間+類別分區實戰案例

分區是Dagster中的核心抽象概念,它允許我們管理大型數據集、處理增量更新并提高管道性能。本文將詳細介紹如何創建和實現基于時間和類別的分區資產。 什么是分區? 分區是將數據集劃分為更小、更易管理的部分的技術。在Dagster中,分區可以基于…

Cursor:AI時代的智能編輯器

在開發者社區掀起熱潮的Cursor,正以破竹之勢重塑編程工具格局。這款基于VS Code的AI優先編輯器,不僅延續了經典IDE的穩定基因,更通過深度集成的智能能力,將開發效率推向全新維度。2023年Anysphere公司獲得的6000萬美元A輪融資&…

SpringMVC再復習1

一、三層架構 表現層(WEB 層) 定義 :是應用程序與客戶端進行交互的最外層,主要負責接收用戶的請求,并將處理結果顯示給用戶。 作用 :在 Spring MVC 中,表現層通常采用 MVC 設計模式來構建。 技…

Centos 7系統 寶塔部署Tomcat項目(保姆級教程)

再看文章之前默認已經安裝好系統,可能是云系統,或者是虛擬機。 寶塔安裝 這個比較簡單,參考這個老哥的即可: https://blog.csdn.net/weixin_42753193/article/details/125959289 環境配置 進入寶塔面板之后會出現環境安裝&…

Nginx核心功能

目錄 一:基于授權的訪問控制 1:基于授權的訪問控制簡介 2:基于授權的訪問控制步驟 (1)使用htpasswd 生成用戶認證文件 (2)修改密碼文件權限為400,將所有者改為nginx,…

AnimateCC基礎教學:漫天繁星-由DeepSeek輔助完成

1.界面及元件抓圖: 2.核心代碼: // 初始化設置 var stars []; var stars2 []; var numStars 100; var stageWidth stage.canvas.width; var stageHeight stage.canvas.height; console.log(stageWidth, stageHeight);// 創建星星函數 function createStar() {var star n…

通過DeepSeek大語言模型控制panda機械臂,聽懂人話,擬人性回答。智能機械臂助手又進一步啦

文章目錄 前言環境配置運行測試報錯 前言 通過使用智能化的工作流控制系統來精確操控機械臂,不僅能夠基于預設算法可靠地規劃每個動作步驟的執行順序和力度,確保作業流程的標準化和可重復性,還能通過模塊化的程序設計思路靈活地在原有工作流中…

分享一款免費的 AI 工作流平臺

分享一款 AI 工作流/任務流平臺,通過直觀的流程圖設計,輕松實現復雜業務流程的自動化與可視化,無縫集成 AI 大模型、AI 生圖、數據庫、條件分支、并行節點、自定義任務節點等等。 效果圖: 官網體驗地址:https://www.…

前端開發本地配置 HTTPS 全面詳細教程

分為兩步:生成證書、本地服務配置使用證書一、HTTPS 的基本概念 HTTPS 是一種安全的 HTTP 協議,它通過 SSL/TLS 對數據進行加密,確保數據在傳輸過程中不被竊取或篡改。在前端開發中,某些功能(如 Geolocation API、Web…

day10 python機器學習全流程實踐

在機器學習的實踐中,數據預處理與模型構建是極為關鍵的環節。本文將回顧數據預處理的全流程,并基于處理后的數據完成簡單的機器學習建模與評估,暫不涉及復雜的調參過程。 一、預處理流程回顧 機器學習的成功,很大程度上依賴于高…

4月28號

初認web前端: web標準: HTML:

【Linux系統】systemV共享內存

system V共享內存 在Linux系統中,共享內存是一種高效的進程間通信(IPC)機制,它允許兩個或者多個進程共享同一塊物理內存區域,這些進程可以將這塊區域映射到自己的虛擬地址空間中。 共享內存區是最快的IPC形式。一旦這…

(七)RestAPI 毛子(Http 緩存/樂觀鎖/Polly/Rate limiting/異步大文件上傳)

文章目錄 項目地址一、Http Cache1.1 服務注冊1.2 Validation with ETag1. 添加ETagMiddleware中間件2. 創建內存ETag存儲器3. 服務注冊4. 測試二、使用ETag實現樂觀鎖2.1 添加樂觀鎖方法2.2 修改Controller2.3 測試樂觀鎖三、Rate Limiting3.1 添加速率控制服務1. 在Program里…

2025.4.26_STM32_SPI

1.SPI簡介 2.硬件電路 所有SPI設備的SCK(時鐘)、MOSI(主機輸出從機輸入)、MISO(主機輸入從機輸出)分別連在一起。SCK線只能被主機控制,和I2C相同。 主機另外引出多條SS控制線,分別接到各從機的SS引腳 (SS不用的時候為高電平,當主機需要選中某…

JAVA:單例模式

單例模式是設計模式之一 設計模式,就像古代打仗,我們都聽過孫子兵法,把計謀概括下來弄成一種模式,形成一種套路。 軟件開發中也有很多場景,多數類似的問題場景,解決方案就形成固定的模式,單例…

腦機接口:重塑人類未來的神經增強革命

引言 人類對大腦的探索從未停止,而腦機接口(Brain-Computer Interface, BCI)的崛起,正在將科幻電影中的“意念操控”變為現實。 這項技術通過解碼腦電信號,實現人腦與外部設備的直接交互,不僅為醫療康復帶來…

從SOA到微服務:架構演進之路與實踐示例

一、架構演進背景 在軟件開發領域,架構風格隨著業務需求和技術發展不斷演進。從早期的單體架構,到面向服務架構(SOA),再到如今的微服務架構,每一次變革都是為了解決當時面臨的核心問題。 二、SOA架構解析 2.1 SOA核心概念 SOA&…