0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

一文讀懂Redis

程序員cxuan ? 來源:說故事的五公子 ? 作者:說故事的五公子 ? 2022-11-01 09:15 ? 次閱讀

Redis 管道

我們通常使用 Redis 的方式是,發(fā)送命令,命令排隊,Redis 執(zhí)行,然后返回結果,這個過程稱為Round trip time(簡稱RTT, 往返時間)。但是如果有多條命令需要執(zhí)行時,需要消耗 N 次 RTT,經過 N 次 IO 傳輸,這樣效率明顯很低。

于是 Redis 管道(pipeline)便產生了,一次請求/響應服務器能實現處理新的請求即使舊的請求還未被響應。這樣就可以將多個命令發(fā)送到服務器,而不用等待回復,最后在一個步驟中讀取該答復。這就是管道(pipelining),減少了 RTT,提升了效率

重要說明: 使用管道發(fā)送命令時,服務器將被迫回復一個隊列答復,占用很多內存。所以,如果你需要發(fā)送大量的命令,最好是把他們按照合理數量分批次的處理,例如10K的命令,讀回復,然后再發(fā)送另一個10k的命令,等等。這樣速度幾乎是相同的,但是在回復這10k命令隊列需要非常大量的內存用來組織返回數據內容。

Redis 發(fā)布訂閱

發(fā)布訂閱是一種消息模式,發(fā)送者(sub)發(fā)送消息,訂閱者(pub)接收消息

155c4a6a-597d-11ed-a3b6-dac502259ad0.png

如上圖所示,發(fā)布訂閱基于頻道實現的,同一個頻道可以有多個訂閱者,多個發(fā)布者。其中任意一個發(fā)布者發(fā)布消息到頻道中,所以訂閱者都可以收到該消息。

發(fā)布訂閱 Redis 演示

我在服務器上啟動了 4 個 Redis 客戶端,2 個訂閱者,2 個發(fā)布者,訂閱者訂閱的頻道為 channel01

1610f636-597d-11ed-a3b6-dac502259ad0.png

第一步:發(fā)布者 1 往 channel01 中發(fā)送消息 "wugongzi"

16516c84-597d-11ed-a3b6-dac502259ad0.png

訂閱者 1 收到消息:

1660bb1c-597d-11ed-a3b6-dac502259ad0.png

訂閱者 2 收到消息:

1679bfa4-597d-11ed-a3b6-dac502259ad0.png

第二步:發(fā)布者 2 往頻道中發(fā)布消息 "hello-redis"

16dea586-597d-11ed-a3b6-dac502259ad0.png

訂閱者 1 收到消息:

16ebca18-597d-11ed-a3b6-dac502259ad0.png

訂閱者 2 收到消息:

16fec4b0-597d-11ed-a3b6-dac502259ad0.png

Redis 同時支持訂閱多個頻道:

173b64b0-597d-11ed-a3b6-dac502259ad0.png

Redis 過期策略

過期時間使用

Redis 可以給每個 key 都設置一個過期時間,過期時間到達后,Redis 會自動刪除這個 key。

實際生產中,我們還是要求必須要為每個 Redis 的 Key 設置一個過期時間,如果不設置過期時間,時間一久,Redis 內存就會滿了,有很多冷數據,依然存在。

設置過期時間的命令:EXPIRE key seconds

在使用過程中有一點需要注意,就是在每次更新 Redis 時,都需要重新設置過期時間,如果不設置,那個 key 就是永久的,下面給大家演示一下如何使用:

17ab60f8-597d-11ed-a3b6-dac502259ad0.png

過期刪除策略

Redis keys過期有兩種方式:被動和主動方式。

當一些客戶端嘗試訪問過期 key 時,Redis 發(fā)現 key 已經過期便刪除掉這些 key

當然,這樣是不夠的,因為有些過期的 keys,可能永遠不會被訪問到。 無論如何,這些 keys 應該過期,所以 Redis 會定時刪除這些 key

具體就是Redis每秒10次做的事情:

測試隨機的20個keys進行相關過期檢測。

刪除所有已經過期的keys。

如果有多于25%的keys過期,重復步奏1

這是一個平凡的概率算法,基本上的假設是,Redis的樣本是這個密鑰控件,并且我們不斷重復過期檢測,直到過期的keys的百分百低于25%,這意味著,在任何給定的時刻,最多會清除1/4的過期keys。

Redis 事務

事務基本使用

Redis 事務可以一次執(zhí)行多條命令,Redis 事務有如下特點:

事務是一個單獨的隔離操作:事務中的所有命令都會序列化、按順序地執(zhí)行。事務在執(zhí)行的過程中,不會被其他客戶端發(fā)送來的命令請求所打斷。

事務是一個原子操作:事務中的命令要么全部被執(zhí)行,要么全部都不執(zhí)行。

Redis 事務通過 MULTI 、EXEC、DISCARD、WATCH 幾個命令來實現,MULTI 命令用于開啟事務,EXEC 用于提交事務,DISCARD 用于放棄事務,WATCH 可以為 Redis 事務提供 check-and-set (CAS)行為。

17c33084-597d-11ed-a3b6-dac502259ad0.png

事務發(fā)生錯誤

Reids 事務發(fā)生錯誤分為兩種情況。

第一種:事務提交前發(fā)生錯誤,也就是在發(fā)送命令過程中發(fā)生錯誤,看演示

17d2ca44-597d-11ed-a3b6-dac502259ad0.png

上面我故意將 incr 命令寫錯,從結果我們可以看到,這條 incr 沒有入隊,并且事務執(zhí)行失敗,k1 和 k2 都沒有值

第二種:事務提交后發(fā)生錯誤,也就是在執(zhí)行命令過程中發(fā)生錯誤,看演示

18021e66-597d-11ed-a3b6-dac502259ad0.png

上面的事務命令中,我給 k1 設置了一個 d,然后執(zhí)行自增命令,最后獲取 k1 的值,我們發(fā)現第二條命令執(zhí)行發(fā)生了錯誤,但是整個事務依然提交成功了,從上面現象中可以得出,Redis 事務不支持回滾操作。如果支持的話,整個事務的命令都不應該被執(zhí)行。

為什么 Redis 不支持回滾

如果你有使用關系式數據庫的經驗, 那么 “Redis 在事務失敗時不進行回滾,而是繼續(xù)執(zhí)行余下的命令”這種做法可能會讓你覺得有點奇怪。

以下是這種做法的優(yōu)點:

Redis 命令只會因為錯誤的語法而失敗(并且這些問題不能在入隊時發(fā)現),或是命令用在了錯誤類型的鍵上面:這也就是說,從實用性的角度來說,失敗的命令是由編程錯誤造成的,而這些錯誤應該在開發(fā)的過程中被發(fā)現,而不應該出現在生產環(huán)境中。

因為不需要對回滾進行支持,所以 Redis 的內部可以保持簡單且快速。

有種觀點認為 Redis 處理事務的做法會產生 bug , 然而需要注意的是, 在通常情況下, 回滾并不能解決編程錯誤帶來的問題。 舉個例子, 如果你本來想通過 incr 命令將鍵的值加上 1 , 卻不小心加上了 2 , 又或者對錯誤類型的鍵執(zhí)行了 incr , 回滾是沒有辦法處理這些情況的。

放棄事務

當執(zhí)行 discard 命令時, 事務會被放棄, 事務隊列會被清空, 并且客戶端會從事務狀態(tài)中退出

186342a4-597d-11ed-a3b6-dac502259ad0.png

WATCH 命令使用

watch 使得 exec 命令需要有條件地執(zhí)行: 事務只能在所有被監(jiān)視鍵都沒有被修改的前提下執(zhí)行, 如果這個前提不能滿足的話,事務就不會被執(zhí)行

18941d52-597d-11ed-a3b6-dac502259ad0.png

上面我用 watch 命令監(jiān)聽了 k1 和 k2,然后開啟事務,在事務提交之前,k1 的值被修改了,watch 監(jiān)聽到 k1 值被修改,所以事務沒有被提交。

Redis 腳本和事務

從定義上來說, Redis 中的腳本本身就是一種事務, 所以任何在事務里可以完成的事, 在腳本里面也能完成。 并且一般來說, 使用腳本要來得更簡單,并且速度更快。

因為腳本功能是 Redis 2.6 才引入的, 而事務功能則更早之前就存在了, 所以 Redis 才會同時存在兩種處理事務的方法。

Reids 持久化

為什么需要持久化

我們知道 Redis 是內存數據庫,主打高性能,速度快。相比 Redis 而言,MySQL 的數據則是保存再硬盤中(其實也有內存版的 MySQL 數據庫,但是價格極其昂貴,一般公司不會使用),速度慢,但是穩(wěn)定性好。你想想 Redis 數據保存在內存中,一旦服務器宕機了,數據豈不是全部都沒了,這將會出現很大問題。所以 Redis 為了彌補這一缺陷,提供數據持久化機制,即使服務器宕機,依然可以保證數據不丟失。

持久化簡介

Redis 提供了兩種持久化機制 RDB 和 AOF,適用于不同場景

RDB持久化方式能夠在指定的時間間隔能對你的數據進行快照存儲

AOF持久化方式記錄每次對服務器寫的操作,當服務器重啟的時候會重新執(zhí)行這些命令來恢復原始的數據,AOF命令以redis協(xié)議追加保存每次寫的操作到文件末尾.Redis還能對AOF文件進行后臺重寫,使得AOF文件的體積不至于過大

RDB

RDB 持久化是通過在指定時間間隔對數據進行快照,比如在 8 點鐘對數據進行持久化,那么 Redis 會 fork 一個子進程將 8 點那一刻內存中的數據持久化到磁盤上。觸發(fā) RDB 持久化有以下幾種方法

RDB 持久化方式

1、執(zhí)行 save 命令

執(zhí)行 save 命令進行持久會阻塞 Redis,備份期間 Redis 無法對外提供服務,一般不建議使用,使用場景為 Redis 服務器需要停機維護的情況下。

2、執(zhí)行 bgsave 命令

bgsave 命令不會阻塞 Redis 主進程,持久化期間 Redis 依然可以正常對外提供服務

3、通過配置文件中配置的 save 規(guī)則來觸發(fā)

18ad90c0-597d-11ed-a3b6-dac502259ad0.png

save 900 1:900s 內有 1 個 key 發(fā)生變化,則觸發(fā) RDB 快照

save 300 10:300s 內有 10 個 key 發(fā)生變化,則觸發(fā) RDB 快照

save 60 10000:60s 內有 10000 個 key 發(fā)生變化(新增、修改、刪除),則觸發(fā) RDB 快照

save "":該配置表示關閉 RDB 持久化

RDB 持久化原理

Redis 進行 RDB 時,會 fork 一個子進程來進行數據持久化,這樣不妨礙 Redis 繼續(xù)對外提供服務,提高效率。

曾經面試官出過這樣面試題:

假如 Redis 在 8 點觸發(fā)了 RDB 持久化,持久化用時 2 分鐘,在持久化期間,Redis 中有 100 個 key 被修改了,那么 RDB 文件中的 key 是 8 點那一刻的數據,還是變化的呢?

先不要看答案,自己思考 1 分鐘,一個問題只有你自己思考了,才能印象深刻。

好,下面我們一起來看下這張圖:

18b90590-597d-11ed-a3b6-dac502259ad0.png

從圖中我們可以清晰的看到,Redis 備份時,fork 了一個子進程,子進程去做持久化的工作,子進程中的 key 指向了 8 點那一刻的數據,后面 k1 的值修改了,redis 會在內存中創(chuàng)建一個新的值,然后主進程 k1 指針指向新的值,子進程 k1 指針依然指向 19,這樣 Redis 持久化的就是 8 點那一刻的數據,不會發(fā)生變化。同時,從圖中我們也可以看到,Redis 持久化時并不是將內存中數據全部拷貝一份進行備份。

RDB 優(yōu)缺點

優(yōu)點

RDB是一個非常緊湊的文件,它保存了某個時間點得數據集,非常適用于數據集的備份,比如你可以在每個小時報保存一下過去24小時內的數據,同時每天保存過去30天的數據,這樣即使出了問題你也可以根據需求恢復到不同版本的數據集

RDB在保存RDB文件時父進程唯一需要做的就是fork出一個子進程,接下來的工作全部由子進程來做,父進程不需要再做其他IO操作,所以RDB持久化方式可以最大化redis的性能

與AOF相比,在恢復大的數據集的時候,RDB方式會更快一些

缺點

如果備份間隔時間較長,RDB 會丟失較多的數據。比如 8 點備份一次,8 點半服務器宕機,那么這半小時內的數據就會丟失了

AOF

AOF 持久化是通過日志的方式,記錄每次 Redis 的寫操作。當服務器重啟的時候會重新執(zhí)行這些命令來恢復原始的數據,AOF 命令以Redis 協(xié)議追加保存每次寫的操作到文件末尾。Redis 還能對 AOF 文件進行后臺重寫,使得 AOF 文件的體積不至于過大

AOF 持久化配置

#是否開啟aofno:關閉;yes:開啟
appendonlyno

#aof文件名
appendfilename"appendonly.aof"

#aof同步策略
#appendfsyncalways#每個命令都寫入磁盤,性能較差
appendfsynceverysec#每秒寫一次磁盤,Redis默認配置
#appendfsyncno#由操作系統(tǒng)執(zhí)行,默認Linux配置最多丟失30秒

#aof重寫期間是否同步
no-appendfsync-on-rewriteno

#重寫觸發(fā)策略
auto-aof-rewrite-percentage100#觸發(fā)重寫百分比(指定百分比為0,將禁用aof自動重寫功能)
auto-aof-rewrite-min-size64mb#觸發(fā)自動重寫的最低文件體積(小于64mb不自動重寫)

#加載aof時如果有錯如何處理
#如果該配置啟用,在加載時發(fā)現aof尾部不正確是,會向客戶端寫入一個log,但是會繼續(xù)執(zhí)行,如果設置為no,發(fā)現錯誤就會停止,必須修復后才能重新加載。
aof-load-truncatedyes

#aof中是否使用rdb
#開啟該選項,觸發(fā)AOF重寫將不再是根據當前內容生成寫命令。而是先生成RDB文件寫到開頭,再將RDB生成期間的發(fā)生的增量寫命令附加到文件末尾。
aof-use-rdb-preambleyes

AOF 文件寫入

aof 文件是命令追加的方式,先將命令寫入緩沖區(qū),時間到了再寫如磁盤中

appendfsyncalways#每個命令都寫入磁盤,性能較差
appendfsynceverysec#每秒寫一次磁盤,Redis默認配置
appendfsyncno#由操作系統(tǒng)執(zhí)行,默認Linux配置最多丟失30秒

上面配置就是何時寫入磁盤中

AOF 重寫

aof 文件雖然丟失的數據少,但是隨著時間的增加,aof 文件體積越來越大,占用磁盤空間越來越大,恢復時間長。所以 redis 會對 aof 文件進行重寫,以減少 aof 文件體積

下面以一個例子說明

--重寫前的aof
setk120
setk240
setk135
setk334
setk219

--這里k1最終的值為35,k2最終值為19,所以不需要寫入兩個命令
--重寫后
setk135
setk334
setk219

混合持久化

從 Redis 4.0 版本開始,引入了混合持久化機制,純AOF方式、RDB+AOF方式,這一策略由配置參數aof-use-rdb-preamble(使用RDB作為AOF文件的前半段)控制,默認關閉(no),設置為yes可開啟

no:按照AOF格式寫入命令,與4.0前版本無差別;

yes:先按照RDB格式寫入數據狀態(tài),然后把重寫期間AOF緩沖區(qū)的內容以AOF格式寫入,文件前半部分為RDB格式,后半部分為AOF格式。

混合持久化優(yōu)點如下:

大大減少了 aof 文件體積

加快了 aof 文件恢復速度,前面是 rdb ,恢復速度快

AOF 數據恢復

第一種:純 AOF

恢復時,取出 AOF 中命令,一條條執(zhí)行恢復

第二種:RDB+AOF

先執(zhí)行 RDB 加載流程,執(zhí)行完畢后,再取出余下命令,開始一條條執(zhí)行

AOF 優(yōu)缺點

優(yōu)點

AOF 實時性更好,丟失數據更少

AOF 已經支持混合持久化,文件大小可以有效控制,并提高了數據加載時的效率

缺點

對于相同的數據集合,AOF 文件通常會比 RDB 文件大

在特定的 fsync 策略下,AOF 會比 RDB 略慢

AOF 恢復速度比 RDB 慢

Redis 分布式鎖

分布式鎖介紹

學習過 Java 的同學,應該對鎖都不陌生。Java 中多個線程訪問共享資源時,會出現并發(fā)問題,我們通常利用 synchronized 或者 Lock 鎖來解決多線程并發(fā)訪問從而出現的安全問題。細心的同學可能已經發(fā)現了, synchronized 或者 Lock 鎖解決線程安全問題在單節(jié)點情況下是可行的,但是如果服務部署在多臺服務器上,本地鎖就失效了。

分布式場景下,需要采用新的解決方案,就是今天要說的 Redis 分布式鎖。日常業(yè)務中,類似搶紅包,秒殺等場景都可以使用 Redis 分布式鎖來解決并發(fā)問題。

分布式鎖特點

分布式在保障安全、高可用的情況下需要具備以下特性

互斥性:任意一個時刻,只能有一個客戶端獲取到鎖

安全性:鎖只能被持久的客戶端刪除,不能被其他人刪除

高可用,高性能:加鎖和解鎖消耗的性能少,時間短

鎖超時:當客戶端獲取鎖后出現故障,沒有立即釋放鎖,該鎖要能夠在一定時間內釋放,否則其他客戶端無法獲取到鎖

可重入性:客戶端獲取到鎖后,在持久鎖期間可以再次獲取到該鎖

解決方案

方案一:SETNX 命令

Redis 提供了一個獲取分布式鎖的命令 SETNX

setnxkeyvalue

如果獲取鎖成功,redis 返回 1,獲取鎖失敗 redis 返回 0

18d5840e-597d-11ed-a3b6-dac502259ad0.png

客戶端使用偽代碼

if(setnx(k1,v1)==1){
try{
//執(zhí)行邏輯
....
}catch(){

}finally{
//執(zhí)行完成后釋放鎖
delk1;
}
}

這個命令看似可以達到我們的目的,但是不符合分布式鎖的特性,如果客戶端在執(zhí)行業(yè)務邏輯過程中,服務器宕機了,finally 中代碼還沒來得及執(zhí)行,鎖沒有釋放,也就意味其他客戶端永遠無法獲取到這個鎖

方案二:SETNX + EXPIRE

該方案獲取鎖之后,立即給鎖加上一個過期時間,這樣即使客戶端沒有手動釋放鎖,鎖到期后也會自動釋放

18e905f6-597d-11ed-a3b6-dac502259ad0.png

我們來看下偽代碼

if(setnx(k1,v1)==1){
expire(key,10);
try{
//....你的業(yè)務邏輯
}finally{
del(key);
}
}

這個方案很完美,既可以獲取到,又不用擔心客戶端宕機。等等,這里面真的沒有問題嗎?再仔細瞅瞅,一瞅就瞅出問題來了

if(setnx(k1,v1)==1){
//再剛獲取鎖之后,想要給鎖設置過期時間,此時服務器掛了
expire(key,10);//這條命令沒有執(zhí)行
try{
//....你的業(yè)務邏輯
}finally{
del(key);
}
}

這里的 setnx 命令和 expire 命令不是原子性的,他們之間執(zhí)行需要一定的等等時間,雖然這個時間很短,但是依然有極小概率出現問題

使用 Lua 腳本

既然 setnx 和 expire 兩個命令非原子性,那么我們讓其符合原子性即可,通過 Lua 腳本即可實現。Redis 使用單個 Lua 解釋器去運行所有腳本,并且, Redis 也保證腳本會以原子性(atomic)的方式執(zhí)行: 當某個腳本正在運行的時候,不會有其他腳本或 Redis 命令被執(zhí)行

具體實現如下:

ifredis.call('setnx',KEYS[1],ARGV[1])==1then
redis.call('expire',KEYS[1],ARGV[2])
else
return0
end;

這樣應該沒問題了吧,看似上面的幾個問題都很好解決了。不對,再想想,肯定還有沒考慮到的

我們再來看一段偽代碼

//執(zhí)行l(wèi)ua腳本
//獲取k1鎖,過期時間10s
if(execlua()==1){
try{
buyGoods();
}finally{
del(key);
}
}
18fbe5cc-597d-11ed-a3b6-dac502259ad0.png

從圖中我們可以很清晰發(fā)現問題所在

客戶端 A 還未執(zhí)行完畢,客戶端 B 就獲取到了鎖,這樣就可能導致并發(fā)問題

客戶端 A 執(zhí)行完畢,開始刪除鎖。但此時的鎖為 B 所有,相當于刪除了屬于客戶端 B 的鎖,這樣肯定會發(fā)生問題

方案四:SET EX PX NX + 校驗唯一隨機值,再刪除

既然鎖有可能被別的客戶端刪除,那么在刪除鎖的時候我們加上一層校驗,判斷釋放鎖是當前客戶端持有的,如果是當前客戶端,則允許刪除,否則不允許刪除。

EX second :設置鍵的過期時間為 second 秒。 SET key value EX second 效果等同于 SETEX key second value 。

PX millisecond :設置鍵的過期時間為 millisecond 毫秒。 SET key value PX millisecond 效果等同于 PSETEX key millisecond value 。

NX :只在鍵不存在時,才對鍵進行設置操作。 SET key value NX 效果等同于 SETNX key value 。

XX :只在鍵已經存在時,才對鍵進行設置操作。

使用示例:

if(jedis.set(resource_name,random_value,"NX","EX",100s)==1){//加鎖,value傳入一個隨機數
try{
dosomething//業(yè)務處理
}catch(){
}
finally{
//判斷value是否相等,相等才釋放鎖,這里判斷和刪除是非原子性,真實場景下可以將這兩步放入Lua腳本中執(zhí)行
if(random_value.equals(jedis.get(resource_name))){
jedis.del(lockKey);//釋放鎖
}
}
}

Lua 腳本如下:

ifredis.call("get",KEYS[1])==ARGV[1]then
returnredis.call("del",KEYS[1])
else
return0
end

此方案解決了鎖被其他客戶端解除的問題,但是依然沒有解決鎖過期釋放,但是業(yè)務還沒有執(zhí)行完成的問題

Redisson框架

方案四中并沒有解決方法未執(zhí)行完成,鎖就超時釋放的問題。這里有個方案大家比較容易想到,那就是鎖的超時時間設置長一點,比如2min,一個接口執(zhí)行時間總不能比 2 min 還長,那你就等著領盒飯吧,哈哈哈。但是這么做,一來是不能每個鎖都設置這么久超時時間,二來是如果接口出現異常了,鎖只能 2 min 后才能釋放,其他客戶端等待時間較長。

這個問題早就有人想到了,并給出了解決方案,開源框架 Redisson 解決了這個問題。

19249486-597d-11ed-a3b6-dac502259ad0.png

Redisson 在方法執(zhí)行期間,會不斷的檢測鎖是否到期,如果發(fā)現鎖快要到期,但是方法還沒有執(zhí)行完成,便會延長鎖的過期時間,從而解決了鎖超時釋放問題。

Redlock

上面所介紹的分布式鎖,都是在單臺 Redis 服務器下的解決方案。真實的生產環(huán)境中,我們通常會部署多臺 Redis 服務器,也就是集群模式,這種情況上述解決方案就失效了。

對于集群 Redis,Redis 的作者 antirez 提出了另一種解決方案,Redlock 算法

Redlock 算法大致流程如下:

19554b58-597d-11ed-a3b6-dac502259ad0.png

1、獲取當前Unix時間,以毫秒為單位。

2、依次嘗試從N個實例,使用相同的key和隨機值獲取鎖。在步驟2,當向Redis設置鎖時,客戶端應該設置一個網絡連接和響應超時時間,這個超時時間應該小于鎖的失效時間。例如你的鎖自動失效時間為10秒,則超時時間應該在5-50毫秒之間。這樣可以避免服務器端Redis已經掛掉的情況下,客戶端還在死死地等待響應結果。如果服務器端沒有在規(guī)定時間內響應,客戶端應該盡快嘗試另外一個Redis實例。

3、客戶端使用當前時間減去開始獲取鎖時間(步驟1記錄的時間)就得到獲取鎖使用的時間。當且僅當從大多數(這里是3個節(jié)點)的Redis節(jié)點都取到鎖,并且使用的時間小于鎖失效時間時,鎖才算獲取成功。

4、如果取到了鎖,key的真正有效時間等于有效時間減去獲取鎖所使用的時間(步驟3計算的結果)。

5、如果因為某些原因,獲取鎖失敗(沒有在至少N/2+1個Redis實例取到鎖或者取鎖時間已經超過了有效時間),客戶端應該在所有的Redis實例上進行解鎖(即便某些Redis實例根本就沒有加鎖成功)。

總結: 簡單總結一下就是客戶端向 Redis 集群中所有服務器發(fā)送獲取鎖的請求,只有半數以上的鎖獲取成功后,才代表鎖獲取成功,否則鎖獲取失敗。

Redis 集群

Redis 集群的三種模式

在生產環(huán)境中,我們使用 Redis 通常采用集群模式,因為單機版 Redis 穩(wěn)定性可靠性較低,而且存儲空間有限。

Redis 支持三種集群模式

主從復制

哨兵模式

Cluster 模式

主從復制

主從復制概念

主從復制模式,有一個主,多個從,從而實現讀寫分離。主機負責寫請求,從機負責讀請求,減輕主機壓力

19651e52-597d-11ed-a3b6-dac502259ad0.png

主從復制原理

197268aa-597d-11ed-a3b6-dac502259ad0.png

從數據庫啟動成功后,連接主數據庫,發(fā)送 SYNC 命令;

主數據庫接收到 SYNC 命令后,開始執(zhí)行 BGSAVE 命令生成 RDB 文件并使用緩沖區(qū)記錄此后執(zhí)行的所有寫命令;

主數據庫 BGSAVE 執(zhí)行完后,向所有從數據庫發(fā)送快照文件,并在發(fā)送期間繼續(xù)記錄被執(zhí)行的寫命令;

從數據庫收到快照文件后丟棄所有舊數據,載入收到的快照;

主數據庫快照發(fā)送完畢后開始向從數據庫發(fā)送緩沖區(qū)中的寫命令;

從數據庫完成對快照的載入,開始接收命令請求,并執(zhí)行來自主數據庫緩沖區(qū)的寫命令;(從數據庫初始化完成

主數據庫每執(zhí)行一個寫命令就會向從數據庫發(fā)送相同的寫命令,從數據庫接收并執(zhí)行收到的寫命令(從數據庫初始化完成后的操作

出現斷開重連后,2.8之后的版本會將斷線期間的命令傳給重數據庫,增量復制。

主從剛剛連接的時候,進行全量同步;全同步結束后,進行增量同步。當然,如果有需要,slave 在任何時候都可以發(fā)起全量同步。Redis 的策略是,無論如何,首先會嘗試進行增量同步,如不成功,要求從機進行全量同步。

主從復制優(yōu)缺點

優(yōu)點

支持主從復制,主機會自動將數據同步到從機,可以進行讀寫分離

Slave 同樣可以接受其它 Slaves 的連接和同步請求,這樣可以有效的分載 Master 的同步壓力

Master Server 是以非阻塞的方式為 Slaves 提供服務。所以在 Master-Slave 同步期間,客戶端仍然可以提交查詢或修改請求

缺點

主從不具備容錯和恢復能力,一旦主機掛了,那么整個集群處理可讀狀態(tài),無法處理寫請求,會丟失數據

主機宕機后無法自動恢復,只能人工手動恢復

集群存儲容量有限,容量上線就是主庫的內存的大小,無法存儲更多內容

###哨兵集群

哨兵概念

哨兵,我們經常在電視劇中看到一些放哨的,哨兵的作用和這些放哨的差不多,起到監(jiān)控作用。一旦 Redis 集群出現問題了,哨兵會立即做出相應動作,應對異常情況。

哨兵模式是基于主從復制模式上搭建的,因為主從復制模式情況下主服務器宕機,會導致整個集群不可用,需要人工干預,所以哨兵模式在主從復制模式下引入了哨兵來監(jiān)控整個集群,哨兵模式架構圖如下:

198f12a2-597d-11ed-a3b6-dac502259ad0.png

哨兵功能

監(jiān)控(Monitoring):哨兵會不斷地檢查主節(jié)點和從節(jié)點是否運作正常。

自動故障轉移(Automatic failover):當主節(jié)點不能正常工作時,哨兵會開始自動故障轉移操作,它會將失效主節(jié)點的其中一個從節(jié)點升級為新的主節(jié)點,并讓其他從節(jié)點改為復制新的主節(jié)點。

配置提供者(Configuration provider):客戶端在初始化時,通過連接哨兵來獲得當前Redis服務的主節(jié)點地址。

通知(Notification):哨兵可以將故障轉移的結果發(fā)送給客戶端。

下線判斷

Redis 下線分為主觀下線和客觀下線兩種

主觀下線:單臺哨兵任務主庫處于不可用狀態(tài)

客觀下線:整個哨兵集群半數以上的哨兵都認為主庫處于可不用狀態(tài)

哨兵集群中任意一臺服務器判斷主庫不可用時,此時會發(fā)送命令給哨兵集群中的其他服務器確認,其他服務器收到命令后會確認主庫的狀態(tài),如果不可用,返回 YES,可用則返回 NO,當有半數的服務器都返回 YES,說明主庫真的不可用,此時需要重新選舉

19a473cc-597d-11ed-a3b6-dac502259ad0.jpg

主庫選舉

當哨兵集群判定主庫下線了,此時需要重新選舉出一個新的主庫對外提供服務。那么該由哪個哨兵來完成這個新庫選舉和切換的動作呢?

注意:這里不能讓每個哨兵都去選舉,可能會出現每個哨兵選舉出的新主庫都不同,這樣就沒法判定,所以需要派出一個代表

哨兵代表選擇

哨兵的選舉機制其實很簡單,就是一個Raft選舉算法: 選舉的票數大于等于num(sentinels)/2+1時,將成為領導者,如果沒有超過,繼續(xù)選舉

任何一個想成為 Leader 的哨兵,要滿足兩個條件:

第一,拿到半數以上的贊成票;

第二,拿到的票數同時還需要大于等于哨兵配置文件中的 quorum 值。

以 3 個哨兵為例,假設此時的 quorum 設置為 2,那么,任何一個想成為 Leader 的哨兵只要拿到 2 張贊成票,就可以了。

新庫選擇

上面已經選舉出了哨兵代表,此時代表需要完成新主庫的選擇,新庫的選擇需要滿足以下幾個標準

新庫需要處于健康狀態(tài),也就是和哨兵之間保持正常的網絡連接

選擇salve-priority從節(jié)點優(yōu)先級最高(redis.conf)的

選擇復制偏移量最大,只復制最完整的從節(jié)點

故障轉移

上面一小節(jié)哨兵已經選舉出了新的主庫,故障轉移要實現新老主庫之間的切換

故障轉移流程如下:

19b60736-597d-11ed-a3b6-dac502259ad0.png

哨兵模式優(yōu)缺點

優(yōu)點

實現了集群的監(jiān)控,故障轉移,實現了高可用

擁有主從復制模式的所有優(yōu)點

缺點

集群存儲容量有限,容量上線就是主庫的內存的大小,無法存儲更多內容

Cluser 集群

Redis 的哨兵模式實現了高可用了,但是每臺 Redis 服務器上存儲的都是相同的數據,浪費內存,而且很難實現容量上的擴展。所以在 redis3.0上加入了 Cluster 集群模式,實現了 Redis 的分布式存儲,也就是說每臺 Redis 節(jié)點上存儲不同的內容。

Redis 集群的數據分片

Redis 集群沒有使用一致性hash, 而是引入了 哈希槽的概念.

Redis 集群有16384個哈希槽,每個key通過CRC16校驗后對16384取模來決定放置哪個槽.集群的每個節(jié)點負責一部分hash槽,舉個例子,比如當前集群有3個節(jié)點,那么:

節(jié)點 A 包含 0 到 5500號哈希槽.

節(jié)點 B 包含5501 到 11000 號哈希槽.

節(jié)點 C 包含11001 到 16384號哈希槽.

這種結構很容易添加或者刪除節(jié)點. 比如如果我想新添加個節(jié)點D, 我需要從節(jié)點 A, B, C中得部分槽到D上. 如果我想移除節(jié)點A,需要將A中的槽移到B和C節(jié)點上,然后將沒有任何槽的A節(jié)點從集群中移除即可. 由于從一個節(jié)點將哈希槽移動到另一個節(jié)點并不會停止服務,所以無論添加刪除或者改變某個節(jié)點的哈希槽的數量都不會造成集群不可用的狀態(tài).

Redis 集群實戰(zhàn)

環(huán)境:

Vmware 虛擬機

CentOS 7

Redis 6.0.6

因為我是在本機上演示的,所以用的虛擬機

主從復制

集群信息如下:

節(jié)點 配置文件 端口
master redis6379.conf 6379
slave1 redis6380.conf 6380
slave1 redis6381.conf 6380

第一步:準備三個 redis.conf 配置文件,配置文件信息如下

#redis6379.confmaster
#包含命令,有點復用的意思
include/soft/redis6.0.6/bin/redis.conf
pidfileredis_6379.pid
port6379
dbfilenamedump6379.rdb
logfile"redis-6379.log"

#redis6380.confslave1
include/soft/redis6.0.6/bin/redis.conf
pidfileredis_6380.pid
port6380
dbfilenamedump6380.rdb
logfile"redis-6380.log"
#最后一行設置了主節(jié)點的ip端口
replicaof127.0.0.16379

#redis6381.confslave2
include/soft/redis6.0.6/bin/redis.conf
pidfileredis_6381.pid
port6381
dbfilenamedump6381.rdb
logfile"redis-6381.log"
#最后一行設置了主節(jié)點的ip端口
replicaof127.0.0.16379

##注意redis.conf要調整一項,設置后臺運行,對咱們操作比較友好
daemonizeyes

第二步:啟動服務器

--首先啟動6379這臺服務器,因為他是主庫(啟動命令在redis安裝目錄的bin目錄下)
../bin/redis-serverredis6379.conf
--接口啟動6380和6381
../bin/redis-serverredis6380.conf
../bin/redis-serverredis6381.conf

第三步:用客戶端連接服務器

cdbin
redis-cli-p6379
redis-cli-p6380
redis-cli-p6381

這里我開了三個窗口分別連接三臺redis服務器,方便查看

在 6379 客戶端輸入命令: info replication 可用查看集群信息

19cc62a6-597d-11ed-a3b6-dac502259ad0.png

第四步:數據同步

現在集群已經搭建好了,我們在 6379 服務器寫入幾條數據,看下可不可以同步到 6380 和 6381

6379:

1a149f30-597d-11ed-a3b6-dac502259ad0.png

6380:

1a33ed90-597d-11ed-a3b6-dac502259ad0.png

6381:

1a50c2bc-597d-11ed-a3b6-dac502259ad0.png

從圖中可用看出,數據已經成功同步了

哨兵模式

哨兵集群是在主從復制的基礎上構建的,相當于是主從+哨兵

搭建哨兵模式分為兩步:

搭建主從復制集群

添加哨兵配置

哨兵模式節(jié)點信息如下,一主二從,三個哨兵組成一個哨兵集群

節(jié)點 配置 端口
master redis6379.conf 6379
slave1 redis6380.conf 6380
slave2 redis6381.conf 6381
sentinel1 sentinel1.conf 26379
sentinel2 sentinel2.conf 26380
sentinel3 sentinel3.conf 26381

主從復制集群的配置同上,這里就不再贅述,下面主要介紹下哨兵的配置,哨兵的配置文件其實非常簡單

#文件內容
#sentinel1.conf
port26379
sentinelmonitormymaster127.0.0.163791
#sentinel2.conf
port26380
sentinelmonitormymaster127.0.0.163791
#sentinel3.conf
port26381
sentinelmonitormymaster127.0.0.163791

配置文件創(chuàng)建好了以后就可以啟動了,首先啟動主從服務器,然后啟動哨兵

../bin/redis-serverredis6379.conf
../bin/redis-serverredis6380.conf
../bin/redis-serverredis6381.conf

--啟動哨兵
../bin/redis-sentinelsentinel1.conf
../bin/redis-sentinelsentinel2.conf
../bin/redis-sentinelsentinel3.conf
1a5f02a0-597d-11ed-a3b6-dac502259ad0.png

從哨兵的啟動日志中我們可用看到主從服務器的信息,以及其他哨兵節(jié)點的信息

故障轉移

主從同步功能上面已經演示過了,這里主要測試一下哨兵的故障轉移

現在我手動將主節(jié)點停掉,在 6379 上執(zhí)行 shutdown 命令

此時我們觀察一下哨兵的頁面:

1a9f662e-597d-11ed-a3b6-dac502259ad0.png

哨兵檢測到了 6379 下線,然后選舉出了新的主庫 6380

此時我們通過 info replication 命令查看集群信息,發(fā)現 6380 已經是主庫了,他有一個從節(jié)點 6381

1d4e1082-597d-11ed-a3b6-dac502259ad0.png

現在我手動將 6379 啟動,看下 6379 會不會重新變成主庫

1d71128a-597d-11ed-a3b6-dac502259ad0.png

重新啟動后,我們發(fā)現 6379 變成了 80 的從庫

Cluser 集群

官方推薦,Cluser 集群至少要部署 3 臺以上的 master 節(jié)點,最好使用 3 主 3 從

節(jié)點 配置 端口
cluster-master1 redis7001.conf 7001
cluster-master2 redis7002.conf 7002
cluster-master3 redis7003.conf 7003
cluster-slave1 redis7004.conf 7004
cluster-slave2 redis7006.conf 7005
cluster-slave3 redis7006.conf 7006

配置文件內容如下,6 個配置文件信息基本相同,編輯好一份后其他文件直接復制修改端口即可

#端口
port7001
#啟用集群模式
cluster-enabledyes
#根據你啟用的節(jié)點來命名,最好和端口保持一致,這個是用來保存其他節(jié)點的名稱,狀態(tài)等信息的
cluster-config-filenodes_7001.conf
#超時時間
cluster-node-timeout5000
appendonlyyes
#后臺運行
daemonizeyes
#非保護模式
protected-modeno
pidfileredis_7001.pid

然后分別啟動 6 個節(jié)點

../bin/redis-serverredis7001.conf
../bin/redis-serverredis7002.conf
../bin/redis-serverredis7003.conf
../bin/redis-serverredis7004.conf
../bin/redis-serverredis7005.conf
../bin/redis-serverredis7006.conf

啟動集群

#執(zhí)行命令
#--cluster-replicas1命令的意思是創(chuàng)建master的時候同時創(chuàng)建一個slave

$redis-cli--clustercreate127.0.0.1:7001127.0.0.1:7002127.0.0.1:7003127.0.0.1:7004127.0.0.1:7005127.0.0.1:7006--cluster-replicas1

啟動過程有個地方需要輸入 yes 確認:

1d992810-597d-11ed-a3b6-dac502259ad0.png

啟動成功后可用看到控制臺輸出結果:

1db54702-597d-11ed-a3b6-dac502259ad0.png

3 個 master 節(jié)點,3 個 slave 節(jié)點,

master[0]槽位:0-5460

master[1]槽位:5461-10922

master[2]槽位:10923-16383

數據驗證

連接 7001 服務器

redis-cli -p 7001 -c 集群模式下需要加上 -c 參數

1e3726e6-597d-11ed-a3b6-dac502259ad0.png

從圖中可用看出,k1 被放到 7003 主機上了,我們此時獲取 k1 ,可用正常獲取到

登錄 7003 也可以正常拿到數據

1e48ac7c-597d-11ed-a3b6-dac502259ad0.png

Redis 緩存問題

在服務端中,數據庫通常是業(yè)務上的瓶頸,為了提高并發(fā)量和響應速度,我們通常會采用 Redis 來作為緩存,讓盡量多的數據走 Redis 查詢,不直接訪問數據庫。同時 Redis 在使用過程中也會出現各種各樣的問題,面對這些問題我們該如何處理?

緩存穿透

緩存擊穿

緩存雪崩

緩存污染

緩存穿透

1、定義:

緩存穿透是指,當緩存和數據中都沒有對應記錄,但是客戶端卻一直在查詢。比如黑客攻擊系統(tǒng),不斷的去查詢系統(tǒng)中不存在的用戶,查詢時先走緩存,緩存中沒有,再去查數據庫;或者電商系統(tǒng)中,用戶搜索某類商品,但是這類商品再系統(tǒng)中根本不存在,這次的搜索應該直接返回空

2、解決方案

網關層增加校驗,進行用戶鑒權,黑名單控制,接口流量控制

對于同一類查詢,如果緩存和數據庫都沒有獲取到數據,那么可用用一個空緩存記錄下來,過期時間 60s,下次遇到同類查詢,直接取出緩存中的空數據返回即可

使用布隆過濾器,布隆過濾器可以用來判斷某個元素是否存在于集合中,利用布隆過濾器可以過濾掉一大部分無效請求

緩存擊穿

1、定義:

緩存擊穿是指,緩存中數據失效,在高并發(fā)情況下,所有用戶的請求全部都打到數據庫上,短時間造成數據庫壓力過大

2、解決方案:

接口限流、熔斷

加鎖,當第一個用戶請求到時,如果緩存中沒有,其他用戶的請求先鎖住,第一個用戶查詢數據庫后立即緩存到 Redis,然后釋放鎖,這時候其他用戶就可以直接查詢緩存

緩存雪崩

1、定義

緩存雪崩是指 Redis 中大批量的 key 在同一時間,或者某一段時間內一起過期,造成多個 key 的請求全部無法命中緩存,這些請求全部到數據庫中,給數據庫帶來很大壓力。與緩存擊穿不同,擊穿是指一個 key 過期,雪崩是指很多 key 同時過期。

2、解決方案

緩存過期時間設置成不同時間,不要再統(tǒng)一時間過期

如果緩存數據庫是分布式部署,將熱點數據均勻分布在不同的緩存數據庫中。

緩存污染

1、定義

緩存污染是指,由于歷史原因,緩存中有很多 key 沒有設置過期時間,導致很多 key 其實已經沒有用了,但是一直存放在 redis 中,時間久了,redis 內存就被占滿了

2、解決方案

緩存盡量設置過期時間

設置緩存淘汰策略為最近最少使用的原則,然后將這些數據刪除。




審核編輯:劉清

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規(guī)問題,請聯系本站處理。 舉報投訴
  • RTT
    RTT
    +關注

    關注

    0

    文章

    65

    瀏覽量

    17057
  • 過濾器
    +關注

    關注

    1

    文章

    427

    瀏覽量

    19519
  • Redis
    +關注

    關注

    0

    文章

    370

    瀏覽量

    10830

原文標題:這次徹底讀透 Redis !

文章出處:【微信號:cxuangoodjob,微信公眾號:程序員cxuan】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    讀懂接口模塊的組合應用有哪些?

    讀懂接口模塊的組合應用有哪些?
    發(fā)表于 05-17 07:15

    讀懂如何去優(yōu)化AC耦合電容?

    讀懂如何去優(yōu)化AC耦合電容?
    發(fā)表于 06-08 07:04

    讀懂什么是NEC協(xié)議

    讀懂什么是NEC協(xié)議?
    發(fā)表于 10-15 09:22

    讀懂中斷方式和輪詢操作有什么區(qū)別嗎

    讀懂中斷方式和輪詢操作有什么區(qū)別嗎?
    發(fā)表于 12-10 06:00

    讀懂傳感器的原理與結構

    讀懂傳感器傳感器在原理與結構上千差萬別,如何根據具體的測量目的、測量對象以及測量環(huán)境合理地選用傳感器,是在進行某個量的測量時首先要解決的問題。當傳感器確定之后,與之相配套的測量方法和測量設備也就
    發(fā)表于 01-13 07:08

    讀懂無線充電產業(yè)鏈

    讀懂無線充電產業(yè)鏈,新用戶關注【電子發(fā)燒友網】微信公眾號,輸入“積分”,立送10積分!
    發(fā)表于 12-04 19:13 ?46次下載

    讀懂NB-IoT 的現狀、挑戰(zhàn)和前景

    讀懂 NB-IoT 的現狀、挑戰(zhàn)和前景
    的頭像 發(fā)表于 02-28 15:42 ?6317次閱讀

    讀懂MCU的特點、功能及如何編寫

    讀懂MCU的特點、功能及如何編寫
    發(fā)表于 12-05 09:51 ?24次下載
    <b class='flag-5'>一</b><b class='flag-5'>文</b><b class='flag-5'>讀懂</b>MCU的特點、功能及如何編寫

    讀懂汽輪機轉子軸油封磨損修復的方法

    讀懂,汽輪機轉子軸油封磨損修復的方法
    發(fā)表于 06-24 15:42 ?1次下載

    讀懂方殼電池倉段差缺陷檢測

    讀懂方殼電池倉段差缺陷檢測
    的頭像 發(fā)表于 01-12 15:46 ?864次閱讀
    <b class='flag-5'>一</b><b class='flag-5'>文</b><b class='flag-5'>讀懂</b>方殼電池倉段差缺陷檢測

    讀懂,什么是BLE?

    讀懂,什么是BLE?
    的頭像 發(fā)表于 11-27 17:11 ?2114次閱讀
    <b class='flag-5'>一</b><b class='flag-5'>文</b><b class='flag-5'>讀懂</b>,什么是BLE?

    讀懂車規(guī)級AEC-Q認證

    讀懂車規(guī)級AEC-Q認證
    的頭像 發(fā)表于 12-04 16:45 ?859次閱讀

    讀懂微力扭轉試驗機的優(yōu)勢

    讀懂微力扭轉試驗機的優(yōu)勢
    的頭像 發(fā)表于 11-30 09:08 ?521次閱讀
    <b class='flag-5'>一</b><b class='flag-5'>文</b><b class='flag-5'>讀懂</b>微力扭轉試驗機的優(yōu)勢

    讀懂新能源汽車的功能安全

    電子發(fā)燒友網站提供《讀懂新能源汽車的功能安全.pdf》資料免費下載
    發(fā)表于 09-04 09:22 ?3次下載

    讀懂MSA(測量系統(tǒng)分析)

    讀懂MSA(測量系統(tǒng)分析)
    的頭像 發(fā)表于 11-01 11:08 ?670次閱讀
    <b class='flag-5'>一</b><b class='flag-5'>文</b><b class='flag-5'>讀懂</b>MSA(測量系統(tǒng)分析)