redis要點(diǎn)分析

redis要點(diǎn)分析

一、導(dǎo)語

redis(Remote Dictionary Server ),即遠(yuǎn)程字典服務(wù),是一個(gè)開源的使用ANSI?C語言編寫、支持網(wǎng)絡(luò)、可基于內(nèi)存亦可持久化的日志型、Key-Value數(shù)據(jù)庫,并提供多種語言的API。

(學(xué)習(xí)視頻分享:redis視頻教程

由于其上手快,執(zhí)行效率高,擁有多種數(shù)據(jù)結(jié)構(gòu),支持持久化以及集群等功能和特點(diǎn)被眾多互聯(lián)網(wǎng)公司所使用。但是,如果使用和操作不當(dāng),會(huì)引起內(nèi)存浪費(fèi),甚至系統(tǒng)宕機(jī)等嚴(yán)重后果。

二、要點(diǎn)分析

2.1 使用正確的數(shù)據(jù)類型

在 Redis 5 種數(shù)據(jù)類型中,string 類型最為常用,也最為簡單。但是,能解決問題不代表使用了正確的數(shù)據(jù)類型。

例如,將一個(gè)用戶(name,age,city)信息保存到 Redis 中,下邊有三種方案:

方案1:使用 string 類型,每個(gè)屬性當(dāng)作一個(gè) key

set?user:1:name?laowang set?user:1:age?40 set?user:1:city?shanghai

優(yōu)點(diǎn):簡單直觀,每個(gè)屬性支持更新操作
缺點(diǎn):使用過多的 key,占用的內(nèi)存較大,同時(shí)用戶信息的聚合性較差,管理和維護(hù)麻煩

方案2:使用 string 類型,將用戶信息序列化成字符串保存

//?序列化用戶信息 String?userInfo?=?serialize(user) set?user:1?userInfo

優(yōu)點(diǎn):簡化存儲(chǔ)步驟
缺點(diǎn):序列化和反序列化存在一定開銷

方案3:使用 hash 類型,每個(gè)屬性使用一對 field-value,但只用一個(gè) key

hmset?user:1?name?laowang?age?40?city?shanghai

優(yōu)點(diǎn):簡單直觀,合理使用可以減少內(nèi)存空間

總結(jié):盡量減少 Redis 中的 key。

2.2 警惕 Big Key

big key 一般指的是字符串類型 value 值非常大(大于10KB),或哈希、列表、集合、有序集合元素個(gè)數(shù)多(大于5000個(gè))的 key。

big key 會(huì)對 Redis 造成很多負(fù)面影響:

內(nèi)存不均:在集群環(huán)境下,big key 被分配到某個(gè)節(jié)點(diǎn)機(jī)器中,由于不知道被分配到哪個(gè)節(jié)點(diǎn)上且該節(jié)點(diǎn)內(nèi)存占用大,不利于集群環(huán)境下內(nèi)存的統(tǒng)一管理

超時(shí)阻塞:由于 Redis 是單線程操作,操作 big key 比較耗時(shí),容易造成阻塞

過期刪除:big key 不單讀寫慢,刪除也慢,刪除過期 big key 也比較耗時(shí)

遷移困難:由于數(shù)據(jù)龐大,備份和還原也容易造成阻塞,操作失敗

知道了 big key 的危害,我們?nèi)绾闻袛嗪筒樵?big key 呢?其實(shí),redis-cli 提供了 –bigkeys 參數(shù),鍵入 redis-cli –bigkeys 即可查詢出 big key 。

找到 big key 后,我們一般會(huì)將 big key 拆分成多個(gè)小 key 進(jìn)行存儲(chǔ)。這種做法似乎與 2.1 的總結(jié)相矛盾,但任何方案都有優(yōu)缺點(diǎn),衡量利弊取決于實(shí)際情況。

總結(jié):盡量減少 Redis 中的 big key。

補(bǔ)充:如果想查看某個(gè) key 所占用的內(nèi)存空間,可以使用 memory usage ?命令。注意:該命令是 Redis 4.0+ 才開始提供的,如想使用必須將 Redis 升級(jí)至 4.0+。

2.3 內(nèi)存消耗

即便我們合適使用正確的數(shù)據(jù)類型保存數(shù)據(jù),將 Big Key 拆分小 key,還是會(huì)出現(xiàn)內(nèi)存消耗問題,那么 Redis 內(nèi)存消耗是如何產(chǎn)生的呢?一般由以下 3 種情況產(chǎn)生:

業(yè)務(wù)不斷發(fā)展,存儲(chǔ)的數(shù)據(jù)不斷增多(不可避免)

無效/過期的數(shù)據(jù)沒有及時(shí)處理(可優(yōu)化)

沒有對冷數(shù)據(jù)進(jìn)行降級(jí)(可優(yōu)化)

在優(yōu)化情況 2 之前,我們得先知道為什么會(huì)出現(xiàn)沒有及時(shí)處理過期數(shù)據(jù)的問題,這得說到 Redis 提供的 3 種過期刪除策略:

定時(shí)刪除:對于每個(gè)設(shè)置了過期時(shí)間的 key 都會(huì)創(chuàng)建一個(gè)定時(shí)器,一旦達(dá)到過期時(shí)間就立即刪除

惰性刪除:當(dāng)訪問一個(gè) key 時(shí),才判斷該 key 是否已過期,如過期就刪除

定期刪除:每隔一段時(shí)間掃描 Redis 中過期 key 的字典,并清除部分過期的 key

由于定時(shí)刪除需要?jiǎng)?chuàng)建定時(shí)器,會(huì)占用的大量內(nèi)存,同時(shí)精準(zhǔn)刪除大量 key 也會(huì)消耗大量 CPU 資源,因此 Redis 同時(shí)采用的是惰性刪除和定時(shí)刪除兩種策略。如果客戶端沒有請求過期的 key 或定期刪除線程沒有掃描到并清除這個(gè) key,該 key 就會(huì)一直占用著內(nèi)存,導(dǎo)致內(nèi)存浪費(fèi)。

知道了內(nèi)存消耗的原因后,我們可以很快地想出優(yōu)化方案:手動(dòng)刪除。

當(dāng)使用完緩存后,緩存即使設(shè)置了過期時(shí)間,我們也要手動(dòng)調(diào)用 del ?方法/命令刪除。如果不能當(dāng)場刪除,我們也可在代碼中開啟定時(shí)器定期刪除這些過期的 key,相比較 Redis 的兩種刪除策略,手動(dòng)清除數(shù)據(jù)要及時(shí)很多。

情況 3 的問題不算大,針對其優(yōu)化的手段,我們可以調(diào)整 Redis 的淘汰策略。

2.4 多命令的執(zhí)行

Redis 是基于一個(gè) request, 一個(gè) response 的同步請求服務(wù)。即當(dāng)多個(gè)客戶端向 Redis 服務(wù)端發(fā)送命令時(shí),Redis 服務(wù)端只能接收和處理其中的一個(gè)客戶端的命令,其他客戶端只能等待 Redis 服務(wù)端處理好當(dāng)前的命令并作出響應(yīng)后才會(huì)繼續(xù)接收和處理其他命令請求。

Redis 處理命令分 3 個(gè)過程:接收命令,處理命令,返回結(jié)果。由于處理的數(shù)據(jù)都是在內(nèi)存中的,因此處理時(shí)長通常都是納秒級(jí)別,非???big key 除外)。因此,大部分耗時(shí)的情況都發(fā)生在接受命令和返回結(jié)果上。當(dāng)客戶端發(fā)送多個(gè)命令給 Redis 服務(wù)器時(shí),如果有一條命令處理時(shí)長很久,其他命令只能等待著,從而影響整體性能。

為了解決這類問題,Redis 提供了 pipeline(管道),客戶端可以將多條命令放入 pipeline 中,然后一次性將 pipeline 的命令發(fā)給 Redis 服務(wù)端處理,當(dāng) Redis 服務(wù)端處理完畢后再一次性將結(jié)果返回給客戶端。這樣處理減少了客戶端與 Redis 服務(wù)端的交互次數(shù),從而減少了往返時(shí)間,提升了性能。

補(bǔ)充:

Redis pipeline 與原生批量命令對比:

原生批量命令是原子性,pipeline 是非原子性

原生批量命令一次只能執(zhí)行一種命令,pipeline 支持執(zhí)行多種命令

原生批量命令是服務(wù)端實(shí)現(xiàn),pipeline 需要服務(wù)端和客戶端實(shí)現(xiàn)

使用 Redis pipeline 的注意事項(xiàng):

使用 pipeline 裝載的命令數(shù)量不能太多

pipeline 中的命令會(huì)按照緩沖的順序執(zhí)行,但是可能會(huì)穿插其他客戶端發(fā)來的命令,即不保證時(shí)序性

pipeline 執(zhí)行中間某一指令出現(xiàn)異常,會(huì)繼續(xù)執(zhí)行后續(xù)的指令,即不保證原子性

2.5 緩存穿透

在項(xiàng)目中運(yùn)用緩存,我們通常的設(shè)計(jì)思路如下圖:

redis要點(diǎn)分析

發(fā)送請求查詢數(shù)據(jù),查詢規(guī)則是先查緩存,如果緩存沒有數(shù)據(jù)再查詢數(shù)據(jù)庫,將查到的數(shù)據(jù)放入緩存最后返回?cái)?shù)據(jù)給客戶端。如果請求的數(shù)據(jù)是不存在的,最終每次請求都會(huì)請求到數(shù)據(jù)庫中,這就是緩存穿透。

緩存穿透存到很大的安全隱患,如果有人使用工具發(fā)送大量請求,請求一個(gè)不存在的數(shù)據(jù),大量請求會(huì)流入到數(shù)據(jù)庫上,導(dǎo)致數(shù)據(jù)庫壓力增大,可能會(huì)導(dǎo)致數(shù)據(jù)庫宕機(jī),進(jìn)而影響整個(gè)應(yīng)用的正常運(yùn)行,導(dǎo)致系統(tǒng)癱瘓。

解決這類問題,重點(diǎn)在于減少對數(shù)據(jù)庫的訪問,通常有以下幾種方案:

緩存預(yù)熱:系統(tǒng)發(fā)布上線后,提前把相關(guān)的數(shù)據(jù)直接加載到緩存系統(tǒng)中

設(shè)置默認(rèn)值:如果請求最終落在數(shù)據(jù)庫中,數(shù)據(jù)庫也查不出數(shù)據(jù),給緩存 key 設(shè)置一個(gè)默認(rèn)值,放入緩存中,注意:由于這個(gè)默認(rèn)值是無意義的,因此我們需要設(shè)置過期時(shí)間,減少內(nèi)存占用

布隆過濾器:將所有可能存在的數(shù)據(jù)哈希到一個(gè)足夠大的 bitmap 中,一個(gè)不存在的數(shù)據(jù)肯定會(huì)被 bitmap 攔截掉

2.6 緩存雪崩

緩存雪崩: 簡單來說是指大量請求訪問緩存數(shù)據(jù)但無法查詢到,進(jìn)而去請求數(shù)據(jù)庫,導(dǎo)致數(shù)據(jù)庫壓力增大,性能下降,不堪重負(fù)宕機(jī),從而影響整個(gè)系統(tǒng)正常運(yùn)行,甚至系統(tǒng)癱瘓的現(xiàn)象。

比如,一個(gè)完整的系統(tǒng)由系統(tǒng)A,系統(tǒng)B,系統(tǒng)C 三個(gè)子系統(tǒng)組成,它們的數(shù)據(jù)請求鏈?zhǔn)窍到y(tǒng)A -> 系統(tǒng)B -> 系統(tǒng)C -> 數(shù)據(jù)庫。如果緩存中沒有數(shù)據(jù),數(shù)據(jù)庫宕機(jī),系統(tǒng)C不能查詢數(shù)據(jù)作出響應(yīng),只能處在重試等待的階段,從而影響了系統(tǒng)B 和系統(tǒng)A。一處節(jié)點(diǎn)發(fā)生異常導(dǎo)致一連串的問題就像雪山的一陣風(fēng)吹過引起雪崩的現(xiàn)象。

看到這里,可能有讀者會(huì)疑惑,緩存穿透和緩存雪崩有什么區(qū)別呢?

緩存穿透側(cè)重于請求的數(shù)據(jù)不在緩存中,從而去請求數(shù)據(jù)庫,就好像直接透過緩存直接請求數(shù)據(jù)庫。

緩存雪崩側(cè)重于大請求由于在緩存中查詢不出數(shù)據(jù),從而訪問數(shù)據(jù)庫導(dǎo)致數(shù)據(jù)庫壓力增大引起一系列異常。

要解決緩存雪崩問題,還是得先知道導(dǎo)致問題的原因:

Redis 自身出現(xiàn)問題

熱點(diǎn)數(shù)據(jù)集中失效

針對原因1,我們可以做主從,集群,盡量讓請求都在緩存中查到數(shù)據(jù),減少對數(shù)據(jù)庫的訪問

針對原因2,給緩存設(shè)置過期時(shí)間時(shí),錯(cuò)開過期時(shí)間(如在基礎(chǔ)時(shí)間上在增減一個(gè)隨機(jī)值),避免緩存集中失效。同時(shí),我們還可以設(shè)置本地緩存(如 ehcache),對接口進(jìn)行限流或服務(wù)降級(jí),也可以減少數(shù)據(jù)庫的訪問壓力。

三、參考資料

redis視頻教程

redis視頻教程

相關(guān)推薦:redis視頻教程

以上就是

? 版權(quán)聲明
THE END
喜歡就支持一下吧
點(diǎn)贊15 分享