Redis 分佈式鎖基礎理論

引言

本文只是對分佈式鎖的一個簡單的理論入門,不夠完善,若是您想學習完整、系統的分佈式鎖,還請莫在本文浪費時間。html

原計劃使用Redis做爲系統緩存提高系統性能,在探究過程當中發現序列化過於耗時,在編輯到一半時將原計劃做廢。java

原計劃

本地測試的時候,構造了大量數據,但MySQL表現卻並不理想。面試

測試數據:262144條,單表查詢。若是平均到每一個教師的出題數來講,二十萬很正常。redis

image.png

分頁查詢接口,每頁十條,查詢第一頁數據,居然花費了驚人的12.09秒,太慢了。shell

決定使用Redis緩存進行查詢優化。數據庫

引入Spring Data Redis,安裝Redis,配置Redis,配置Serializable接口,配置Cacheable註解。緩存

@Cacheable(value = "pageAllByCurrentUser")
public Page<Subject> pageAllByCurrentUser(Pageable pageable, Long courseId, Long modelId, Integer difficult) {
}

測試,首次請求花費了18.80秒,也就是說序列化10條數據花費了6秒,推測Hibernate應該關聯出來了許多數據因此才須要這麼長的時間去序列化。服務器

image.png

整到這直接放棄,這個優化方案不合適。原計劃夭折。併發

分佈式鎖

既然掛在序列化上了,也不可否認Redis的高性能,就講講面試常考的Redis分佈式鎖的理論吧,具體實現去找開源項目一大堆。分佈式

分佈式鎖聽起來挺高大上,其實特別簡單。

image.png

在集羣環境下,多個後臺服務去操做數據庫,若是併發操做,某些場景下會產生問題。

假設數據表以下,又是餘額的例子:

id balance
1 500

餘額500

這是併發都會遇到的問題,下面進行統一描述。

兩個任務:AB併發執行,AB能夠是一臺服務器上的兩個線程,也能夠是集羣下的不一樣服務器中的線程。

A執行取錢,取200

B執行存錢,存200

AB查詢原餘額,取到的都是500,併發執行完加減操做後。

A500 - 200 = 300

B500 + 200 = 700

因此AB將結果寫回數據庫時,不管誰先誰後,最終的結果都不正確。

因此須要經過加鎖的方式來解決,A執行時,加鎖,B再執行時,嘗試獲取鎖失敗,等待,A執行完成,解鎖,B獲取到鎖,執行,解鎖。反之亦然。

單機環境下的加鎖方案請參照美團博客:不可不說的Java「鎖」事 - 美團技術團隊,面試必考,不會不行,學完使不上,兩天就得忘。

集羣環境下的併發問題,就須要分佈式鎖了。

由於集羣環境下,各服務實例互不影響,不共享內存,傳統的像ReentrantLock之類的普通加鎖方式在分佈式環境下無效。

三者經過共同的Redis實例實現加解鎖。

image.png

大體流程以下:

ARedis中寫入「A正在執行用戶1的取錢操做,請其餘操做用戶1的任務等待」。

B看到了Redis中的數據,等待稍候重試,或直接結束使操做失敗。

A執行,執行完成,清除在Redis中寫入的數據。

若是B稍候從新執行,去Redis中查詢,沒有查詢到互相沖突的實例在執行的消息,開始執行。

用專業的話來說,這就是分佈式鎖。

專業描述

ABRedis中執行SETNX命令。

SETNXSET if Not eXist。若是key不存在,進行SET,不然失敗。

redis> SETNX mykey "Hello"
(integer) 1
redis> SETNX mykey "World"
(integer) 0
redis> GET mykey
"Hello"

因此,誰在Redis中執行SETNX成功,就至關於那個服務實例加鎖成功。

加鎖成功的繼續執行,失敗的等待,稍後重試。

執行完成後,將Redis中的key刪除,其餘服務實例便可從新獲取鎖。

爲何 Redis 不會產生併發問題?

由於Redis是單線程的,因此假設AB同時去Redis中加鎖,由於Redis單線程,必然有前後順序,不會出現AB同時加鎖成功的狀況。

以上只是理論,也是最基本最古老的分佈式鎖實現原理,一樣存在諸多問題,Redis宕機了怎麼辦?解鎖失敗的時候怎麼辦?

真不知道去年的咱們是怎麼學會這些東西的。

image.png

總結

吾生也有涯,而知也無涯。

相關文章
相關標籤/搜索