本文只是對分佈式鎖的一個簡單的理論入門,不夠完善,若是您想學習完整、系統的分佈式鎖,還請莫在本文浪費時間。html
原計劃使用Redis
做爲系統緩存提高系統性能,在探究過程當中發現序列化過於耗時,在編輯到一半時將原計劃做廢。java
本地測試的時候,構造了大量數據,但MySQL
表現卻並不理想。面試
測試數據:262144
條,單表查詢。若是平均到每一個教師的出題數來講,二十萬很正常。redis
分頁查詢接口,每頁十條,查詢第一頁數據,居然花費了驚人的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
應該關聯出來了許多數據因此才須要這麼長的時間去序列化。服務器
整到這直接放棄,這個優化方案不合適。原計劃夭折。併發
既然掛在序列化上了,也不可否認Redis
的高性能,就講講面試常考的Redis
分佈式鎖的理論吧,具體實現去找開源項目一大堆。分佈式
分佈式鎖聽起來挺高大上,其實特別簡單。
在集羣環境下,多個後臺服務去操做數據庫,若是併發操做,某些場景下會產生問題。
假設數據表以下,又是餘額的例子:
id | balance |
---|---|
1 | 500 |
餘額500
。
這是併發都會遇到的問題,下面進行統一描述。
兩個任務:A
和B
併發執行,AB
能夠是一臺服務器上的兩個線程,也能夠是集羣下的不一樣服務器中的線程。
A
執行取錢,取200
。
B
執行存錢,存200
。
A
、B
查詢原餘額,取到的都是500
,併發執行完加減操做後。
A
:500 - 200 = 300
B
:500 + 200 = 700
因此A
、B
將結果寫回數據庫時,不管誰先誰後,最終的結果都不正確。
因此須要經過加鎖的方式來解決,A
執行時,加鎖,B
再執行時,嘗試獲取鎖失敗,等待,A
執行完成,解鎖,B
獲取到鎖,執行,解鎖。反之亦然。
單機環境下的加鎖方案請參照美團博客:不可不說的Java「鎖」事 - 美團技術團隊,面試必考,不會不行,學完使不上,兩天就得忘。
集羣環境下的併發問題,就須要分佈式鎖了。
由於集羣環境下,各服務實例互不影響,不共享內存,傳統的像ReentrantLock
之類的普通加鎖方式在分佈式環境下無效。
三者經過共同的Redis
實例實現加解鎖。
大體流程以下:
A
在Redis
中寫入「A
正在執行用戶1
的取錢操做,請其餘操做用戶1
的任務等待」。
B
看到了Redis
中的數據,等待稍候重試,或直接結束使操做失敗。
A
執行,執行完成,清除在Redis
中寫入的數據。
若是B
稍候從新執行,去Redis
中查詢,沒有查詢到互相沖突的實例在執行的消息,開始執行。
用專業的話來說,這就是分佈式鎖。
專業描述
A
、B
在Redis
中執行SETNX
命令。
SETNX
:SET 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
是單線程的,因此假設A
和B
同時去Redis
中加鎖,由於Redis
單線程,必然有前後順序,不會出現A
與B
同時加鎖成功的狀況。
以上只是理論,也是最基本最古老的分佈式鎖實現原理,一樣存在諸多問題,Redis
宕機了怎麼辦?解鎖失敗的時候怎麼辦?
真不知道去年的咱們是怎麼學會這些東西的。
吾生也有涯,而知也無涯。