redis 基礎(chǔ)
如果對 Redis 還不了解的同學(xué)可以先看一下這篇 https://www.cnblogs.com/wugongzi/p/12841273.html 這里面介紹了 Redis 是什么,以及怎么用。
Redis 管道
我們通常使用 Redis 的方式是,發(fā)送命令,命令排隊(duì),Redis 執(zhí)行,然后返回結(jié)果,這個(gè)過程稱為Round trip time(簡稱RTT, 往返時(shí)間)。但是如果有多條命令需要執(zhí)行時(shí),需要消耗 N 次 RTT,經(jīng)過 N 次 IO 傳輸,這樣效率明顯很低。
于是 Redis 管道(pipeline)便產(chǎn)生了,一次請求/響應(yīng)服務(wù)器能實(shí)現(xiàn)處理新的請求即使舊的請求還未被響應(yīng)。這樣就可以將多個(gè)命令發(fā)送到服務(wù)器,而不用等待回復(fù),最后在一個(gè)步驟中讀取該答復(fù)。這就是管道(pipelining),減少了 RTT,提升了效率
重要說明: 使用管道發(fā)送命令時(shí),服務(wù)器將被迫回復(fù)一個(gè)隊(duì)列答復(fù),占用很多內(nèi)存。所以,如果你需要發(fā)送大量的命令,最好是把他們按照合理數(shù)量分批次的處理,例如10K的命令,讀回復(fù),然后再發(fā)送另一個(gè)10k的命令,等等。這樣速度幾乎是相同的,但是在回復(fù)這10k命令隊(duì)列需要非常大量的內(nèi)存用來組織返回?cái)?shù)據(jù)內(nèi)容。
Redis 發(fā)布訂閱
發(fā)布訂閱是一種消息模式,發(fā)送者(sub)發(fā)送消息,訂閱者(pub)接收消息
如上圖所示,發(fā)布訂閱基于頻道實(shí)現(xiàn)的,同一個(gè)頻道可以有多個(gè)訂閱者,多個(gè)發(fā)布者。其中任意一個(gè)發(fā)布者發(fā)布消息到頻道中,所以訂閱者都可以收到該消息。
發(fā)布訂閱 Redis 演示
我在服務(wù)器上啟動了 4 個(gè) Redis 客戶端,2 個(gè)訂閱者,2 個(gè)發(fā)布者,訂閱者訂閱的頻道為 channel01
第一步:發(fā)布者 1 往 channel01 中發(fā)送消息 "wugongzi"
訂閱者 1 收到消息:
訂閱者 2 收到消息:
第二步:發(fā)布者 2 往頻道中發(fā)布消息 "hello-redis"
訂閱者 1 收到消息:
訂閱者 2 收到消息:
Redis 同時(shí)支持訂閱多個(gè)頻道:
Redis 過期策略過期時(shí)間使用
Redis 可以給每個(gè) key 都設(shè)置一個(gè)過期時(shí)間,過期時(shí)間到達(dá)后,Redis 會自動刪除這個(gè) key。
實(shí)際生產(chǎn)中,我們還是要求必須要為每個(gè) Redis 的 Key 設(shè)置一個(gè)過期時(shí)間,如果不設(shè)置過期時(shí)間,時(shí)間一久,Redis 內(nèi)存就會滿了,有很多冷數(shù)據(jù),依然存在。
設(shè)置過期時(shí)間的命令:EXPIRE key seconds
在使用過程中有一點(diǎn)需要注意,就是在每次更新 Redis 時(shí),都需要重新設(shè)置過期時(shí)間,如果不設(shè)置,那個(gè) key 就是永久的,下面給大家演示一下如何使用:
過期刪除策略
Redis keys過期有兩種方式:被動和主動方式。
當(dāng)一些客戶端嘗試訪問過期 key 時(shí),Redis 發(fā)現(xiàn) key 已經(jīng)過期便刪除掉這些 key
當(dāng)然,這樣是不夠的,因?yàn)橛行┻^期的 keys,可能永遠(yuǎn)不會被訪問到。 無論如何,這些 keys 應(yīng)該過期,所以 Redis 會定時(shí)刪除這些 key
具體就是Redis每秒10次做的事情:
- 測試隨機(jī)的20個(gè)keys進(jìn)行相關(guān)過期檢測。
- 刪除所有已經(jīng)過期的keys。
- 如果有多于25%的keys過期,重復(fù)步奏1
這是一個(gè)平凡的概率算法,基本上的假設(shè)是,Redis的樣本是這個(gè)密鑰控件,并且我們不斷重復(fù)過期檢測,直到過期的keys的百分百低于25%,這意味著,在任何給定的時(shí)刻,最多會清除1/4的過期keys。
Redis 事務(wù)事務(wù)基本使用
Redis 事務(wù)可以一次執(zhí)行多條命令,Redis 事務(wù)有如下特點(diǎn):
- 事務(wù)是一個(gè)單獨(dú)的隔離操作:事務(wù)中的所有命令都會序列化、按順序地執(zhí)行。事務(wù)在執(zhí)行的過程中,不會被其他客戶端發(fā)送來的命令請求所打斷。
- 事務(wù)是一個(gè)原子操作:事務(wù)中的命令要么全部被執(zhí)行,要么全部都不執(zhí)行。
Redis 事務(wù)通過 MULTI 、exec、DISCARD、WATCH 幾個(gè)命令來實(shí)現(xiàn),MULTI 命令用于開啟事務(wù),EXEC 用于提交事務(wù),DISCARD 用于放棄事務(wù),WATCH 可以為 Redis 事務(wù)提供 check-and-set (CAS)行為。
事務(wù)發(fā)生錯(cuò)誤
Reids 事務(wù)發(fā)生錯(cuò)誤分為兩種情況。
第一種:事務(wù)提交前發(fā)生錯(cuò)誤,也就是在發(fā)送命令過程中發(fā)生錯(cuò)誤,看演示
上面我故意將 incr 命令寫錯(cuò),從結(jié)果我們可以看到,這條 incr 沒有入隊(duì),并且事務(wù)執(zhí)行失敗,k1 和 k2 都沒有值
第二種:事務(wù)提交后發(fā)生錯(cuò)誤,也就是在執(zhí)行命令過程中發(fā)生錯(cuò)誤,看演示
上面的事務(wù)命令中,我給 k1 設(shè)置了一個(gè) d,然后執(zhí)行自增命令,最后獲取 k1 的值,我們發(fā)現(xiàn)第二條命令執(zhí)行發(fā)生了錯(cuò)誤,但是整個(gè)事務(wù)依然提交成功了,從上面現(xiàn)象中可以得出,Redis 事務(wù)不支持回滾操作。如果支持的話,整個(gè)事務(wù)的命令都不應(yīng)該被執(zhí)行。
為什么 Redis 不支持回滾
如果你有使用關(guān)系式數(shù)據(jù)庫的經(jīng)驗(yàn), 那么 “Redis 在事務(wù)失敗時(shí)不進(jìn)行回滾,而是繼續(xù)執(zhí)行余下的命令”這種做法可能會讓你覺得有點(diǎn)奇怪。
以下是這種做法的優(yōu)點(diǎn):
- Redis 命令只會因?yàn)殄e(cuò)誤的語法而失敗(并且這些問題不能在入隊(duì)時(shí)發(fā)現(xiàn)),或是命令用在了錯(cuò)誤類型的鍵上面:這也就是說,從實(shí)用性的角度來說,失敗的命令是由編程錯(cuò)誤造成的,而這些錯(cuò)誤應(yīng)該在開發(fā)的過程中被發(fā)現(xiàn),而不應(yīng)該出現(xiàn)在生產(chǎn)環(huán)境中。
- 因?yàn)椴恍枰獙貪L進(jìn)行支持,所以 Redis 的內(nèi)部可以保持簡單且快速。
有種觀點(diǎn)認(rèn)為 Redis 處理事務(wù)的做法會產(chǎn)生 bug , 然而需要注意的是, 在通常情況下, 回滾并不能解決編程錯(cuò)誤帶來的問題。 舉個(gè)例子, 如果你本來想通過 incr 命令將鍵的值加上 1 , 卻不小心加上了 2 , 又或者對錯(cuò)誤類型的鍵執(zhí)行了 incr , 回滾是沒有辦法處理這些情況的。
放棄事務(wù)
當(dāng)執(zhí)行 discard 命令時(shí), 事務(wù)會被放棄, 事務(wù)隊(duì)列會被清空, 并且客戶端會從事務(wù)狀態(tài)中退出
WATCH 命令使用
watch 使得 exec 命令需要有條件地執(zhí)行: 事務(wù)只能在所有被監(jiān)視鍵都沒有被修改的前提下執(zhí)行, 如果這個(gè)前提不能滿足的話,事務(wù)就不會被執(zhí)行
上面我用 watch 命令監(jiān)聽了 k1 和 k2,然后開啟事務(wù),在事務(wù)提交之前,k1 的值被修改了,watch 監(jiān)聽到 k1 值被修改,所以事務(wù)沒有被提交。
Redis 腳本和事務(wù)
從定義上來說, Redis 中的腳本本身就是一種事務(wù), 所以任何在事務(wù)里可以完成的事, 在腳本里面也能完成。 并且一般來說, 使用腳本要來得更簡單,并且速度更快。
因?yàn)槟_本功能是 Redis 2.6 才引入的, 而事務(wù)功能則更早之前就存在了, 所以 Redis 才會同時(shí)存在兩種處理事務(wù)的方法。
Reids 持久化為什么需要持久化
我們知道 Redis 是內(nèi)存數(shù)據(jù)庫,主打高性能,速度快。相比 Redis 而言,MySQL 的數(shù)據(jù)則是保存再硬盤中(其實(shí)也有內(nèi)存版的 MySQL 數(shù)據(jù)庫,但是價(jià)格極其昂貴,一般公司不會使用),速度慢,但是穩(wěn)定性好。你想想 Redis 數(shù)據(jù)保存在內(nèi)存中,一旦服務(wù)器宕機(jī)了,數(shù)據(jù)豈不是全部都沒了,這將會出現(xiàn)很大問題。所以 Redis 為了彌補(bǔ)這一缺陷,提供數(shù)據(jù)持久化機(jī)制,即使服務(wù)器宕機(jī),依然可以保證數(shù)據(jù)不丟失。
持久化簡介
Redis 提供了兩種持久化機(jī)制 RDB 和 AOF,適用于不同場景
- RDB持久化方式能夠在指定的時(shí)間間隔能對你的數(shù)據(jù)進(jìn)行快照存儲
- AOF持久化方式記錄每次對服務(wù)器寫的操作,當(dāng)服務(wù)器重啟的時(shí)候會重新執(zhí)行這些命令來恢復(fù)原始的數(shù)據(jù),AOF命令以redis協(xié)議追加保存每次寫的操作到文件末尾.Redis還能對AOF文件進(jìn)行后臺重寫,使得AOF文件的體積不至于過大
RDB 持久化是通過在指定時(shí)間間隔對數(shù)據(jù)進(jìn)行快照,比如在 8 點(diǎn)鐘對數(shù)據(jù)進(jìn)行持久化,那么 Redis 會 fork 一個(gè)子進(jìn)程將 8 點(diǎn)那一刻內(nèi)存中的數(shù)據(jù)持久化到磁盤上。觸發(fā) RDB 持久化有以下幾種方法
RDB 持久化方式
1、執(zhí)行 save 命令
執(zhí)行 save 命令進(jìn)行持久會阻塞 Redis,備份期間 Redis 無法對外提供服務(wù),一般不建議使用,使用場景為 Redis 服務(wù)器需要停機(jī)維護(hù)的情況下。
2、執(zhí)行 bgsave 命令
bgsave 命令不會阻塞 Redis 主進(jìn)程,持久化期間 Redis 依然可以正常對外提供服務(wù)
3、通過配置文件中配置的 save 規(guī)則來觸發(fā)
save 900 1:900s 內(nèi)有 1 個(gè) key 發(fā)生變化,則觸發(fā) RDB 快照
save 300 10:300s 內(nèi)有 10 個(gè) key 發(fā)生變化,則觸發(fā) RDB 快照
save 60 10000:60s 內(nèi)有 10000 個(gè) key 發(fā)生變化(新增、修改、刪除),則觸發(fā) RDB 快照
save "":該配置表示關(guān)閉 RDB 持久化
RDB 持久化原理
Redis 進(jìn)行 RDB 時(shí),會 fork 一個(gè)子進(jìn)程來進(jìn)行數(shù)據(jù)持久化,這樣不妨礙 Redis 繼續(xù)對外提供服務(wù),提高效率。
曾經(jīng)面試官出過這樣面試題:
假如 Redis 在 8 點(diǎn)觸發(fā)了 RDB 持久化,持久化用時(shí) 2 分鐘,在持久化期間,Redis 中有 100 個(gè) key 被修改了,那么 RDB 文件中的 key 是 8 點(diǎn)那一刻的數(shù)據(jù),還是變化的呢?
先不要看答案,自己思考 1 分鐘,一個(gè)問題只有你自己思考了,才能印象深刻。
好,下面我們一起來看下這張圖:
從圖中我們可以清晰的看到,Redis 備份時(shí),fork 了一個(gè)子進(jìn)程,子進(jìn)程去做持久化的工作,子進(jìn)程中的 key 指向了 8 點(diǎn)那一刻的數(shù)據(jù),后面 k1 的值修改了,redis 會在內(nèi)存中創(chuàng)建一個(gè)新的值,然后主進(jìn)程 k1 指針指向新的值,子進(jìn)程 k1 指針依然指向 19,這樣 Redis 持久化的就是 8 點(diǎn)那一刻的數(shù)據(jù),不會發(fā)生變化。同時(shí),從圖中我們也可以看到,Redis 持久化時(shí)并不是將內(nèi)存中數(shù)據(jù)全部拷貝一份進(jìn)行備份。
RDB 優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
- RDB是一個(gè)非常緊湊的文件,它保存了某個(gè)時(shí)間點(diǎn)得數(shù)據(jù)集,非常適用于數(shù)據(jù)集的備份,比如你可以在每個(gè)小時(shí)報(bào)保存一下過去24小時(shí)內(nèi)的數(shù)據(jù),同時(shí)每天保存過去30天的數(shù)據(jù),這樣即使出了問題你也可以根據(jù)需求恢復(fù)到不同版本的數(shù)據(jù)集
- RDB在保存RDB文件時(shí)父進(jìn)程唯一需要做的就是fork出一個(gè)子進(jìn)程,接下來的工作全部由子進(jìn)程來做,父進(jìn)程不需要再做其他IO操作,所以RDB持久化方式可以最大化redis的性能
- 與AOF相比,在恢復(fù)大的數(shù)據(jù)集的時(shí)候,RDB方式會更快一些
缺點(diǎn)
- 如果備份間隔時(shí)間較長,RDB 會丟失較多的數(shù)據(jù)。比如 8 點(diǎn)備份一次,8 點(diǎn)半服務(wù)器宕機(jī),那么這半小時(shí)內(nèi)的數(shù)據(jù)就會丟失了
AOF 持久化是通過日志的方式,記錄每次 Redis 的寫操作。當(dāng)服務(wù)器重啟的時(shí)候會重新執(zhí)行這些命令來恢復(fù)原始的數(shù)據(jù),AOF 命令以Redis 協(xié)議追加保存每次寫的操作到文件末尾。Redis 還能對 AOF 文件進(jìn)行后臺重寫,使得 AOF 文件的體積不至于過大
AOF 持久化配置
# 是否開啟 aof no:關(guān)閉;yes: 開啟 Appendonly no # aof 文件名 appendfilename "appendonly.aof" # aof 同步策略 # appendfsync always # 每個(gè)命令都寫入磁盤,性能較差 appendfsync everysec # 每秒寫一次磁盤,Redis 默認(rèn)配置 # appendfsync no # 由操作系統(tǒng)執(zhí)行,默認(rèn)linux配置最多丟失30秒 # aof 重寫期間是否同步 no-appendfsync-on-rewrite no # 重寫觸發(fā)策略 auto-aof-rewrite-percentage 100 # 觸發(fā)重寫百分比 (指定百分比為0,將禁用aof自動重寫功能) auto-aof-rewrite-min-size 64mb # 觸發(fā)自動重寫的最低文件體積(小于64mb不自動重寫) # 加載aof時(shí)如果有錯(cuò)如何處理 # 如果該配置啟用,在加載時(shí)發(fā)現(xiàn)aof尾部不正確是,會向客戶端寫入一個(gè)log,但是會繼續(xù)執(zhí)行,如果設(shè)置為 no ,發(fā)現(xiàn)錯(cuò)誤就會停止,必須修復(fù)后才能重新加載。 aof-load-truncated yes # aof 中是否使用 rdb # 開啟該選項(xiàng),觸發(fā)AOF重寫將不再是根據(jù)當(dāng)前內(nèi)容生成寫命令。而是先生成RDB文件寫到開頭,再將RDB生成期間的發(fā)生的增量寫命令附加到文件末尾。 aof-use-rdb-preamble yes
AOF 文件寫入
aof 文件是命令追加的方式,先將命令寫入緩沖區(qū),時(shí)間到了再寫如磁盤中
appendfsync always # 每個(gè)命令都寫入磁盤,性能較差 appendfsync everysec # 每秒寫一次磁盤,Redis 默認(rèn)配置 appendfsync no # 由操作系統(tǒng)執(zhí)行,默認(rèn)Linux配置最多丟失30秒
上面配置就是何時(shí)寫入磁盤中
AOF 重寫
aof 文件雖然丟失的數(shù)據(jù)少,但是隨著時(shí)間的增加,aof 文件體積越來越大,占用磁盤空間越來越大,恢復(fù)時(shí)間長。所以 redis 會對 aof 文件進(jìn)行重寫,以減少 aof 文件體積
下面以一個(gè)例子說明
-- 重寫前的 aof set k1 20 set k2 40 set k1 35 set k3 34 set k2 19 -- 這里 k1 最終的值為 35,k2 最終值為 19,所以不需要寫入兩個(gè)命令 -- 重寫后 set k1 35 set k3 34 set k2 19
混合持久化
從 Redis 4.0 版本開始,引入了混合持久化機(jī)制,純AOF方式、RDB+AOF方式,這一策略由配置參數(shù)aof-use-rdb-preamble(使用RDB作為AOF文件的前半段)控制,默認(rèn)關(guān)閉(no),設(shè)置為yes可開啟
- no:按照AOF格式寫入命令,與4.0前版本無差別;
- yes:先按照RDB格式寫入數(shù)據(jù)狀態(tài),然后把重寫期間AOF緩沖區(qū)的內(nèi)容以AOF格式寫入,文件前半部分為RDB格式,后半部分為AOF格式。
混合持久化優(yōu)點(diǎn)如下:
- 大大減少了 aof 文件體積
- 加快了 aof 文件恢復(fù)速度,前面是 rdb ,恢復(fù)速度快
AOF 數(shù)據(jù)恢復(fù)
第一種:純 AOF
恢復(fù)時(shí),取出 AOF 中命令,一條條執(zhí)行恢復(fù)
第二種:RDB+AOF
先執(zhí)行 RDB 加載流程,執(zhí)行完畢后,再取出余下命令,開始一條條執(zhí)行
AOF 優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
- AOF 實(shí)時(shí)性更好,丟失數(shù)據(jù)更少
- AOF 已經(jīng)支持混合持久化,文件大小可以有效控制,并提高了數(shù)據(jù)加載時(shí)的效率
缺點(diǎn)
- 對于相同的數(shù)據(jù)集合,AOF 文件通常會比 RDB 文件大
- 在特定的 fsync 策略下,AOF 會比 RDB 略慢
- AOF 恢復(fù)速度比 RDB 慢
學(xué)習(xí)過 JAVA 的同學(xué),應(yīng)該對鎖都不陌生。Java 中多個(gè)線程訪問共享資源時(shí),會出現(xiàn)并發(fā)問題,我們通常利用 synchronized 或者 Lock 鎖來解決多線程并發(fā)訪問從而出現(xiàn)的安全問題。細(xì)心的同學(xué)可能已經(jīng)發(fā)現(xiàn)了, synchronized 或者 Lock 鎖解決線程安全問題在單節(jié)點(diǎn)情況下是可行的,但是如果服務(wù)部署在多臺服務(wù)器上,本地鎖就失效了。
分布式場景下,需要采用新的解決方案,就是今天要說的 Redis 分布式鎖。日常業(yè)務(wù)中,類似搶紅包,秒殺等場景都可以使用 Redis 分布式鎖來解決并發(fā)問題。
分布式鎖特點(diǎn)
分布式在保障安全、高可用的情況下需要具備以下特性
- 互斥性:任意一個(gè)時(shí)刻,只能有一個(gè)客戶端獲取到鎖
- 安全性:鎖只能被持久的客戶端刪除,不能被其他人刪除
- 高可用,高性能:加鎖和解鎖消耗的性能少,時(shí)間短
- 鎖超時(shí):當(dāng)客戶端獲取鎖后出現(xiàn)故障,沒有立即釋放鎖,該鎖要能夠在一定時(shí)間內(nèi)釋放,否則其他客戶端無法獲取到鎖
- 可重入性:客戶端獲取到鎖后,在持久鎖期間可以再次獲取到該鎖
方案一:SETNX 命令
Redis 提供了一個(gè)獲取分布式鎖的命令 SETNX
setnx key value
如果獲取鎖成功,redis 返回 1,獲取鎖失敗 redis 返回 0
客戶端使用偽代碼
if (setnx(k1,v1) == 1) { try{ // 執(zhí)行邏輯 .... }catch() { }finally{ // 執(zhí)行完成后釋放鎖 del k1; } }
這個(gè)命令看似可以達(dá)到我們的目的,但是不符合分布式鎖的特性,如果客戶端在執(zhí)行業(yè)務(wù)邏輯過程中,服務(wù)器宕機(jī)了,finally 中代碼還沒來得及執(zhí)行,鎖沒有釋放,也就意味其他客戶端永遠(yuǎn)無法獲取到這個(gè)鎖
方案二:SETNX + EXPIRE
該方案獲取鎖之后,立即給鎖加上一個(gè)過期時(shí)間,這樣即使客戶端沒有手動釋放鎖,鎖到期后也會自動釋放
我們來看下偽代碼
if (setnx(k1, v1) == 1){ expire(key, 10); try { //.... 你的業(yè)務(wù)邏輯 } finally { del(key); } }
這個(gè)方案很完美,既可以獲取到,又不用擔(dān)心客戶端宕機(jī)。等等,這里面真的沒有問題嗎?再仔細(xì)瞅瞅,一瞅就瞅出問題來了
if (setnx(k1, v1) == 1){ // 再剛獲取鎖之后,想要給鎖設(shè)置過期時(shí)間,此時(shí)服務(wù)器掛了 expire(key, 10); // 這條命令沒有執(zhí)行 try { //.... 你的業(yè)務(wù)邏輯 } finally { del(key); } }
這里的 setnx 命令和 expire 命令不是原子性的,他們之間執(zhí)行需要一定的等等時(shí)間,雖然這個(gè)時(shí)間很短,但是依然有極小概率出現(xiàn)問題
使用 Lua 腳本
既然 setnx 和 expire 兩個(gè)命令非原子性,那么我們讓其符合原子性即可,通過 Lua 腳本即可實(shí)現(xiàn)。Redis 使用單個(gè) Lua 解釋器去運(yùn)行所有腳本,并且, Redis 也保證腳本會以原子性(atomic)的方式執(zhí)行: 當(dāng)某個(gè)腳本正在運(yùn)行的時(shí)候,不會有其他腳本或 Redis 命令被執(zhí)行
具體實(shí)現(xiàn)如下:
if redis.call('setnx',KEYS[1],ARGV[1]) == 1 then redis.call('expire',KEYS[1],ARGV[2]) else return 0 end;
這樣應(yīng)該沒問題了吧,看似上面的幾個(gè)問題都很好解決了。不對,再想想,肯定還有沒考慮到的
我們再來看一段偽代碼
// 執(zhí)行 lua 腳本 // 獲取 k1 鎖,過期時(shí)間 10 s if (execlua()==1){ try { buyGoods(); } finally { del(key); } }
從圖中我們可以很清晰發(fā)現(xiàn)問題所在
- 客戶端 A 還未執(zhí)行完畢,客戶端 B 就獲取到了鎖,這樣就可能導(dǎo)致并發(fā)問題
- 客戶端 A 執(zhí)行完畢,開始刪除鎖。但此時(shí)的鎖為 B 所有,相當(dāng)于刪除了屬于客戶端 B 的鎖,這樣肯定會發(fā)生問題
方案四:SET EX PX NX + 校驗(yàn)唯一隨機(jī)值,再刪除
既然鎖有可能被別的客戶端刪除,那么在刪除鎖的時(shí)候我們加上一層校驗(yàn),判斷釋放鎖是當(dāng)前客戶端持有的,如果是當(dāng)前客戶端,則允許刪除,否則不允許刪除。
- EX second :設(shè)置鍵的過期時(shí)間為 second 秒。 SET key value EX second 效果等同于 SETEX key second value 。
- PX millisecond :設(shè)置鍵的過期時(shí)間為 millisecond 毫秒。 SET key value PX millisecond 效果等同于 PSETEX key millisecond value 。
- NX :只在鍵不存在時(shí),才對鍵進(jìn)行設(shè)置操作。 SET key value NX 效果等同于 SETNX key value 。
- XX :只在鍵已經(jīng)存在時(shí),才對鍵進(jìn)行設(shè)置操作。
- 嵌入式物聯(lián)網(wǎng)需要學(xué)的東西真的非常多,千萬不要學(xué)錯(cuò)了路線和內(nèi)容,導(dǎo)致工資要不上去!
使用示例:
if(jedis.set(resource_name, random_value, "NX", "EX", 100s) == 1){ //加鎖, value 傳入一個(gè)隨機(jī)數(shù) try { do something //業(yè)務(wù)處理 }catch(){ } finally { // 判斷 value 是否相等, 相等才釋放鎖, 這里判斷和刪除是非原子性, 真實(shí)場景下可以將這兩步放入 Lua 腳本中執(zhí)行 if (random_value.equals(jedis.get(resource_name))) { jedis.del(lockKey); //釋放鎖 } } }
Lua 腳本如下:
if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end
此方案解決了鎖被其他客戶端解除的問題,但是依然沒有解決鎖過期釋放,但是業(yè)務(wù)還沒有執(zhí)行完成的問題
Redisson框架
方案四中并沒有解決方法未執(zhí)行完成,鎖就超時(shí)釋放的問題。這里有個(gè)方案大家比較容易想到,那就是鎖的超時(shí)時(shí)間設(shè)置長一點(diǎn),比如2min,一個(gè)接口執(zhí)行時(shí)間總不能比 2 min 還長,那你就等著領(lǐng)盒飯吧,哈哈哈。但是這么做,一來是不能每個(gè)鎖都設(shè)置這么久超時(shí)時(shí)間,二來是如果接口出現(xiàn)異常了,鎖只能 2 min 后才能釋放,其他客戶端等待時(shí)間較長。
這個(gè)問題早就有人想到了,并給出了解決方案,開源框架 Redisson 解決了這個(gè)問題。
Redisson 在方法執(zhí)行期間,會不斷的檢測鎖是否到期,如果發(fā)現(xiàn)鎖快要到期,但是方法還沒有執(zhí)行完成,便會延長鎖的過期時(shí)間,從而解決了鎖超時(shí)釋放問題。
Redlock
上面所介紹的分布式鎖,都是在單臺 Redis 服務(wù)器下的解決方案。真實(shí)的生產(chǎn)環(huán)境中,我們通常會部署多臺 Redis 服務(wù)器,也就是集群模式,這種情況上述解決方案就失效了。
對于集群 Redis,Redis 的作者 antirez 提出了另一種解決方案,Redlock 算法
Redlock 算法大致流程如下:
1、獲取當(dāng)前Unix時(shí)間,以毫秒為單位。
2、依次嘗試從N個(gè)實(shí)例,使用相同的key和隨機(jī)值獲取鎖。在步驟2,當(dāng)向Redis設(shè)置鎖時(shí),客戶端應(yīng)該設(shè)置一個(gè)網(wǎng)絡(luò)連接和響應(yīng)超時(shí)時(shí)間,這個(gè)超時(shí)時(shí)間應(yīng)該小于鎖的失效時(shí)間。例如你的鎖自動失效時(shí)間為10秒,則超時(shí)時(shí)間應(yīng)該在5-50毫秒之間。這樣可以避免服務(wù)器端Redis已經(jīng)掛掉的情況下,客戶端還在死死地等待響應(yīng)結(jié)果。如果服務(wù)器端沒有在規(guī)定時(shí)間內(nèi)響應(yīng),客戶端應(yīng)該盡快嘗試另外一個(gè)Redis實(shí)例。
3、客戶端使用當(dāng)前時(shí)間減去開始獲取鎖時(shí)間(步驟1記錄的時(shí)間)就得到獲取鎖使用的時(shí)間。當(dāng)且僅當(dāng)從大多數(shù)(這里是3個(gè)節(jié)點(diǎn))的Redis節(jié)點(diǎn)都取到鎖,并且使用的時(shí)間小于鎖失效時(shí)間時(shí),鎖才算獲取成功。
4、如果取到了鎖,key的真正有效時(shí)間等于有效時(shí)間減去獲取鎖所使用的時(shí)間(步驟3計(jì)算的結(jié)果)。
5、如果因?yàn)槟承┰颍@取鎖失敗(沒有在至少N/2+1個(gè)Redis實(shí)例取到鎖或者取鎖時(shí)間已經(jīng)超過了有效時(shí)間),客戶端應(yīng)該在所有的Redis實(shí)例上進(jìn)行解鎖(即便某些Redis實(shí)例根本就沒有加鎖成功)。
總結(jié):簡單總結(jié)一下就是客戶端向 Redis 集群中所有服務(wù)器發(fā)送獲取鎖的請求,只有半數(shù)以上的鎖獲取成功后,才代表鎖獲取成功,否則鎖獲取失敗。
Redis 集群Redis 集群的三種模式
在生產(chǎn)環(huán)境中,我們使用 Redis 通常采用集群模式,因?yàn)閱螜C(jī)版 Redis 穩(wěn)定性可靠性較低,而且存儲空間有限。
Redis 支持三種集群模式
- 主從復(fù)制
- 哨兵模式
- Cluster 模式
主從復(fù)制概念
主從復(fù)制模式,有一個(gè)主,多個(gè)從,從而實(shí)現(xiàn)讀寫分離。主機(jī)負(fù)責(zé)寫請求,從機(jī)負(fù)責(zé)讀請求,減輕主機(jī)壓力
主從復(fù)制原理
- 從數(shù)據(jù)庫啟動成功后,連接主數(shù)據(jù)庫,發(fā)送 SYNC 命令;
- 主數(shù)據(jù)庫接收到 SYNC 命令后,開始執(zhí)行 BGSAVE 命令生成 RDB 文件并使用緩沖區(qū)記錄此后執(zhí)行的所有寫命令;
- 主數(shù)據(jù)庫 BGSAVE 執(zhí)行完后,向所有從數(shù)據(jù)庫發(fā)送快照文件,并在發(fā)送期間繼續(xù)記錄被執(zhí)行的寫命令;
- 從數(shù)據(jù)庫收到快照文件后丟棄所有舊數(shù)據(jù),載入收到的快照;
- 主數(shù)據(jù)庫快照發(fā)送完畢后開始向從數(shù)據(jù)庫發(fā)送緩沖區(qū)中的寫命令;
- 從數(shù)據(jù)庫完成對快照的載入,開始接收命令請求,并執(zhí)行來自主數(shù)據(jù)庫緩沖區(qū)的寫命令;(從數(shù)據(jù)庫初始化完成
- 主數(shù)據(jù)庫每執(zhí)行一個(gè)寫命令就會向從數(shù)據(jù)庫發(fā)送相同的寫命令,從數(shù)據(jù)庫接收并執(zhí)行收到的寫命令(從數(shù)據(jù)庫初始化完成后的操作
- 出現(xiàn)斷開重連后,2.8之后的版本會將斷線期間的命令傳給重?cái)?shù)據(jù)庫,增量復(fù)制。
- 主從剛剛連接的時(shí)候,進(jìn)行全量同步;全同步結(jié)束后,進(jìn)行增量同步。當(dāng)然,如果有需要,slave 在任何時(shí)候都可以發(fā)起全量同步。Redis 的策略是,無論如何,首先會嘗試進(jìn)行增量同步,如不成功,要求從機(jī)進(jìn)行全量同步。
主從復(fù)制優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
- 支持主從復(fù)制,主機(jī)會自動將數(shù)據(jù)同步到從機(jī),可以進(jìn)行讀寫分離
- Slave 同樣可以接受其它 Slaves 的連接和同步請求,這樣可以有效的分載 Master 的同步壓力
- Master Server 是以非阻塞的方式為 Slaves 提供服務(wù)。所以在 Master-Slave 同步期間,客戶端仍然可以提交查詢或修改請求
缺點(diǎn)
- 主從不具備容錯(cuò)和恢復(fù)能力,一旦主機(jī)掛了,那么整個(gè)集群處理可讀狀態(tài),無法處理寫請求,會丟失數(shù)據(jù)
- 主機(jī)宕機(jī)后無法自動恢復(fù),只能人工手動恢復(fù)
- 集群存儲容量有限,容量上線就是主庫的內(nèi)存的大小,無法存儲更多內(nèi)容
###哨兵集群
哨兵概念
哨兵,我們經(jīng)常在電視劇中看到一些放哨的,哨兵的作用和這些放哨的差不多,起到監(jiān)控作用。一旦 Redis 集群出現(xiàn)問題了,哨兵會立即做出相應(yīng)動作,應(yīng)對異常情況。
哨兵模式是基于主從復(fù)制模式上搭建的,因?yàn)橹鲝膹?fù)制模式情況下主服務(wù)器宕機(jī),會導(dǎo)致整個(gè)集群不可用,需要人工干預(yù),所以哨兵模式在主從復(fù)制模式下引入了哨兵來監(jiān)控整個(gè)集群,哨兵模式架構(gòu)圖如下:
哨兵功能
監(jiān)控(Monitoring):哨兵會不斷地檢查主節(jié)點(diǎn)和從節(jié)點(diǎn)是否運(yùn)作正常。
自動故障轉(zhuǎn)移(Automatic failover):當(dāng)主節(jié)點(diǎn)不能正常工作時(shí),哨兵會開始自動故障轉(zhuǎn)移操作,它會將失效主節(jié)點(diǎn)的其中一個(gè)從節(jié)點(diǎn)升級為新的主節(jié)點(diǎn),并讓其他從節(jié)點(diǎn)改為復(fù)制新的主節(jié)點(diǎn)。
配置提供者(Configuration provider):客戶端在初始化時(shí),通過連接哨兵來獲得當(dāng)前Redis服務(wù)的主節(jié)點(diǎn)地址。
通知(Notification):哨兵可以將故障轉(zhuǎn)移的結(jié)果發(fā)送給客戶端。
下線判斷
Redis 下線分為主觀下線和客觀下線兩種
- 主觀下線:單臺哨兵任務(wù)主庫處于不可用狀態(tài)
- 客觀下線:整個(gè)哨兵集群半數(shù)以上的哨兵都認(rèn)為主庫處于可不用狀態(tài)
哨兵集群中任意一臺服務(wù)器判斷主庫不可用時(shí),此時(shí)會發(fā)送命令給哨兵集群中的其他服務(wù)器確認(rèn),其他服務(wù)器收到命令后會確認(rèn)主庫的狀態(tài),如果不可用,返回 YES,可用則返回 NO,當(dāng)有半數(shù)的服務(wù)器都返回 YES,說明主庫真的不可用,此時(shí)需要重新選舉
主庫選舉
當(dāng)哨兵集群判定主庫下線了,此時(shí)需要重新選舉出一個(gè)新的主庫對外提供服務(wù)。那么該由哪個(gè)哨兵來完成這個(gè)新庫選舉和切換的動作呢?
注意:這里不能讓每個(gè)哨兵都去選舉,可能會出現(xiàn)每個(gè)哨兵選舉出的新主庫都不同,這樣就沒法判定,所以需要派出一個(gè)代表
哨兵代表選擇
哨兵的選舉機(jī)制其實(shí)很簡單,就是一個(gè)Raft選舉算法:選舉的票數(shù)大于等于num(sentinels)/2+1時(shí),將成為領(lǐng)導(dǎo)者,如果沒有超過,繼續(xù)選舉
- 任何一個(gè)想成為 Leader 的哨兵,要滿足兩個(gè)條件:
-
- 第一,拿到半數(shù)以上的贊成票;
- 第二,拿到的票數(shù)同時(shí)還需要大于等于哨兵配置文件中的 quorum 值。
以 3 個(gè)哨兵為例,假設(shè)此時(shí)的 quorum 設(shè)置為 2,那么,任何一個(gè)想成為 Leader 的哨兵只要拿到 2 張贊成票,就可以了。
新庫選擇
上面已經(jīng)選舉出了哨兵代表,此時(shí)代表需要完成新主庫的選擇,新庫的選擇需要滿足以下幾個(gè)標(biāo)準(zhǔn)
- 新庫需要處于健康狀態(tài),也就是和哨兵之間保持正常的網(wǎng)絡(luò)連接
- 選擇salve-priority從節(jié)點(diǎn)優(yōu)先級最高(redis.conf)的
- 選擇復(fù)制偏移量最大,只復(fù)制最完整的從節(jié)點(diǎn)
故障轉(zhuǎn)移
上面一小節(jié)哨兵已經(jīng)選舉出了新的主庫,故障轉(zhuǎn)移要實(shí)現(xiàn)新老主庫之間的切換
故障轉(zhuǎn)移流程如下:
哨兵模式優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
- 實(shí)現(xiàn)了集群的監(jiān)控,故障轉(zhuǎn)移,實(shí)現(xiàn)了高可用
- 擁有主從復(fù)制模式的所有優(yōu)點(diǎn)
缺點(diǎn)
- 集群存儲容量有限,容量上線就是主庫的內(nèi)存的大小,無法存儲更多內(nèi)容
Redis 的哨兵模式實(shí)現(xiàn)了高可用了,但是每臺 Redis 服務(wù)器上存儲的都是相同的數(shù)據(jù),浪費(fèi)內(nèi)存,而且很難實(shí)現(xiàn)容量上的擴(kuò)展。所以在 redis3.0上加入了 Cluster 集群模式,實(shí)現(xiàn)了 Redis 的分布式存儲,也就是說每臺 Redis 節(jié)點(diǎn)上存儲不同的內(nèi)容。
Redis 集群的數(shù)據(jù)分片
Redis 集群沒有使用一致性hash, 而是引入了哈希槽的概念.
Redis 集群有16384個(gè)哈希槽,每個(gè)key通過CRC16校驗(yàn)后對16384取模來決定放置哪個(gè)槽.集群的每個(gè)節(jié)點(diǎn)負(fù)責(zé)一部分hash槽,舉個(gè)例子,比如當(dāng)前集群有3個(gè)節(jié)點(diǎn),那么:
- 節(jié)點(diǎn) A 包含 0 到 5500號哈希槽.
- 節(jié)點(diǎn) B 包含5501 到 11000 號哈希槽.
- 節(jié)點(diǎn) C 包含11001 到 16384號哈希槽.
這種結(jié)構(gòu)很容易添加或者刪除節(jié)點(diǎn). 比如如果我想新添加個(gè)節(jié)點(diǎn)D, 我需要從節(jié)點(diǎn) A, B, C中得部分槽到D上. 如果我想移除節(jié)點(diǎn)A,需要將A中的槽移到B和C節(jié)點(diǎn)上,然后將沒有任何槽的A節(jié)點(diǎn)從集群中移除即可. 由于從一個(gè)節(jié)點(diǎn)將哈希槽移動到另一個(gè)節(jié)點(diǎn)并不會停止服務(wù),所以無論添加刪除或者改變某個(gè)節(jié)點(diǎn)的哈希槽的數(shù)量都不會造成集群不可用的狀態(tài).
Redis 集群實(shí)戰(zhàn)
環(huán)境:
- Vmware 虛擬機(jī)
- centos 7
- Redis 6.0.6
因?yàn)槲沂窃诒緳C(jī)上演示的,所以用的虛擬機(jī)
主從復(fù)制
集群信息如下:
節(jié)點(diǎn)配置文件端口masterredis6379.conf6379slave1redis6380.conf6380slave1redis6381.conf6380
第一步:準(zhǔn)備三個(gè) redis.conf 配置文件,配置文件信息如下
# redis6379.conf master # 包含命令,有點(diǎn)復(fù)用的意思 include /soft/redis6.0.6/bin/redis.conf pidfile redis_6379.pid port 6379 dbfilename dump6379.rdb logfile "redis-6379.log" # redis6380.conf slave1 include /soft/redis6.0.6/bin/redis.conf pidfile redis_6380.pid port 6380 dbfilename dump6380.rdb logfile "redis-6380.log" # 最后一行設(shè)置了主節(jié)點(diǎn)的 ip 端口 replicaof 127.0.0.1 6379 # redis6381.conf slave2 include /soft/redis6.0.6/bin/redis.conf pidfile redis_6381.pid port 6381 dbfilename dump6381.rdb logfile "redis-6381.log" # 最后一行設(shè)置了主節(jié)點(diǎn)的 ip 端口 replicaof 127.0.0.1 6379 ## 注意 redis.conf 要調(diào)整一項(xiàng),設(shè)置后臺運(yùn)行,對咱們操作比較友好 daemonize yes
第二步:啟動服務(wù)器
-- 首先啟動 6379 這臺服務(wù)器,因?yàn)樗侵鲙欤▎用钤?redis 安裝目錄的 bin 目錄下) ../bin/redis-server redis6379.conf -- 接口啟動 6380 和 6381 ../bin/redis-server redis6380.conf ../bin/redis-server redis6381.conf
第三步:用客戶端連接服務(wù)器
cd bin redis-cli -p 6379 redis-cli -p 6380 redis-cli -p 6381 這里我開了三個(gè)窗口分別連接三臺 redis 服務(wù)器,方便查看
在 6379 客戶端輸入命令: info replication 可用查看集群信息
第四步:數(shù)據(jù)同步
現(xiàn)在集群已經(jīng)搭建好了,我們在 6379 服務(wù)器寫入幾條數(shù)據(jù),看下可不可以同步到 6380 和 6381
6379:
6380:
6381:
從圖中可用看出,數(shù)據(jù)已經(jīng)成功同步了
哨兵模式
哨兵集群是在主從復(fù)制的基礎(chǔ)上構(gòu)建的,相當(dāng)于是主從+哨兵
搭建哨兵模式分為兩步:
- 搭建主從復(fù)制集群
- 添加哨兵配置
哨兵模式節(jié)點(diǎn)信息如下,一主二從,三個(gè)哨兵組成一個(gè)哨兵集群
節(jié)點(diǎn)配置端口masterredis6379.conf6379slave1redis6380.conf6380slave2redis6381.conf6381sentinel1sentinel1.conf26379sentinel2sentinel2.conf26380sentinel3sentinel3.conf26381
主從復(fù)制集群的配置同上,這里就不再贅述,下面主要介紹下哨兵的配置,哨兵的配置文件其實(shí)非常簡單
# 文件內(nèi)容 # sentinel1.conf port 26379 sentinel monitor mymaster 127.0.0.1 6379 1 # sentinel2.conf port 26380 sentinel monitor mymaster 127.0.0.1 6379 1 # sentinel3.conf port 26381 sentinel monitor mymaster 127.0.0.1 6379 1
配置文件創(chuàng)建好了以后就可以啟動了,首先啟動主從服務(wù)器,然后啟動哨兵
../bin/redis-server redis6379.conf ../bin/redis-server redis6380.conf ../bin/redis-server redis6381.conf -- 啟動哨兵 ../bin/redis-sentinel sentinel1.conf ../bin/redis-sentinel sentinel2.conf ../bin/redis-sentinel sentinel3.conf
從哨兵的啟動日志中我們可用看到主從服務(wù)器的信息,以及其他哨兵節(jié)點(diǎn)的信息
故障轉(zhuǎn)移
主從同步功能上面已經(jīng)演示過了,這里主要測試一下哨兵的故障轉(zhuǎn)移
現(xiàn)在我手動將主節(jié)點(diǎn)停掉,在 6379 上執(zhí)行 shutdown 命令
此時(shí)我們觀察一下哨兵的頁面:
哨兵檢測到了 6379 下線,然后選舉出了新的主庫 6380
此時(shí)我們通過 info replication 命令查看集群信息,發(fā)現(xiàn) 6380 已經(jīng)是主庫了,他有一個(gè)從節(jié)點(diǎn) 6381
現(xiàn)在我手動將 6379 啟動,看下 6379 會不會重新變成主庫
重新啟動后,我們發(fā)現(xiàn) 6379 變成了 80 的從庫
Cluser 集群
官方推薦,Cluser 集群至少要部署 3 臺以上的 master 節(jié)點(diǎn),最好使用 3 主 3 從
節(jié)點(diǎn)配置端口cluster-master1redis7001.conf7001cluster-master2redis7002.conf7002cluster-master3redis7003.conf7003cluster-slave1redis7004.conf7004cluster-slave2redis7006.conf7005cluster-slave3redis7006.conf7006
配置文件內(nèi)容如下,6 個(gè)配置文件信息基本相同,編輯好一份后其他文件直接復(fù)制修改端口即可
# 端口 port 7001 # 啟用集群模式 cluster-enabled yes # 根據(jù)你啟用的節(jié)點(diǎn)來命名,最好和端口保持一致,這個(gè)是用來保存其他節(jié)點(diǎn)的名稱,狀態(tài)等信息的 cluster-config-file nodes_7001.conf # 超時(shí)時(shí)間 cluster-node-timeout 5000 appendonly yes # 后臺運(yùn)行 daemonize yes # 非保護(hù)模式 protected-mode no pidfile redis_7001.pid
然后分別啟動 6 個(gè)節(jié)點(diǎn)
../bin/redis-server redis7001.conf ../bin/redis-server redis7002.conf ../bin/redis-server redis7003.conf ../bin/redis-server redis7004.conf ../bin/redis-server redis7005.conf ../bin/redis-server redis7006.conf
啟動集群
# 執(zhí)行命令 # --cluster-replicas 1 命令的意思是創(chuàng)建master的時(shí)候同時(shí)創(chuàng)建一個(gè)slave $ redis-cli --cluster create 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 127.0.0.1:7006 --cluster-replicas 1
啟動過程有個(gè)地方需要輸入 yes 確認(rèn):
啟動成功后可用看到控制臺輸出結(jié)果:
3 個(gè) master 節(jié)點(diǎn),3 個(gè) slave 節(jié)點(diǎn),
master[0]槽位:0-5460
master[1]槽位:5461-10922
master[2]槽位:10923-16383
數(shù)據(jù)驗(yàn)證
連接 7001 服務(wù)器
redis-cli -p 7001 -c 集群模式下需要加上 -c 參數(shù)
從圖中可用看出,k1 被放到 7003 主機(jī)上了,我們此時(shí)獲取 k1 ,可用正常獲取到
登錄 7003 也可以正常拿到數(shù)據(jù)
Redis 緩存問題
在服務(wù)端中,數(shù)據(jù)庫通常是業(yè)務(wù)上的瓶頸,為了提高并發(fā)量和響應(yīng)速度,我們通常會采用 Redis 來作為緩存,讓盡量多的數(shù)據(jù)走 Redis 查詢,不直接訪問數(shù)據(jù)庫。同時(shí) Redis 在使用過程中也會出現(xiàn)各種各樣的問題,面對這些問題我們該如何處理?
- 緩存穿透
- 緩存擊穿
- 緩存雪崩
- 緩存污染
1、定義:
緩存穿透是指,當(dāng)緩存和數(shù)據(jù)中都沒有對應(yīng)記錄,但是客戶端卻一直在查詢。比如黑客攻擊系統(tǒng),不斷的去查詢系統(tǒng)中不存在的用戶,查詢時(shí)先走緩存,緩存中沒有,再去查數(shù)據(jù)庫;或者電商系統(tǒng)中,用戶搜索某類商品,但是這類商品再系統(tǒng)中根本不存在,這次的搜索應(yīng)該直接返回空
2、解決方案
- 網(wǎng)關(guān)層增加校驗(yàn),進(jìn)行用戶鑒權(quán),黑名單控制,接口流量控制
- 對于同一類查詢,如果緩存和數(shù)據(jù)庫都沒有獲取到數(shù)據(jù),那么可用用一個(gè)空緩存記錄下來,過期時(shí)間 60s,下次遇到同類查詢,直接取出緩存中的空數(shù)據(jù)返回即可
- 使用布隆過濾器,布隆過濾器可以用來判斷某個(gè)元素是否存在于集合中,利用布隆過濾器可以過濾掉一大部分無效請求
1、定義:
緩存擊穿是指,緩存中數(shù)據(jù)失效,在高并發(fā)情況下,所有用戶的請求全部都打到數(shù)據(jù)庫上,短時(shí)間造成數(shù)據(jù)庫壓力過大
2、解決方案:
- 接口限流、熔斷
- 加鎖,當(dāng)?shù)谝粋€(gè)用戶請求到時(shí),如果緩存中沒有,其他用戶的請求先鎖住,第一個(gè)用戶查詢數(shù)據(jù)庫后立即緩存到 Redis,然后釋放鎖,這時(shí)候其他用戶就可以直接查詢緩存
1、定義
緩存雪崩是指 Redis 中大批量的 key 在同一時(shí)間,或者某一段時(shí)間內(nèi)一起過期,造成多個(gè) key 的請求全部無法命中緩存,這些請求全部到數(shù)據(jù)庫中,給數(shù)據(jù)庫帶來很大壓力。與緩存擊穿不同,擊穿是指一個(gè) key 過期,雪崩是指很多 key 同時(shí)過期。
2、解決方案
- 緩存過期時(shí)間設(shè)置成不同時(shí)間,不要再統(tǒng)一時(shí)間過期
- 如果緩存數(shù)據(jù)庫是分布式部署,將熱點(diǎn)數(shù)據(jù)均勻分布在不同的緩存數(shù)據(jù)庫中。
1、定義
緩存污染是指,由于歷史原因,緩存中有很多 key 沒有設(shè)置過期時(shí)間,導(dǎo)致很多 key 其實(shí)已經(jīng)沒有用了,但是一直存放在 redis 中,時(shí)間久了,redis 內(nèi)存就被占滿了
2、解決方案
- 緩存盡量設(shè)置過期時(shí)間
- 設(shè)置緩存淘汰策略為最近最少使用的原則,然后將這些數(shù)據(jù)刪除
轉(zhuǎn)載自:說故事的五公子 文章來源于這次徹底讀透 Redis ! 原文鏈接:https://www.cnblogs.com/wugongzi/p/16827473.html