Redis并發問題解決方案

目錄

前言

1.分布式鎖

1.基于單個節點

2.基于多個節點

3.watch(樂觀鎖)?

2.原子操作

1.單命令操作

2.Lua 腳本(多命令操作)

3.事務

1.執行步驟

2.錯誤處理

3.崩潰處理

總結


前言

在多個客戶端并發訪問Redis的時候,雖然Redis是單線程執行指令,但是由于客戶端指令達到Redis的時序無法保證,所以可能出現如下的情況,導致并發問題。

2個客戶端都執行 get, set指令,期望將key的值設置為3,結果因為并發問題,導致結果為2
client1 get x => 1
client2 get x => 1
client1 set x => 2
client2 set x => 2

本文介紹 Redis 并發方面的解決方案。

Redis 的單個命令是原子的,但是一個業務操作可能包含多條命令,比如以下場景:客戶端查詢值,并遞增,在高并發場景下就可能出現并發問題,導致數據不一致。

為了保證并發訪問的正確性,Redis 提供了三種方法,原子操作、分布式鎖、事務。

1.分布式鎖

與分布式鎖相對的是本地鎖,假如只有一個服務實例,就可以直接在該單應用本地使用鎖變量來控制多個客戶端的訪問。

如果使用的是多實例的分布式系統,就需要使用分布式鎖,即將鎖保存在一個第三方的共享存儲系統中,可以被多個客戶端共享訪問和獲取。通常將一個 Redis 實例作為分布式鎖的存儲系統。

實現分布式鎖的關鍵在于:

  • 保證每個加鎖、釋放鎖操作都是原子的;
  • 保證共享存儲系統的可靠性,即鎖的可靠性;
分布式鎖相較于 Lua 腳本,更簡單易用,但是可能存在死鎖問題。分布式鎖的性能不如 Lua 腳本。

1.基于單個節點

Redis 提供了 SETNX 命令(在 SET 命令后加上 NX 選項也能達到同樣的效果),保證了加鎖操作的原子性。

同時,為了避免客戶端加鎖后不釋放,應該給鎖變量設置過期時間(set NX EX),且在過期釋放鎖時,判斷業務代碼是否執行完成,如果未完成則給鎖續期。如果多次續期后,業務仍然未完成,再釋放鎖。(仍然存在風險)

為了區分不同客戶端的操作,應該將鎖變量設置為隨機值或唯一值,在釋放鎖時進行驗證。

釋放鎖的邏輯包含了讀取鎖變量、判斷值、刪除鎖變量的多個操作,所以應該使用 Lua 腳本來保證互斥執行。

單個節點可以實現分布式鎖的功能,但是無法保證可靠性

2.基于多個節點

為了避免 Redis 實例故障而導致的鎖無法工作的問題,Redis 的開發者 Antirez 提出了分布式鎖算法 Redlock。

Redlock 算法的基本思路,是讓客戶端和多個獨立的 Redis 實例依次請求加鎖,如果客戶端能夠和半數以上的實例成功地完成加鎖操作,就認為客戶端成功地獲得分布式鎖了,否則加鎖失敗。這樣一來,即使有單個實例發生故障,因為鎖變量在其它實例上也有保存,所以客戶端仍然可以正常地進行鎖操作,鎖變量并不會丟失。

加鎖過程:

  1. 客戶端獲取當前時間;
  2. 客戶端按順序依次向 N 個 Redis 實例執行加鎖操作。同樣使用 SETNX 命令,并設置超時時間。如果請求加鎖一直超時,則視為加鎖失敗,向下一個實例執行加鎖操作。
  3. 客戶端完成所有加鎖操作后,計算整個加鎖過程的總耗時。

客戶端只有在滿足下面的這兩個條件時,才能認為是加鎖成功:

  • 從超過半數(大于等于 N/2+1)的實例上成功獲取到了鎖;
  • 獲取鎖的總耗時沒有超過鎖的有效時間。

在滿足了這兩個條件后,還需要重新計算這把鎖的有效時間,計算的結果是鎖的最初有效時間減去客戶端為獲取鎖的總耗時。如果鎖的有效時間已經來不及完成共享數據的操作了,可以釋放鎖,以免出現還沒完成數據操作,鎖就過期了的情況。
如果沒能同時滿足這兩個條件,則視為加鎖失敗,執行釋放鎖的過程:客戶端會向所有節點發起釋放鎖的操作,執行釋放鎖的 Lua 腳本。

判斷是否加鎖時,需要查詢所有節點,以半數以上節點的鎖狀態來判斷整個分布式鎖的狀態。在釋放鎖之前,需要先判斷分布式鎖的狀態。

為了避免 Redis 節點發生崩潰重啟后造成鎖丟失,從而影響鎖的安全性,antirez 還提出了延時重啟的概念,即一個節點崩潰后不要立即重啟,而是等待一段時間后再進行重啟,這段時間應該大于鎖的有效時間。優點是保證了鎖不會被多個客戶端獲取;缺點是延長了重啟時間,可能對系統造成影響。

性能和一致性是沖突的,如果為了分布式鎖的高可用性,可以開啟持久化,但是會有額外的性能開銷,需要根據實際場景進行選擇。

3.watch(樂觀鎖)?

watch通常跟redis事務配合使用,watch某個key在操作過程中有沒有被其他指令改變,進而做出相應的處理。底層利用了CAS操作,后面講Redis事務會講到。

2.原子操作

為了實現并發控制要求的臨界區代碼互斥執行,Redis 的原子操作采用了兩種方法:單命令操作和 Lua 腳本。

1.單命令操作

Redis 的每個操作都是原子性的。

Redis 是使用單線程來串行處理客戶端的請求操作命令的,所以,當 Redis 執行某個命令操作時,其他命令是無法執行的,這相當于單個操作是原子的。雖然 Redis 的單個操作是原子的,但是通常修改數據是包含多個操作的,至少包括讀數據、修改數據、寫回數據這三個操作,此時仍然可能出現并發問題。

針對常用的修改數據場景,Redis 提供了 INCR/DECR 命令,可以對數據進行簡單的遞增/遞減操作,它們本身就是單個命令操作,在執行時,具有互斥性。但是如果要執行更復雜的操作,Redis 的單命令操作就無法保證互斥執行了。

2.Lua 腳本(多命令操作)

Redis 可以將多個操作寫在 Lua 腳本中,然后把整個 Lua 腳本作為一個整體執行,在執行的過程中不會被其他命令打斷,從而保證了 Lua 腳本中操作的原子性。

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

Lua 腳本可以在服務器端執行,不需要將數據傳輸到客戶端再進行處理,可以減少網絡傳輸的開銷,因此性能較高。

使用 Lua 腳本不僅可以實現將多個操作原子執行,還能夠復用 Lua 腳本。但是使用 Lua 腳本需要額外的語言學習成本,還有調試困難、可讀性較差的問題。

如果把很多操作都放在 Lua 腳本中原子執行,會導致 Redis 執行腳本的時間增加,同樣也會降低 Redis 的并發性能。所以,在編寫 Lua 腳本時,要避免把不需要做并發控制的操作寫入腳本中。

在 Lua 腳本執行過程中崩潰怎么辦?
Redis 會在內部維護一個已經加載腳本的 哈希表,記錄了每個腳本的 SHA1 值和對應的 Lua 腳本代碼。當 Redis 服務器重啟時,Redis 會自動重新加載這個哈希表中記錄的所有腳本,再重新執行,此時可能導致部分修改被應用。所以 Lua 腳本并不能嚴格保證原子性。如果對數據一致性非常嚴格,可以使用 Lua 腳本+事務 WATCH 的辦法。

3.事務

Redis 事務的本質是一組命令的集合。事務支持一次執行多個命令,一個事務中所有命令都會被序列化。在事務執行過程,會按照順序串行化執行隊列中的命令,其他客戶端提交的命令請求不會插入到事務執行命令序列中。

Redis 提供了實現事務的幾個命令:

MULTI :開啟事務,redis 會將后續的命令逐個放入隊列中,然后使用 EXEC 命令來原子化執行這個命令系列。

EXEC:執行事務中的所有操作命令。

DISCARD:取消事務,放棄執行事務塊中的所有命令。

WATCH:在開啟事務之前監視一個或多個 key,如果事務在執行前,這個 key (或多個 key)被其他命令修改,則事務被中斷,不會執行事務中的任何命令(一般需要在 EXEC 執行失敗后重新執行整個函數)。

UNWATCH:取消 WATCH 對所有 key 的監視。

為什么 WATCH 是中斷事務,而不是阻塞其他進程?這樣不會導致并發量高的時候,被 WATCH 的事務一直得不到執行嗎?
這種機制稱為 樂觀鎖,因為在大多數情況下,碰撞的概率很小,所以選用了更容易實現的方式(且影響不大)。

?

在使用事務時,可以配合 Pipeline 使用:一次性將所有命令打包好,再全部發送到服務端。
相比于事務的入隊,同樣是一次性執行,這樣不僅能減少網絡 IO,還能保證在開啟 WATCH 時不會被其他操作打斷。

1.執行步驟

  1. 開啟事務:使用 MULTI 命令開啟事務;
  2. 入隊:接收到命令后并不會立即執行,而是放到等待執行的事務隊列里;
  3. 執行:由 EXEC 命令觸發事務執行。

當客戶端切換到事務狀態之后, 服務器會根據這個客戶端發來的不同命令執行不同的操作:

  • 如果客戶端發送的命令為 EXEC 、DISCARD、WATCH、MULTI 四個命令的其中一個, 那么服務器立即執行這個命令;
  • 如果是其他命令, 那么服務器并不立即執行命令, 而是將這個命令放入一個事務隊列里面, 然后向客戶端返回 QUEUED 回復;

2.錯誤處理

在事務執行過程中可能遇到兩種不同類型的錯誤,會有不同的應對方案:

  • 編譯器錯誤:命令在編譯時出錯,會導致整個事務提交失敗,即所有命令執行不成功;
  • 運行時錯誤:命令在運行時檢測到錯誤,最終會導致事務提交失敗,但是事務并不會回滾,而是跳過錯誤命令繼續執行并保留結果;
為什么 Redis 不支持 事務回滾?
Redis 命令只會因為錯誤的語法而失敗(并且這些問題不能在入隊時發現),或是命令用在了錯誤類型的鍵上面:這也就是說,從實用性的角度來說,失敗的命令是由編程錯誤造成的,而這些錯誤應該在開發的過程中被發現,而不應該出現在生產環境中。
不需要對回滾進行支持,所以 Redis 的內部可以保持簡單且快速。

3.崩潰處理

Redis 在執行事務時會使用一個單獨的內存空間來保存事務中的所有修改操作,只有當事務成功提交時,這些修改操作才會被應用到 Redis 中。因此,如果事務被中止,所有的修改操作也都會被撤銷,從而保證了數據的一致性。

如果開啟了 AOF 持久化,會先將事務中的所有命令寫入 AOF 緩沖區,然后執行事務中的命令,再將 AOF 緩沖區中的數據寫入到 AOF 文件。

如果在寫入 AOF 文件前崩潰,則持久化失敗,相當于事務沒有發生,不會出現數據不一致。

另外,RDB 快照不會在事務執行途中進行。

總結

本文介紹了 Redis 應對并發問題的三種方案,Redis 中的單條命令都是原子操作,而且還有 INCR/DECR 來應對簡單的場景。對于復雜的場景,Redis 可以使用 Lua 腳本、分布式鎖、事務來實現操作的原子性。Lua 腳本是將一系列操作放在一個腳本中原子執行。分布式鎖是通過共享的鎖變量來限制客戶端的并發訪問。事務是將一系列操作放到執行隊列中,再按順序原子執行。

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

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

相關文章

【間歇振蕩器2片555時基仿真】2022-9-24

緣由multisim出現這個應該怎么解決吖,急需解決-嵌入式-CSDN問答 輸出一定要有電阻分壓才能前后連接控制否則一定報錯。

Python自動化生成漂亮的測試報告

📢專注于分享軟件測試干貨內容,歡迎點贊 👍 收藏 ?留言 📝 如有錯誤敬請指正!📢交流討論:歡迎加入我們一起學習!📢資源分享:耗時200小時精選的「軟件測試」資…

五種多目標優化算法(MOJS、NSGA3、MOGWO、NSWOA、MOPSO)求解微電網多目標優化調度(MATLAB代碼)

一、多目標優化算法簡介 (1)多目標水母搜索算法MOJS 多目標優化算法:多目標水母搜索算法MOJS(提供MATLAB代碼)_水母算法-CSDN博客 (2)NSGA3 NSGA-III求解微電網多目標優化調度(M…

acwing算法基礎之數學知識--求卡特蘭數

目錄 1 基礎知識2 模板3 工程化 1 基礎知識 題目:給定n個0和n個1,它們將按照某種順序排成長度為2n的序列,求它們能排成的所有序列中,能夠滿足任意前綴序列中0的個數都不少于1的個數的序列有多少個? 輸出的答案對 1 0 …

【云原生 Prometheus篇】Prometheus的動態服務發現機制與認證配置

目錄 一、Prometheus服務發現的方式1.1 基于文件的服務發現1.2 基于consul的服務發現1.3 基于 Kubernetes API 的服務發現1.3.1 簡介1.3.2 基于Kurbernetes發現機制的部分配置參數 二、實例一:部署基于文件的服務發現2.1 創建用于服務發現的文件2.2 修改Prometheus的…

yo!這里是c++11重點新增特性介紹

目錄 前言 列表初始化 { }初始化 initializer_list類 類型推導 auto decltype 范圍for 右值引用與移動語義 左值引用和右值引用 移動語義 1.移動構造 2.移動賦值 3.stl容器相關更新 右值引用和萬能引用 完美轉發 關鍵字 default delete final和override …

西米支付:簡單介紹一下支付公司的分賬功能體系

隨著互聯網的普及和電子商務的快速發展,支付已經成為人們日常生活的重要組成部分。支付公司作為第三方支付平臺,為消費者和商家提供了便捷、安全的支付方式。而在支付領域中,分賬功能是一個非常重要的功能,它可以幫助企業實現資金…

SpringBoot——攔截器

優質博文:IT-BLOG-CN 一、登錄時可能會出現重復提交問題。我們可以通過重定向解決此問題。例如:用戶提交的請求為:/user/login,通過redirect:重定向至 main.html請求。 PostMapping("/user/login") public …

C語言——從終端(鍵盤)將 5 個整數輸入到數組 a 中,然后將 a 逆序復制到數組 b 中,并輸出 b 中 各元素的值。

#define _CRT_SECURE_NO_WARNINGS 1#include<stdio.h> int main() {int i;int a[5];int b[5];printf("輸入5個整數&#xff1a;\n");for(i0;i<5;i){scanf("%d",&a[i]);}printf("數組b的元素值為&#xff1a;\n");for(i4;i>0;i--…

Windows任務管理器內存性能界面各個參數含義

任務管理器的內存性能界面提供了一些關鍵參數&#xff0c;這些參數可以幫助你了解系統中內存的使用情況。以下是一些常見的參數及其含義&#xff1a; 已提交&#xff08;Committed&#xff09;&#xff1a; 表示已分配的物理內存和虛擬內存的總和。已提交的內存包括當前正在使…

Javascript每天一道算法題(十五)——輪轉數組_中等(一行解決輪轉數組)

文章目錄 1、問題2、示例3、解決方法&#xff08;1&#xff09;方法1——while遍歷&#xff08;較為復雜&#xff0c;不推薦&#xff09;&#xff08;2&#xff09;方法2&#xff08;直接截取后插入&#xff0c;推薦&#xff09;&#xff08;3&#xff09;方法3——優化方法2&a…

jQuery_03 dom對象和jQuery對象的互相轉換

dom對象和jQuery對象 dom對象 jQuery對象 在一個文件中同時存在兩種對象 dom對象: 通過js中的document對象獲取的對象 或者創建的對象 jQuery對象: 通過jQuery中的函數獲取的對象。 為什么使用dom或jQuery對象呢&#xff1f; 目的是 要使用dom對象的函數或者屬性 以及呢 要…

python -opencv 輪廓檢測(多邊形,外接矩形,外接圓)

python -opencv 輪廓檢測(多邊形&#xff0c;外接矩形&#xff0c;外接圓) 邊緣檢測步驟: 第一步&#xff1a;讀取圖像為灰度圖 第二步&#xff1a;進行二值化處理 第三步&#xff1a;使用cv2.findContours對二值化圖像提取輪廓 第三步&#xff1a;將輪廓繪制到圖中 代碼如下…

Hibernate的三種狀態

1.瞬時狀態(Transient) 通過new創建對象后&#xff0c;對象并沒有立刻持久化&#xff0c;他并未對數據庫中的數據有任何的關聯&#xff0c;此時java對象的狀態為瞬時狀態&#xff0c;Session對于瞬時狀態的java對象是一無所知的&#xff0c;當對象不再被其他對象引用時&#xf…

【TL431+場效應管組成過壓保護電路】2022-3-22

緣由這個穩壓三極管是構成的電路是起到保護的作用嗎&#xff1f;-硬件開發-CSDN問答

HTML5+ API 爬坑記錄

背景: 有個比較早些使用5開發的項目, 最近兩天反饋了一些問題, 解決過程在此記錄; 坑1: plus.gallery.pick 選擇圖片沒有進入回調 HTML5 API Reference 在 聯想小新 平板電腦上選擇相冊圖片進行上傳時, 打開相冊瞬間 應用會自動重啟, 相冊倒是有打開, 不過應用重啟了, 導…

使用正則表達式來判斷一個字符串只是否包含數字

使用正則表達式來判斷一個字符串只是否包含數字 1、第一種 import java.util.regex.Pattern;public class Main {public static void main(String[] args) {String inputString "12345";if (containsOnlyDigits(inputString)) {System.out.println("字符串只…

文件url 轉File

// param url : http://xxx.xxx.xx.jpg public static File getFile(String url) throws Exception {//對本地?件命名String fileName url.substring(url.lastIndexOf("."),url.length());File file null;URL urlfile;InputStream inStream null;OutputStream os…

[原創](免改BIOS)使用Clover升級舊電腦-(高階玩法)讓固態硬盤內置Win11 PE啟動系統

[簡介] 常用網名: 豬頭三 出生日期: 1981.XX.XXQQ: 643439947 個人網站: 80x86匯編小站 https://www.x86asm.org 編程生涯: 2001年~至今[共22年] 職業生涯: 20年 開發語言: C/C、80x86ASM、PHP、Perl、Objective-C、Object Pascal、C#、Python 開發工具: Visual Studio、Delphi…

【算法專題】滑動窗口—無重復字符的最長子串

力扣題目鏈接&#xff1a;無重復字符的最長子串 一、題目解析 二、算法原理 解法一&#xff1a;暴力解法&#xff08;時間復雜度最壞&#xff1a;O(N)&#xff09; 從每一個位置開始往后枚舉&#xff0c;在往后尋找無重復最長子串時&#xff0c;可以利用哈希表來統計字符出現…