Redis系列:使用Redis實現分布式鎖及相關問題

分布式鎖其實就是,控制分布式系統不同進程共同訪問共享資源的一種鎖的實現。如果不同的系統或同一個系統的不同主機之間共享了某個臨界資源,往往需要互斥來防止彼此干擾,以保證一致性。

本篇內容包括:關于 Redis 與 分布式鎖,Redis 分布式鎖的問題及解決方式,Redis 中的 Lua 腳本 以及 Redis 中的 RedLock 算法!


文章目錄

    • 一、關于 Redis 與 分布式鎖
        • 1、關于分布式鎖
        • 2、關于 Redis 實現分布式鎖
    • 二、Redis 分布式鎖的問題及解決方式
    • 三、Redis 中的 Lua 腳本
    • 四、Redis 中的 RedLock 算法
        • 1、Redis 中的 RedLock 算法
        • 2、 Redlock 算法的客戶端的執行步驟


一、關于 Redis 與 分布式鎖

1、關于分布式鎖

在一個分布式系統中,當一個線程去讀取數據并修改的時候,因為讀取和更新保存不是一個原子操作,在并發時就很容易遇到并發問題,進而導致數據的不正確。這種場景很常見,比如電商秒殺活動,庫存數量的更新就會遇到。如果是單機應用,直接使用本地鎖就可以避免。如果是分布式應用,本地鎖派不上用場,這時就需要引入分布式鎖來解決。

一般來說,實現分布式鎖的方式有以下幾種:

  • 使用 MySQL,基于唯一索引。
  • 使用 ZooKeeper,基于臨時有序節點。
  • 使用 Redis,基于 setnx 命令。

2、關于 Redis 實現分布式鎖

Redis 實現分布式鎖主要利用 Redis 的setnx 命令。setnx 是 SET if not exists(如果不存在,則 SET)的簡寫。

加鎖:使用setnx key value命令,如果 key 不存在,設置 value(加鎖成功)。如果已經存在 lock(也就是有客戶端持有鎖了),則設置失敗(加鎖失敗)

解鎖:使用 del 命令,通過刪除鍵值釋放鎖。釋放鎖之后,其他客戶端可以通過 setnx 命令進行加鎖。

Key 的值可以根據業務設置,比如是用戶中心使用的,可以命令為USER_REDIS_LOCK,value 可以使用 uuid 保證唯一,用于標識加鎖的客戶端。保證加鎖和解鎖都是同一個客戶端。


二、Redis 分布式鎖的問題及解決方式

首先,有一個致命問題,就是某個線程在獲取鎖之后由于某些異常因素(比如宕機)而不能正常的執行解鎖操作,那么這個鎖就永遠釋放不掉了。為此,我們可以為這個鎖加上一個超時時間為此,我們可以為這個鎖加上一個超時時間

  • 執行 SET key value EX seconds 的效果等同于執行 SETEX key seconds value
  • 執行 SET key value PX milliseconds 的效果等同于執行 PSETEX key milliseconds value

然后,此時依然會有問題,某線程 A 獲取了鎖并且設置了過期時間為 10s,然后在執行業務邏輯的時候耗費了 15s,此時線程 A 獲取的鎖早已被 Redis 的過期機制自動釋放了在線程A獲取鎖并經過 10s 之后,改鎖可能已經被其它線程獲取到了。當線程 A 執行完業務邏輯準備解鎖(DEL key)的時候,有可能刪除掉的是其它線程已經獲取到的鎖。當解鎖時,也就是刪除 key 的時候先判斷一下 key 對應的 value 是否等于先前設置的值,如果相等才能刪除 key。

最后,這里我們還是一眼就可以看出問題來:GETDEL是兩個分開的操作,在 GET 執行之后且在 DEL 執行之前的間隙是可能會發生異常的,我們引入了一種新的方式,就是 Lua 腳本,解決原子性的問題。Redis 會將整個 Lua 腳本作為一個整體執行,中間不會被其他請求插入。

另外,為了防止多個線程同時執行業務代碼,需要確保過期時間大于業務執行時間,可以在代碼增加一個線程用于刷新定時過期時間,并增加一個 bool 類型的值表示是否開啟定時刷新過期時間,在線程獲取鎖的時候,將其設置為 true,解鎖前設置回 false。比如,Redisson 實現,獲取鎖成功就會開啟一個定時任務,定時任務會定期檢查去續期。

此外,還有一個問題:在集群中,主節點掛掉時,從節點會取而代之,客戶端上卻并沒有明顯感知。原先第一個客戶端在主節點中申請成功了一把鎖,但是這把鎖還沒有來得及同步到從節點,主節點突然掛掉了。然后從節點變成了主節點,這個新的節點內部沒有這個鎖,所以當另一個客戶端過來請求加鎖時,立即就批準了。這樣就會導致系統中同樣一把鎖被兩個客戶端同時持有,不安全性由此產生。此處可以用 RedLock 算法解決。


三、Redis 中的 Lua 腳本

Lua 是一種輕量小巧的腳本語言,用標準 C 語言編寫并以源代碼形式開放。其設計目的就是為了嵌入應用程序中,從而為應用程序提供靈活的擴展和定制功能

Redis 在 2.6 版本推出了 lua 腳本功能,允許開發者使用 Lua 語言編寫腳本傳到 Redis 中執行。

使用 Lua 腳本的好處:

  • 原子操作。Redis 會將整個腳本作為一個整體執行,中間不會被其他請求插入。因此在腳本運行過程中無需擔心會出現競態條件,無需使用事務;
  • 減少網絡開銷。可以將多個請求通過腳本的形式一次發送,減少網絡時延;
  • 復用。客戶端發送的腳本會永久存在 Redis 中,這樣其他客戶端可以復用這一腳本,而不需要使用代碼完成相同的邏輯。

四、Redis 中的 RedLock 算法

1、Redis 中的 RedLock 算法

在集群中,主節點掛掉時,從節點會取而代之,客戶端上卻并沒有明顯感知。原先第一個客戶端在主節點中申請成功了一把鎖,但是這把鎖還沒有來得及同步到從節點,主節點突然掛掉了。然后從節點變成了主節點,這個新的節點內部沒有這個鎖,所以當另一個客戶端過來請求加鎖時,立即就批準了。這樣就會導致系統中同樣一把鎖被兩個客戶端同時持有,不安全性由此產生

Redlock 算法就是為了解決這個問題

使用 Redlock,需要提供多個 Redis 實例,這些實例之前相互獨立沒有主從關系。同很多分布式算法一樣,Redlock 也使用大多數機制

加鎖時,它會向過半節點發送 set 指令,只要過半節點 set 成功,那就認為加鎖成功。釋放鎖時,需要向所有節點發送 del 指令。不過 Redlock 算法還需要考慮出錯重試、時鐘漂移等很多細節問題,同時因為 Redlock 需要向多個節點進行讀寫,意味著相比單實例 Redis 性能會下降一些

Redlock 算法是在單 Redis 節點基礎上引入的高可用模式,Redlock 基于 N 個完全獨立的 Redis 節點,一般是大于 3 的奇數個(通常情況下 N 可以設置為 5),可以基本保證集群內各個節點不會同時宕機。

2、 Redlock 算法的客戶端的執行步驟

當 Redis 集群有 5 個節點,運行 Redlock 算法的客戶端的執行步驟:

  1. 客戶端記錄當前系統時間,以毫秒為單位;
  2. 依次嘗試從 5 個 Redis 實例中,使用相同的 key 獲取鎖,當向 Redis 請求獲取鎖時,客戶端應該設置一個網絡連接和響應超時時間,超時時間應該小于鎖的失效時間,避免因為網絡故障出現的問題;
  3. 客戶端使用當前時間減去開始獲取鎖時間就得到了獲取鎖使用的時間,當且僅當從半數以上的 Redis 節點獲取到鎖,并且當使用的時間小于鎖失效時間時,鎖才算獲取成功;
  4. 如果獲取到了鎖,key 的真正有效時間等于有效時間減去獲取鎖所使用的時間,減少超時的幾率;
  5. 如果獲取鎖失敗,客戶端應該在所有的 Redis 實例上進行解鎖,即使是上一步操作請求失敗的節點,防止因為服務端響應消息丟失,但是實際數據添加成功導致的不一致。

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

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

相關文章

Redis系列:Redis持久化機制與Redis事務

Redis 是個基于內存的數據庫。那服務一旦宕機,內存中數據必將全部丟失。所以丟失數據的恢復對于 Redis 是十分重要的,我們首先想到是可以從數據庫中恢復,但是在由 Redis 宕機時(說明相關工作正在運行)且數據量很大情況…

Java基礎:Java程序設計環境

按應用范圍,Java 可分為 3 個體系,即 Java SE、Java EE 和 Java ME。Java 語言的開發運行,也離不開 Java 語言的運行環境 JRE。沒有 JRE 的支持,Java 語言便無法運行。當然,如果還想編譯 Java 程序,搞搞小開…

負載均衡策略

輪循均衡(Round Robin):每一次來自網絡的請求輪流分配給內部中的服務器,從1至N然后重新開始。此種均衡算法適合于服務器組中的所有服務器都有相同的軟硬件配置并且平均服務請求相對均衡的情況。 我們的業務web服務器都是同樣配置…

Java基礎:Java數據類型

Java 是一種強類型語言,這就意味著必須為每一個變量聲明一種類型。在 Java 中基本數據類型共有 8 種,包括 4 種整型、2 種浮點型、1 種用于表現 Unicode 編碼的字符單元的字符類型 char 和一種用于表示真值的 boolean 類型 ~ 本篇主要記錄內容包括&#…

TCP連接的建立與終止

TCP連接的建立與終止 1.三次握手 TCP是面向連接的,無論哪一方向另一方發送數據之前,都必須先在雙方之間建立一條連接。在TCP/IP協議中,TCP協議提供可靠的連接服務,連接是通過三次握手進行初始化的。三次握手的目的是同步連接雙方…

日常問題:MySQL排序字段數據相同不能分頁問題

【問題日期】 2022-11-14 22:45:12 【問題描述】 MySQL 排序字段數據相同不能分頁問題:在分頁查詢數據時,按創建時間排序,由于數據是批量創建的,導致部分數據創建時間一樣,而此時分頁查詢數據,翻頁后出現…

數據缺失值處理

數據缺失值處理 In [1]: import pandas as pd import numpy as np from sklearn.ensemble import RandomForestRegressor,RandomForestClassifier from sklearn.preprocessing import StandardScaler from sklearn.impute import SimpleImputer In [2]: df pd.DataFrame() df[…

Java基礎:Java數字類型

Java 中包含多種運算符:算數運算符、關系運算符、邏輯運算符、位運算符。在 Math 類中,包含了各種各樣的數學函數。在編寫不同類別的程序時,可能需要的函數也不同。要生成一個隨機數,可以使用 Random 對象。 ~ 本篇主要…

Java基礎:Java流程控制

塊(即復合語句)是指由一對大括號括起來的若干條簡單的 Java 語句。塊確定了變量的作用域。一個塊可以嵌套在另一個塊中。但是,不能在嵌套的兩個塊中聲明同名的變量。使用塊(有時稱為復合語句)可以在Java程序結構中原本…

Java基礎:Java類與對象

面向對象程序設計(簡稱OOP)是當今主流的程序設計范型,它已經取代了20世紀70年代的“結構化”過程化程序設計開發技術。Java是完全面向對象的,必須熟悉OOP才能夠編寫Java程序。面向對象的程序是由對象組成的,每個對象包…

SVN備份腳本

#!/bin/bash #svn全量備份腳本 wwytcode_path/home/wwytcode/project backup_path/home/bak Datedate %Y%m%d cd $backup_path echo date >> $back_path/svn_backup.log svnversionsvnlook youngest $wwytcode_path svnadmin dump --revision 0:$svnversion $wwytcode_pa…

Java基礎:Java面向對象

面向過程的優點是性能比面向對象高,不需要面向對象的實例化;缺點是不容易維護、復用和擴展。面向對象的優點是具有封裝、繼承、多態的特性,因而容易維護、復用和擴展,可以設計出低耦合的系統;缺點是由于需要實例化對象…

薪資生成

import openpyxl from openpyxl.styles import Font,Alignment,Side,Border#設置字體樣式 fontFont(name宋體,size20,boldTrue) font2Font(name宋體,size12,boldTrue) alignmentAlignment(horizontalcenter,verticalcenter,wrap_textTrue) sideSide(stylethin ,color000000) …

Java基礎:Java抽象接口

在Java中,一個沒有方法體的方法應該定義為抽象方法,而如果一個類中含有抽象方法,則該類必須定義為一個抽象類。接口是功能的集合,同樣可看做是一種特殊的數據類型,是比抽象類更為抽象的類,接口只描述所應該…

13 張圖帶你學懂 Kubernetes Service(轉載)

在 Kubernetes 中 Service 主要有4種不同的類型,其中的 ClusterIP 是最基礎的,如下圖所示: 當我們創建一個 NodePort 的 Service 時,它也會創建一個 ClusterIP,而如果你創建一個 LoadBalancer,它就會創建一…

Java基礎:Java異常機制

異常是程序運行過程中出現的錯誤。Java 把異常當作對象來處理,把異常信息封裝成了一個類,并定義一個基類java.lang.Throwable作為所有異常的超類。Throwable : 它是所有錯誤與異常的超類(祖宗類),有兩個子類 Error 和 Exception。…

JavaWeb:Servlet的應用及接口介紹

廣義的 Servlet 泛指在服務器上運行的 Java 程序,但是這個 Java 程序,并不能獨立運行(因為 Servlet 沒有 main 方法),需要部署在相應的 Servlet 容器中,比如 Tomcat 和 Jetty。Servlet 主要功能在于交互式地…

DOCKERFILE參數注解

Dockerfile由一行行命令語句組成,并且支持以#開頭的注釋行。 一般的,Dockerfile 分為四部分:基礎鏡像信息、維護者信息、鏡像操作指令和容器啟動時執行指令。 Dockerfile的指令是忽略大小寫的,建議使用大寫,使用 # 作為…

Zookeeper:分布式過程協同技術

Zookeeper 是一個高性能的分布式一致系統,在分布式系統中有著廣泛的應用。基于它,可以實現諸如“分布式同步”、“配置管理”、“命名空間管理”等眾多功能,是分布式系統中常見的基礎系統。Zookeeper 主要用來解決分布式集群中應用系統的一致…

面試題2021-2-24

給某CentOs6慮擬機添加了新的數據盤,設備名為/de/sdd.寫命令格式化滿加的效的并掛載到指定目錄/opt fdisk -l mkfs.ext4 /de/sdd mount /de/sdd /opt 如何查看與RabbtMQ服務器之間的establish狀態連接數?netstat -an |grep ESTABLISHED |grep tcp |wc -l…