自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Redis 核心知識(shí)點(diǎn)深度剖析:原理、機(jī)制與應(yīng)用

開發(fā) Redis
本文將深入探討Redis的核心知識(shí)點(diǎn),帶你領(lǐng)略其內(nèi)部的奧秘,助力你在數(shù)據(jù)處理的領(lǐng)域中如魚得水。

在當(dāng)今數(shù)字化浪潮洶涌澎湃的時(shí)代,數(shù)據(jù)如同企業(yè)的生命線,高效的數(shù)據(jù)存儲(chǔ)、處理與管理成為眾多應(yīng)用程序成功的關(guān)鍵。在這數(shù)據(jù)管理的競(jìng)技場(chǎng)上,Redis作為一款開源的內(nèi)存數(shù)據(jù)結(jié)構(gòu)存儲(chǔ)系統(tǒng),憑借其卓越的性能、豐富的數(shù)據(jù)結(jié)構(gòu)和強(qiáng)大的功能,脫穎而出,成為開發(fā)者手中的得力利器。無論是高并發(fā)場(chǎng)景下的數(shù)據(jù)緩存,還是實(shí)時(shí)數(shù)據(jù)分析、消息隊(duì)列等應(yīng)用,Redis都展現(xiàn)出無可替代的價(jià)值。

本文將深入探討Redis的核心知識(shí)點(diǎn),帶你領(lǐng)略其內(nèi)部的奧秘,助力你在數(shù)據(jù)處理的領(lǐng)域中如魚得水。

一、詳解Redis基礎(chǔ)知識(shí)點(diǎn)

1. 為什么Redis被設(shè)計(jì)成是單線程的

redis本質(zhì)上都是在內(nèi)存操作,性能瓶頸不在CPU,通過單線程處理客戶端指令可以避免線程上下文切換開銷。

此時(shí)如果使用多線程進(jìn)行操作,勢(shì)必要保護(hù)臨界資源并發(fā)安全而采用較粗力度的鎖,由此導(dǎo)致的大量線程阻塞爭(zhēng)搶臨界資源而導(dǎo)致操作各種大耗時(shí)操作顯然是得不償失的,并且多線程操作一般會(huì)引入各種同步原語(yǔ),對(duì)于我們這種動(dòng)輒十幾萬的內(nèi)存數(shù)據(jù)庫(kù)問題的定位和排查的難度都會(huì)大大增加。

2. 為什么Redis單線程也能這么快

  • 通過IO多路復(fù)用保證單線程處理多連接
  • 數(shù)據(jù)結(jié)構(gòu)做了極致的優(yōu)化
  • 活躍于內(nèi)存即純粹的內(nèi)存操作,性能表現(xiàn)出色
  • 單線程處理所有指令,避免線程上下文切換和同步原語(yǔ)的使用的開銷

3. 說說Redis6.0中的多線程

Redis6.0的多線程是用多線程來處理數(shù)據(jù)的讀寫和協(xié)議解析,但是Redis執(zhí)行命令還是單線程的:

4. Redis管道pipeline的概念

特定場(chǎng)景下我們某些業(yè)務(wù)需要針對(duì)redis執(zhí)行多條指令,按照傳統(tǒng)做法我們需要逐條發(fā)送指令,這樣的做法使得一個(gè)業(yè)務(wù)針對(duì)redis的操作存在多次網(wǎng)絡(luò)往返即多次RTT:

于是就有了pipeline的概念,通過管道一次性將要執(zhí)行的多條命令發(fā)送給服務(wù)端,其作用是為了降低 RTT(Round Trip Time) 對(duì)性能的影響,redis收到這些指令之后會(huì)依次執(zhí)行并響應(yīng)給客戶端:

Redis 服務(wù)端接收到管道發(fā)送過來的多條命令后,會(huì)一直執(zhí)命令,并將命令的執(zhí)行結(jié)果進(jìn)行緩存,直到最后一條命令執(zhí)行完成,再所有命令的執(zhí)行結(jié)果一次性返回給客戶端 。

在性能方面, pipeline 有下面兩個(gè)優(yōu)勢(shì):

  • 節(jié)省了RTT:將多條命令打包一次性發(fā)送給服務(wù)端,減少了客戶端與服務(wù)端之間的網(wǎng)絡(luò)調(diào)用次數(shù)。
  • 減少了上下文切換:當(dāng)客戶端/服務(wù)端需要從網(wǎng)絡(luò)中讀寫數(shù)據(jù)時(shí),都會(huì)產(chǎn)生一次系統(tǒng)調(diào)用,系統(tǒng)調(diào)用是非常耗時(shí)的操作,其中設(shè)計(jì)到程序由用戶態(tài)切換到內(nèi)核態(tài),再?gòu)膬?nèi)核態(tài)切換回用戶態(tài)的過程。當(dāng)我們執(zhí)行 10 條 redis 命令的時(shí)候,就會(huì)發(fā)生 10 次用戶態(tài)到內(nèi)核態(tài)的上下文切換,但如果我們使用 pipeline將多條命令打包成一條一次性發(fā)送給服務(wù)端,就只會(huì)產(chǎn)生一次上下文切換。

唯一需要注意的是redis的pipeline不保證原子性,即使我們通過pipeline處理多條指令,它也是逐條執(zhí)行的,這一點(diǎn)我們還是需要注意一下。

5. Redis如何保證命令原子性

使用原子命令:

  • Redis 提供了 INCR/DECR/SETNX 命令,把RMW三個(gè)操作轉(zhuǎn)變?yōu)橐粋€(gè)原子操作
  • Redis 是使用單線程串行處理客戶端的請(qǐng)求來操作命令,所以當(dāng) Redis 執(zhí)行某個(gè)命令操作時(shí),其他命令是無法執(zhí)行的,這相當(dāng)于命令操作是互斥執(zhí)行的.

加鎖:

加鎖主要是考慮多個(gè)客戶端對(duì)相同業(yè)務(wù)方法進(jìn)行修改操作,我們可以使用加鎖的方式保證原子性,大致的方式為:

  • 使用setnx上鎖
  • 上鎖成功后,執(zhí)行業(yè)務(wù)修改操作
  • 使用del釋放鎖

這期間你可能會(huì)遇到兩個(gè)問題:

  • 假如在操作期間出現(xiàn)了業(yè)務(wù)異常(或者服務(wù)器宕機(jī)了),就會(huì)導(dǎo)致key未能及時(shí)釋放,進(jìn)而導(dǎo)致鎖無法釋放,我們必須對(duì)這個(gè)鎖設(shè)置時(shí)效,并且在操作期間定時(shí)監(jiān)測(cè)和續(xù)命。
SET key value [EX seconds | PX milliseconds] [NX]

誤刪除,比如用戶1持有鎖,用戶2拿不到鎖,用del命令把這個(gè)鎖刪除,對(duì)此我們可以使用setnx的value比對(duì)看看上鎖和用戶和解鎖的用戶是不是同一個(gè)進(jìn)行進(jìn)一步的操作。

SET lock_key unique_value NX PX 10000

使用lua腳本:多個(gè)操作寫到一個(gè) Lua 腳本中(Redis 會(huì)把整個(gè) Lua 腳本作為一個(gè)整體執(zhí)行,在執(zhí)行的過程中不會(huì)被其他命令打斷,從而保證了 Lua 腳本中操作的原子性)

local current current = redis.call("incr",KEYS[1]) 
if tonumber(current) == 1 
then redis.call("expire",KEYS[1],60) 
end

6. Redis 使用什么協(xié)議進(jìn)行通信

是resp自己設(shè)計(jì)的RESP協(xié)議,該協(xié)議的特點(diǎn)為:

  • 簡(jiǎn)單
  • 高效
  • 易于解析
  • 保證二進(jìn)制安全

7. Redis 與 Memcached 有什么區(qū)別

  • 數(shù)據(jù)結(jié)構(gòu)層面:redis支持多種數(shù)據(jù)結(jié)構(gòu)例如字符串、列表、集合、有序集合、哈希,Memcached 僅僅支持簡(jiǎn)單的鍵值對(duì)存儲(chǔ)。
  • 持久化層面:Redis 支持RDB或者AOF的方式進(jìn)行持久化,后者不支持持久化。
  • 數(shù)據(jù)分片層面:redis通過hash slot實(shí)現(xiàn)自動(dòng)分片和負(fù)載均衡,而后者只能手動(dòng)進(jìn)行分片。
  • 處理數(shù)據(jù)的方式:redis通過單線程處理所有的指令,并且支持事務(wù)、lua腳本等高級(jí)功能,而后者使用多線程處理請(qǐng)求,且僅僅支持get、set操作。
  • 協(xié)議:redis使用自定義的resp協(xié)議、同時(shí)支持多個(gè)數(shù)據(jù)庫(kù)并且支持密碼認(rèn)證,而后者僅僅支持文本協(xié)議且只有一個(gè)默認(rèn)的數(shù)據(jù)庫(kù)。
  • 內(nèi)存管理:redis內(nèi)存層面各種緩存置換、數(shù)據(jù)持久化等策略相比后者更加健壯和復(fù)雜。

8. Redis為什么這么快

  • 操作數(shù)據(jù)活躍于內(nèi)存:通過內(nèi)存進(jìn)行數(shù)據(jù)操作速度遠(yuǎn)快于硬盤訪問速度。
  • 單線程:通過單線程處理所有客戶端請(qǐng)求,避免線程上下文切換開銷,大大提高的redis的運(yùn)行效率和響應(yīng)速度。
  • IO多路復(fù)用:以Linux系統(tǒng)為例,redis通過epoll模型實(shí)現(xiàn)單線程處理大量客戶端并發(fā)請(qǐng)求,提升了redis的并發(fā)性能。
  • 數(shù)據(jù)結(jié)構(gòu):redis提供了各種各樣的數(shù)據(jù)結(jié)構(gòu),并且針對(duì)這些數(shù)據(jù)類型都進(jìn)行了各種極致的優(yōu)化,例如哈希對(duì)象,在數(shù)據(jù)大小較小的情況下使用壓縮列表,一旦數(shù)據(jù)大小達(dá)到閾值后就會(huì)轉(zhuǎn)為哈希集。
  • 6.0引入多線程:在高并發(fā)場(chǎng)景下,性能的瓶頸往往處于網(wǎng)絡(luò)連接上,為了進(jìn)一步提升IO性能,redis通過多線程來充分利用CPU核心處理盡可能多個(gè)客戶端連接。

9. Redis 支持哪幾種數(shù)據(jù)類型

比較常見的有:

  • 字符串
  • 列表
  • 集合
  • 有序集合
  • 字典

需要補(bǔ)充的是redis還有一些高級(jí)的數(shù)據(jù)結(jié)構(gòu)例如:

  • stream
  • bitmap
  • Geo
  • HyperLogLog

10. Redis為什么要自己定義SDS

這里我們直接引用《redis設(shè)計(jì)與實(shí)現(xiàn)》一書中的說法:

  • C語(yǔ)言的字符串用\0收尾,在redis的使用場(chǎng)景下,很可能因?yàn)檫@個(gè)結(jié)束符導(dǎo)致數(shù)據(jù)被截?cái)唷?/li>
  • C語(yǔ)言字符串獲取長(zhǎng)度需要進(jìn)行遍歷,即O(n)級(jí)別的時(shí)間復(fù)雜度。
  • C語(yǔ)言進(jìn)行字符串拼接總是需要預(yù)先做好分配,否則很容易出現(xiàn)緩沖區(qū)溢出的問題。對(duì)于不斷擴(kuò)大的字符串還需要反復(fù)創(chuàng)建新的字符數(shù)組解決問題。

11. Redis中的Zset是怎么實(shí)現(xiàn)的

這個(gè)問題我們可以針對(duì)不同的版本進(jìn)行回答:

  • 在5.0之前:有序集合在數(shù)據(jù)體積不是很大的情況下,通過ziplist或dict+skiplist的方式實(shí)現(xiàn)有序集合。
  • 7.0 之后:完全取消了壓縮列表,改為dict+listpack/skiplist。

以我們最常用的版本,即5.0左右的版本,本質(zhì)上redis的有序集合是通過dict保證O(1)級(jí)別的直接映射定位,通過跳表實(shí)現(xiàn)O(logN)級(jí)別的范圍有序查詢。

12. 什么是GEO,有什么用

用于表示地理坐標(biāo)信息,從而實(shí)現(xiàn)經(jīng)緯度數(shù)據(jù)檢索,它主要支持的命令有: Redis 的 GEO 模塊提供了一系列用于處理地理位置數(shù)據(jù)的命令。以下是一些常見的 GEO 命令:

  • GEOADD:將一個(gè)或多個(gè)地理空間元素(經(jīng)度、緯度和成員)添加到指定的鍵中。
GEOADD key longitude latitude member [longitude latitude member ...]
  • GEODIST:返回兩個(gè)給定成員之間的距離。如果其中任何一個(gè)成員不存在,則返回空值。
GEODIST key member1 member2 [unit]
  • GEOHASH:返回一個(gè)或多個(gè)成員的 Geohash 表示形式。
GEOHASH key member [member ...]
  • GEOPOS:返回一個(gè)或多個(gè)成員的位置(經(jīng)度和緯度)。如果成員不存在,則返回空值。
GEOPOS key member [member ...]
  • GEORADIUS:以給定的經(jīng)緯度為中心,返回指定半徑范圍內(nèi)的所有成員。
GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]
  • GEORADIUSBYMEMBER:以給定成員的位置為中心,返回指定半徑范圍內(nèi)的所有成員。
GEORADIUSBYMEMBER key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]

這些命令可以幫助您在 Redis 中高效地存儲(chǔ)和查詢地理位置信息。

13. 為什么Redis 6.0引入了多線程

redis處理能力即qps大約在8w-10w之間,對(duì)于某些高并發(fā)存在大量客戶端連接的請(qǐng)求,本質(zhì)上可以通過增加實(shí)例解決,但是這種做法在資源消耗和成本無疑是非常大的:

經(jīng)過分析,這些場(chǎng)景大概率性能瓶頸在連接處理上,雖說redis采用epoll等多路復(fù)用技術(shù),但epoll本質(zhì)還是一個(gè)同步阻塞IO模型,所以redis增加多個(gè)線程,充分利用CPU核心,從而減少網(wǎng)絡(luò)等待的影響,提升程序執(zhí)行性能。

14. 為什么Lua腳本可以保證原子性

redis針對(duì)lua腳本的處理上,會(huì)一次性將lua腳本封裝成一個(gè)單獨(dú)的事務(wù),從而保證操作的指令執(zhí)行的原子性但不保證發(fā)生錯(cuò)誤后的回滾兜底。

15. Redis中的setnx命令為什么是原子性的

以下兩個(gè)原因保證了setnx的原子性:

  • 該指令只有在key不存在時(shí)才會(huì)插入/
  • 單機(jī)情況下redis是單線程執(zhí)行,所以保證執(zhí)行執(zhí)行的有序性,間接保證臨界資源操作的線程安全。

16. Redis 5.0中的 Stream是什么

5.0版本新增的數(shù)據(jù)結(jié)構(gòu),主要用于處理有序且可追朔的消息流,每個(gè)消息都有唯一的id,按照添加順序進(jìn)行排序,并且開發(fā)人員可以從中添加、讀取和刪除消息,同時(shí)它還是支持讓多個(gè)消費(fèi)者并發(fā)的處理消息流。 在5.0之前redis通過使用發(fā)布訂閱模型實(shí)現(xiàn)消息隊(duì)列,但缺點(diǎn)是不支持持久化,如果出現(xiàn)網(wǎng)絡(luò)斷開、redis宕機(jī)等情況,就會(huì)造成消息丟失。 而stream提供了消息持久化和主從復(fù)制功能保證消息不丟失,保證客戶端可以訪問任何時(shí)刻的數(shù)據(jù),并且還能記住訪問位置。

總的來說,stream有幾個(gè)幾個(gè)優(yōu)點(diǎn):

  • 有序性
  • 多消費(fèi)者支持
  • 持久化
  • 支持消息分組

17. Redis的虛擬內(nèi)存機(jī)制是什么

2.4 之前的版本,redis提供了一種虛擬內(nèi)存的機(jī)制,當(dāng)內(nèi)存空間不足時(shí),將部分?jǐn)?shù)據(jù)持久化到磁盤上,避免redis進(jìn)程占用過多的內(nèi)存。

18. Redis的持久化機(jī)制是怎樣的

  • rdb:按照協(xié)議規(guī)范定期生成持久化二進(jìn)制數(shù)據(jù),文件小,恢復(fù)速度快,適合做備份和災(zāi)難恢復(fù),當(dāng)然缺點(diǎn)也很明顯,定期更新可能導(dǎo)致丟失某一部分的數(shù)據(jù)。
  • aof:實(shí)時(shí)完成指令持久化,有著更高的數(shù)據(jù)可靠性和更細(xì)粒度的數(shù)據(jù)恢復(fù),缺點(diǎn)是文件可能占用空間更多,每次寫操作都需要寫磁盤導(dǎo)致負(fù)載過高。

19. Redis 的事務(wù)機(jī)制是怎樣的

掌握 Redis 事務(wù),提升數(shù)據(jù)處理效率的必備秘籍

20. Redis的定期內(nèi)存淘汰策略是怎么樣的

redis通過定期刪除和惰性刪除處理過期key:

  • 定期刪除:redis的serverCron函數(shù)會(huì)每個(gè)100ms隨機(jī)抽檢一些key查看是否過期,如果過期則將這些key刪除,通過隨機(jī)抽檢保證單線程執(zhí)行不會(huì)阻塞。
  • 惰性刪除:當(dāng)用戶查詢某個(gè)key的時(shí)候,redis函數(shù)會(huì)檢查該key是否會(huì)過期,如果過期則將其刪除并返回nil。

Redis 的內(nèi)存淘汰策略用于在內(nèi)存不足時(shí)決定如何移除數(shù)據(jù),以確保 Redis 可以繼續(xù)正常運(yùn)行。以下是 Redis 支持的主要內(nèi)存淘汰策略:

  • noeviction:默認(rèn)策略,當(dāng)達(dá)到最大內(nèi)存限制時(shí),任何寫入操作都會(huì)返回錯(cuò)誤(讀取操作仍然可以進(jìn)行)。
  • allkeys-lru:從所有鍵中使用最近最少使用的算法來驅(qū)逐鍵。
  • volatile-lru:僅從設(shè)置了過期時(shí)間的鍵中使用最近最少使用的算法來驅(qū)逐鍵。
  • allkeys-random:從所有鍵中隨機(jī)選擇鍵來驅(qū)逐。
  • volatile-random:僅從設(shè)置了過期時(shí)間的鍵中隨機(jī)選擇鍵來驅(qū)逐。
  • volatile-ttl:優(yōu)先根據(jù)剩余生存時(shí)間(TTL)來驅(qū)逐鍵,即 TTL 較短的鍵會(huì)被優(yōu)先驅(qū)逐。

這些策略可以在 Redis 配置文件 redis.conf 中通過 maxmemory-policy 參數(shù)設(shè)置。選擇合適的淘汰策略取決于具體的應(yīng)用場(chǎng)景和需求。例如,如果希望盡可能保留熱點(diǎn)數(shù)據(jù),可以選擇 allkeys-lru 或 volatile-lru;如果希望更公平地處理所有數(shù)據(jù),則可以選擇 allkeys-random 或 volatile-random。

21. Redis如何實(shí)現(xiàn)發(fā)布/訂閱

redis發(fā)布訂閱是通過pub和sub指令實(shí)現(xiàn)的,如果客戶端對(duì)某個(gè)事件感興趣可以通過sub訂閱,這些客戶端就會(huì)存儲(chǔ)到主題的channel中的鏈表,一旦有發(fā)送者用pub消息,channel就會(huì)遍歷訂閱者通知消息。

當(dāng)然隨著stream的出現(xiàn),可能更多的企業(yè)會(huì)考慮使用更可靠的stream實(shí)現(xiàn)發(fā)布訂閱。

22. 除了做緩存,Redis還能用來干什么

  • 消息隊(duì)列
  • 延遲隊(duì)列
  • 排行版
  • 分布式id
  • 分布式鎖
  • 地理位置運(yùn)用
  • 分布式限流
  • 分布式session
  • 布隆過濾器
  • 狀態(tài)統(tǒng)計(jì)
  • 共同關(guān)注
  • 推薦關(guān)注
  • 數(shù)據(jù)庫(kù)

23. 為什么ZSet 既能支持高效的范圍查詢,還能以 O(1) 復(fù)雜度獲取元素權(quán)重值?

底層數(shù)據(jù)結(jié)構(gòu)由字典和調(diào)表構(gòu)成,兩者共同維護(hù)持有元素指針,當(dāng)進(jìn)行鍵定位時(shí)通過字典的哈希算法完成O(1)級(jí)別的定位,當(dāng)需要有序的范圍查詢時(shí),又可以通過跳表完成O(logN)級(jí)別的范圍檢索定位。

24. 什么是Redis的漸進(jìn)式rehash

redis底層字典本質(zhì)上是通過數(shù)組+哈希算法和拉鏈法解決沖突,隨著時(shí)間推移可能會(huì)重現(xiàn)大量的鏈表導(dǎo)致查詢性能下降,又因?yàn)閞edis是單線程,為避免哈希表擴(kuò)容耗時(shí)長(zhǎng)導(dǎo)致性能下降,redis采用漸進(jìn)式哈希逐步遷移數(shù)據(jù)到新表。

對(duì)于源碼感興趣的讀者可以參考這篇文章:《聊聊redis中的字典設(shè)計(jì)與實(shí)現(xiàn)

25. Redis中key過期了一定會(huì)立即刪除嗎

不一定,serverCron的定時(shí)函數(shù)會(huì)批量抽取一批key進(jìn)行檢查然后刪除。

26. Redis中有一批key瞬間過期,為什么其它key的讀寫效率會(huì)降低

出現(xiàn)讀寫效率低,大體是因?yàn)橹鲃?dòng)過期即用戶手動(dòng)提交一批刪除過期key的任務(wù),因?yàn)閞edis的單線程的原因,對(duì)于瞬時(shí)的過期key操作勢(shì)必出現(xiàn)大量指令需要處理,這時(shí)候就會(huì)對(duì)其他客戶端的讀寫請(qǐng)求造成一定的阻塞,對(duì)此我們的解決策略大體有:

  • 設(shè)置時(shí)間為隨機(jī)過期
  • 采用被動(dòng)過期設(shè)置key,即通過redis ex指令完成

27. 什么是Redis的Pipeline,和事務(wù)有什么區(qū)別

redis的pipeline主要為了解決網(wǎng)絡(luò)延遲的技術(shù),客戶端可以一次性批量提交請(qǐng)求,且無需等待每個(gè)命令的響應(yīng),redis收到這些請(qǐng)求后會(huì)依次執(zhí)行并返回,需要注意的是該操作與事務(wù)不同的是它不保證操作處理的原子性,唯一與事務(wù)的相同點(diǎn)都是一條指令失敗后,后續(xù)的指令都還會(huì)執(zhí)行且不會(huì)回滾操作。

28. Redis的事務(wù)和Lua之間有哪些區(qū)別

事務(wù)和lua之間的相同點(diǎn)是兩者都可以保證操作的原子性,不同點(diǎn)是前者一條指令失敗不影響后續(xù)指令的執(zhí)行,而后者反之。

29. 為什么Redis不支持回滾

本質(zhì)來說redis支持組隊(duì)時(shí)事務(wù)異常回滾,但是不支持執(zhí)行時(shí)異?;貪L,設(shè)計(jì)者針對(duì)這種情況也給出相應(yīng)的原因:

  • redis的設(shè)計(jì)初衷就是為了簡(jiǎn)單、高效,過于復(fù)雜的事務(wù)實(shí)現(xiàn)會(huì)讓系統(tǒng)復(fù)雜并影響性能
  • 從使用場(chǎng)景來說,redis本質(zhì)上就是一個(gè)緩存工具,不需要復(fù)雜的事務(wù)支持
  • redis中出錯(cuò)的問題基本上都是指令不正確,這些問題一般都需要預(yù)先解決,而不是依靠事務(wù)

30. 關(guān)于redis中的布隆過濾器

布隆過濾器是一種概率性的數(shù)據(jù)結(jié)構(gòu),用戶快速判斷一個(gè)元素是否存在于某個(gè)集合中,它的特點(diǎn)是:

  • 通過盡可能少的物理空間維護(hù)盡可能多的數(shù)據(jù)的存在情況
  • 允許誤判(這一點(diǎn)后續(xù)會(huì)補(bǔ)充)
  • 無法進(jìn)行元素刪除

對(duì)于redis而言實(shí)現(xiàn)布隆過濾器的方式有兩種:

  • 基于bitmap結(jié)合多個(gè)哈希函數(shù)模擬布隆過濾器
  • 引入redis官方的redisBloom模塊,對(duì)應(yīng)的操作指令示例如下:
BF.ADD myfilter "user123"  # 添加元素
BF.EXISTS myfilter "user123"  # 檢查是否存在

我們來一個(gè)實(shí)際的場(chǎng)景,例如我們要統(tǒng)計(jì)系統(tǒng)中千萬用戶是否在線,我們就布隆過濾器進(jìn)行記錄和維護(hù),整體的流程比較簡(jiǎn)單:

  • 通過多次哈希運(yùn)算定位當(dāng)前用戶id對(duì)應(yīng)的布隆過濾器中的位置。
  • 定位到bit array將索引i位置標(biāo)記為1。

需要了解的是布隆過濾器在進(jìn)行哈希的時(shí)候是可能存在碰撞的,例如id為1和id為13232的用戶可能因?yàn)楣K惴▽?dǎo)致的bitmap索引位是一樣的,所以我們可以得出以下結(jié)論:

  • 當(dāng)布隆過濾器認(rèn)為數(shù)據(jù)不存在的時(shí)候,它100%不存在。
  • 當(dāng)布隆過濾器認(rèn)為數(shù)據(jù)存在的時(shí)候,它不一定存在。

二、詳解Redis持久化機(jī)制

1. Redis持久化方式有哪些?有什么區(qū)別?

持久化分為rdb和aof兩種。

RDB持久化是把當(dāng)前進(jìn)程數(shù)據(jù)生成快照保存到硬盤的過程,觸發(fā)RDB持久化過程分為手動(dòng)觸發(fā)和自動(dòng)觸發(fā)。分別使用命令save或者bgsave。 同時(shí)rdb是一個(gè)二進(jìn)制的壓縮文件,

以下幾個(gè)場(chǎng)景會(huì)自動(dòng)觸發(fā)rdb持久化:

  • 使用save相關(guān)配置,如“save m n”。表示m秒內(nèi)數(shù)據(jù)集存在n次修改時(shí),自動(dòng)觸發(fā)bgsave。
  • 如果從節(jié)點(diǎn)執(zhí)行全量復(fù)制操作,主節(jié)點(diǎn)自動(dòng)執(zhí)行bgsave生成RDB文件并發(fā)送給從節(jié)點(diǎn)
  • 執(zhí)行debug reload命令重新加載Redis時(shí),也會(huì)自動(dòng)觸發(fā)save操作
  • 默認(rèn)情況下執(zhí)行shutdown命令時(shí),如果沒有開啟AOF持久化功能則自動(dòng)執(zhí)行bgsave。

而AOF則是以獨(dú)立日志的方式記錄每次寫命令, 重啟時(shí)再重新執(zhí)行AOF文件中的命令達(dá)到恢復(fù)數(shù)據(jù)的目的,整體工作過程為:

  • 所有的寫入命令會(huì)追加到aof_buf(緩沖區(qū))中。
  • AOF緩沖區(qū)根據(jù)對(duì)應(yīng)的策略向硬盤做同步操作。
  • 隨著AOF文件越來越大,需要定期對(duì)AOF文件進(jìn)行重寫,達(dá)到壓縮 的目的。
  • 當(dāng)Redis服務(wù)器重啟時(shí),可以加載AOF文件進(jìn)行數(shù)據(jù)恢復(fù)。

2. rdb和aof各自有什么優(yōu)缺點(diǎn)?

rdb優(yōu)點(diǎn):

  • 只有一個(gè)緊湊的二進(jìn)制文件 dump.rdb,非常適合備份、全量復(fù)制的場(chǎng)景。
  • 容災(zāi)性好,可以把RDB文件拷貝道遠(yuǎn)程機(jī)器或者文件系統(tǒng)張,用于容災(zāi)恢復(fù)。
  • 恢復(fù)速度快,RDB恢復(fù)數(shù)據(jù)的速度遠(yuǎn)遠(yuǎn)快于AOF的方式

rdb的缺點(diǎn):

  • 實(shí)時(shí)性低,RDB 是間隔一段時(shí)間進(jìn)行持久化,沒法做到實(shí)時(shí)持久化/秒級(jí)持久化。如果在這一間隔事件發(fā)生故障,數(shù)據(jù)會(huì)丟失。
  • 存在兼容問題,Redis演進(jìn)過程存在多個(gè)格式的RDB版本,存在老版本Redis無法兼容新版本RDB的問題。

aof優(yōu)點(diǎn):

  • 實(shí)時(shí)性好,aof 持久化可以配置 appendfsync 屬性,有 always,每進(jìn)行一次命令操作就記錄到 aof 文件中一次。
  • 通過 append 模式寫文件,即使中途服務(wù)器宕機(jī),可以通過 redis-check-aof 工具解決數(shù)據(jù)一致性問題。

aof缺點(diǎn):

  • AOF文件比RDB 文件大,且 恢復(fù)速度慢。
  • 數(shù)據(jù)集大的時(shí)候,比RDB 啟動(dòng)效率低。

3. rdb和aof如何選擇

如果想達(dá)到足以媲美數(shù)據(jù)庫(kù)的 數(shù)據(jù)安全性,應(yīng)該 同時(shí)使用兩種持久化功能。在這種情況下,當(dāng) Redis 重啟的時(shí)候會(huì)優(yōu)先載入 AOF 文件來恢復(fù)原始的數(shù)據(jù),因?yàn)樵谕ǔG闆r下 AOF 文件保存的數(shù)據(jù)集要比 RDB 文件保存的數(shù)據(jù)集要完整。

如果 可以接受數(shù)分鐘以內(nèi)的數(shù)據(jù)丟失,那么可以 只使用 RDB 持久化。

有很多用戶都只使用 AOF 持久化,但并不推薦這種方式,因?yàn)槎〞r(shí)生成 RDB 快照(snapshot)非常便于進(jìn)行數(shù)據(jù)備份, 并且 RDB 恢復(fù)數(shù)據(jù)集的速度也要比 AOF 恢復(fù)的速度要快。

如果只需要數(shù)據(jù)在服務(wù)器運(yùn)行的時(shí)候存在,也可以不使用任何持久化方式。

當(dāng)然如果既要保證同步和故障恢復(fù)效率,又要盡可能減少數(shù)據(jù)丟失的概率,也可以考慮混合持久化機(jī)制。

4. Redis的數(shù)據(jù)恢復(fù)如何做到的?

  • AOF持久化開啟且存在AOF文件時(shí),優(yōu)先加載AOF文件。
  • AOF關(guān)閉或者AOF文件不存在時(shí),加載RDB文件。
  • 加載AOF/RDB文件成功后,Redis啟動(dòng)成功。
  • AOF/RDB文件存在錯(cuò)誤時(shí),Redis啟動(dòng)失敗并打印錯(cuò)誤信息。

5. Redis4.0的混合持久化持久化

將 rdb 文件的內(nèi)容和增量的 AOF 日志文件存在一起。這里的 AOF 日志不再是全量的日志,而是 自持久化開始到持久化結(jié)束 的這段時(shí)間發(fā)生的增量 AOF 日志,通常這部分 AOF 日志很小。

三、Redis場(chǎng)景架構(gòu)設(shè)計(jì)

1. 緩存擊穿、緩存穿透、緩存雪崩問題以及應(yīng)對(duì)策略

緩存擊穿:要查詢的某一個(gè)緩存數(shù)據(jù)剛剛好過期,導(dǎo)致大量查詢的請(qǐng)求直接打到數(shù)據(jù)庫(kù)上,讓數(shù)據(jù)庫(kù)處于高負(fù)載狀態(tài)。

解決策略:

  • 加個(gè)互斥鎖保證單位時(shí)間內(nèi)只有一個(gè)請(qǐng)求處理SQL查詢并緩存數(shù)據(jù)。
  • 設(shè)置熱點(diǎn)數(shù)據(jù)永不過期。

緩存穿透:盡管我們將數(shù)據(jù)庫(kù)中某些數(shù)據(jù)換到到內(nèi)存中,但是若有些攻擊者使用一些數(shù)據(jù)庫(kù)中不存在的key進(jìn)行惡意攻擊,這時(shí)候,所有的查詢請(qǐng)求就像穿透了緩存中間件一樣直接在數(shù)據(jù)庫(kù)中進(jìn)行查詢操作,在高并發(fā)場(chǎng)景,這樣的攻擊就會(huì)使得數(shù)據(jù)壓力過大,從而導(dǎo)致數(shù)據(jù)庫(kù)被打死

針對(duì)緩存穿透問題,對(duì)此我們的應(yīng)對(duì)策略有:

  • 使用過濾器,我們可以使用布隆過濾器來減少對(duì)數(shù)據(jù)庫(kù)的請(qǐng)求,布隆過濾器的原理是將數(shù)據(jù)庫(kù)的數(shù)據(jù)哈希到 bitmap 中(在initialBean階段將數(shù)據(jù)緩存到內(nèi)存中),每次查詢之前,借用布隆過濾器的特性(不能保證數(shù)據(jù)一定存在,但一定能保證數(shù)據(jù)不存在),過濾掉一定不存在的無效請(qǐng)求,從而避免了無效請(qǐng)求給數(shù)據(jù)庫(kù)帶來的查詢壓力。
  • 緩存空結(jié)果,我們可以把每次從數(shù)據(jù)庫(kù)查詢的數(shù)據(jù)都保存到緩存中,為了提高前臺(tái)用戶的使用體驗(yàn) (解決長(zhǎng)時(shí)間內(nèi)查詢不到任何信息的情況),我們可以將空結(jié)果的緩存時(shí)間設(shè)置得短一些,例如 3~5 分鐘,但是有可能導(dǎo)致數(shù)據(jù)一致性問題,所以我們建議查詢或者更新的時(shí)候要對(duì)這個(gè)類型的緩存上個(gè)鎖進(jìn)行進(jìn)一步的操作。
  • 緩存雪崩:大量定時(shí)緩存失效或緩存服務(wù)器宕機(jī),導(dǎo)致數(shù)據(jù)庫(kù)服務(wù)器被打死。

解決策略:

  • 加鎖排隊(duì),示例代碼如下所示,如果數(shù)據(jù)庫(kù)中沒有值的話直接上鎖到數(shù)據(jù)庫(kù)查在放到緩存中,有點(diǎn)類似于單例模式的雙重鎖校驗(yàn),但是并發(fā)場(chǎng)景性能表現(xiàn)會(huì)差一些:
// 緩存 key
String cacheKey = "userlist";
// 查詢緩存
String data = jedis.get(cacheKey);
if (StringUtils.isNotBlank(data)) {
    // 查詢到數(shù)據(jù),直接返回結(jié)果
    return data;
} else {
    // 先排隊(duì)查詢數(shù)據(jù)庫(kù),再放入緩存
    synchronized (cacheKey) {
        data = jedis.get(cacheKey);
        if (!StringUtils.isNotBlank(data)) { // 雙重判斷
            // 查詢數(shù)據(jù)庫(kù)
            data = findUserInfo();
            // 放入緩存
            jedis.set(cacheKey, data);
        }
        return data;
    }
}
  • 設(shè)計(jì)緩存時(shí),對(duì)緩存設(shè)置隨機(jī)時(shí)間:
// 緩存原本的失效時(shí)間
int exTime = 10 * 60;
// 隨機(jī)數(shù)生成類
Random random = new Random();
// 緩存設(shè)置
jedis.setex(cacheKey, exTime + random.nextInt(1000) , value);

2. 緩存污染(緩存空間全滿)

某些數(shù)據(jù)查詢一次就被緩存在數(shù)據(jù)庫(kù)中,隨著時(shí)間推移,緩存空間已經(jīng)滿了,這時(shí)候redis就要根據(jù)緩存策略進(jìn)行緩存置換。這就造成沒意義的數(shù)據(jù)需要通過緩存置換策略來淘汰數(shù)據(jù),而且還可能出現(xiàn)淘汰熱點(diǎn)數(shù)據(jù)的情況。

解決方案:選定合適的緩存置換策略,而redis緩存策略主要分三類。

  • noeviction (v4.0后默認(rèn)的):不會(huì)淘汰任何過期鍵,滿了就報(bào)錯(cuò),對(duì)設(shè)置了過期時(shí)間的數(shù)據(jù)中進(jìn)行淘汰
  • volatile-random:隨機(jī)刪除過期key
  • volatile-ttl:根據(jù)過期時(shí)間進(jìn)行排序,越早過期的數(shù)據(jù)就優(yōu)先被淘汰。
  • volatile-lru:即最近最少使用算法(推薦),redis的lru緩存置換算法相比傳統(tǒng)的算法做了一定優(yōu)化,根據(jù) maxmemory-samples從緩存中隨機(jī)取出幾個(gè)key值,然后進(jìn)行比較在進(jìn)行淘汰,這樣就避免了緩存置換時(shí)需要操作一個(gè)大鏈表進(jìn)行key值淘汰了。
  • volatile-lfu:lru只知曉用戶最近使用次數(shù),而不知道該數(shù)據(jù)使用頻率,所以lfu就是基于lru進(jìn)一步的優(yōu)化,進(jìn)行淘汰時(shí)隨機(jī)取出訪問次數(shù)最少的數(shù)據(jù),如果最少的數(shù)據(jù)有多個(gè),按按照l(shuí)ru算法進(jìn)行淘汰。但是redis只用8bit記錄訪問次數(shù),超過255就無法進(jìn)行自增了,所以我們可以使用lfu-log-factor 和lfu-decay-time來用戶訪問次數(shù)增加的頻率。
  • lfu-decay-time:控制訪問次數(shù)衰減。LFU 策略會(huì)計(jì)算當(dāng)前時(shí)間和數(shù)據(jù)最近一次訪問時(shí)間的差值,并把這個(gè)差值換算成以分鐘為單位。然后,LFU 策略再把這個(gè)差值除以 lfu_decay_time 值,所得的結(jié)果就是數(shù)據(jù) counter 要衰減的值。若設(shè)置為0,則意味著每次掃描訪問次數(shù)都會(huì)扣減。
  • lfu-log-factor:用計(jì)數(shù)器當(dāng)前的值乘以配置項(xiàng) lfu_log_factor 再加 1,再取其倒數(shù),得到一個(gè) p 值;然后,把這個(gè) p 值和一個(gè)取值范圍在(0,1)間的隨機(jī)數(shù) r 值比大小,只有 p 值大于 r 值時(shí),計(jì)數(shù)器才加 1。  全部數(shù)據(jù)進(jìn)行淘汰
  • allkeys-random:從所有鍵值對(duì)中使用lru淘汰
  • allkeys-lru:從所有鍵值對(duì)中隨機(jī)刪除
  • allkeys-lfu:從所有鍵值對(duì)中使用lfu隨機(jī)淘汰

3. 基于Redis定位億級(jí)數(shù)據(jù)

假如Redis里面有1億個(gè)key,其中有10w個(gè)key是以某個(gè)固定的已知的前綴開頭的,如何將它們?nèi)空页鰜恚?/p>

我們可以使用 keys 指令可以掃出指定模式的 key 列表。但是要注意 keys 指令會(huì)導(dǎo)致線程阻塞一段時(shí)間,線上服務(wù)會(huì)停頓,直到指令執(zhí)行完畢,服務(wù)才能恢復(fù)。這個(gè)時(shí)候可以使用 scan 指令,scan 指令可以無阻塞的提取出指定模式的 key 列表,但是會(huì)有一定的重復(fù)概率,在客戶端做一次去重就可以了,但是整體所花費(fèi)的時(shí)間會(huì)比直接用 keys 指令長(zhǎng)。

4. 什么情況下會(huì)出現(xiàn)數(shù)據(jù)庫(kù)和緩存不一致的問題?

大體有以下兩種情況: 我們先來說說更新數(shù)據(jù)庫(kù),然后更新緩存的情況,如下圖所示,線程1和線程2都是先更新數(shù)據(jù)再更新緩存,由于線程1因?yàn)榫W(wǎng)絡(luò)波動(dòng)或者線程調(diào)度順序原因?qū)е潞蟾戮彺?,最終導(dǎo)致數(shù)據(jù)庫(kù)和緩存不一致,而先更新緩存再更新數(shù)據(jù)庫(kù)同理這里就不多贅述:

還有一種情況是針對(duì)讀場(chǎng)景的,如下所示:

  • 線程2查詢緩存發(fā)現(xiàn)沒有數(shù)據(jù),到數(shù)據(jù)庫(kù)讀取到值10。
  • 此時(shí),線程1更新緩存值為20,準(zhǔn)備寫數(shù)據(jù)庫(kù)。
  • 線程2將數(shù)據(jù)庫(kù)讀取到的10寫入緩存。
  • 線程1將數(shù)據(jù)庫(kù)更新為20。

自此,緩存不一致問題又出現(xiàn):

5. 如何解決Redis和數(shù)據(jù)庫(kù)的一致性問題?

  • 延時(shí)雙刪
  • 先更新數(shù)據(jù)庫(kù)再刪除緩存
  • 更新數(shù)據(jù)庫(kù),并基于用binlog監(jiān)聽數(shù)據(jù)庫(kù)變化進(jìn)行緩存刪除。

6. 為什么需要延遲雙刪,兩次刪除的原因是什么?

第一次刪除避免讀請(qǐng)求讀到臟數(shù)據(jù) 第二次刪除避免讀請(qǐng)求將臟數(shù)據(jù)寫入緩存.

7. Redis如何實(shí)現(xiàn)延遲消息

通過配置notify-keyspace-events Ex開啟過期key事件,再通過程序繼承KeyExpirationEventMessageListener監(jiān)聽過期的事件,這種做法的缺點(diǎn)也很明顯,即過期的key不一定會(huì)立即刪除,且該消息沒有持久化可能出現(xiàn)丟失。 關(guān)于過期key不一定會(huì)立即刪除的這一點(diǎn)。

通過zset將過期時(shí)間作為score,然后key作為member,程序通過計(jì)算過期時(shí)間差值進(jìn)行休眠,到期后刪除這個(gè)key,當(dāng)然我們需要保證的就是如果有時(shí)效更短的key進(jìn)來注意更新時(shí)間。

通過redission內(nèi)存輪子提交一個(gè)任務(wù),原理和方法2差不多,只不過對(duì)于并發(fā)消費(fèi)等問題有了較好的優(yōu)化,且使用更加簡(jiǎn)單。

8. 如何基于Redis實(shí)現(xiàn)滑動(dòng)窗口限流?

滑動(dòng)窗口本質(zhì)上就是通過有序集合的方式保證單位時(shí)間內(nèi)保持一定流量數(shù)據(jù),避免突然流量突刺的問題,假設(shè)我們現(xiàn)在有個(gè)接口,希望每秒對(duì)應(yīng)請(qǐng)求控制在2000,對(duì)應(yīng)的落地方案為:

  • 將請(qǐng)求接口作為key。
  • 當(dāng)某個(gè)請(qǐng)求到來時(shí),生成唯一id作為member,時(shí)間戳作為value。
  • 基于當(dāng)前時(shí)間戳減去60s看看60s以內(nèi)的請(qǐng)求數(shù)。
  • 查看當(dāng)前有序集合中元素是否小于2000,如果是則允許新的請(qǐng)求到來。反之不允許。

當(dāng)然我們也可以直接用redisson中的RRateLimiter,它底層本質(zhì)就是用一個(gè)令牌桶算法。

9. 怎么處理熱key

什么是熱Key? 所謂的熱key,就是訪問頻率比較的key。 比如,熱門新聞事件或商品,這類key通常有大流量的訪問,對(duì)存儲(chǔ)這類信息的 Redis來說,是不小的壓力。 假如Redis集群部署,熱key可能會(huì)造成整體流量的不均衡,個(gè)別節(jié)點(diǎn)出現(xiàn)OPS過大的情況,極端情況下熱點(diǎn)key甚至?xí)^ Redis本身能夠承受的OPS。

怎么處理熱key?

熱key處理 對(duì)熱key的處理,最關(guān)鍵的是對(duì)熱點(diǎn)key的監(jiān)控,可以從這些端來監(jiān)控?zé)狳c(diǎn)key: 客戶端 客戶端其實(shí)是距離key“最近”的地方,因?yàn)镽edis命令就是從客戶端發(fā)出的,例如在客戶端設(shè)置全局字典(key和調(diào)用次數(shù)),每次調(diào)用Redis命令時(shí),使用這個(gè)字典進(jìn)行記錄。 代理端 像Twemproxy、Codis這些基于代理的Redis分布式架構(gòu),所有客戶端的請(qǐng)求都是通過代理端完成的,可以在代理端進(jìn)行收集統(tǒng)計(jì)。

Redis服務(wù)端 使用monitor命令統(tǒng)計(jì)熱點(diǎn)key是很多開發(fā)和運(yùn)維人員首先想到,monitor命令可以監(jiān)控到Redis執(zhí)行的所有命令。

只要監(jiān)控到了熱key,對(duì)熱key的處理就簡(jiǎn)單了: 把熱key打散到不同的服務(wù)器,降低壓? 加??級(jí)緩存,提前加載熱key數(shù)據(jù)到內(nèi)存中,如果redis宕機(jī),?內(nèi)存查詢

10. 緩存預(yù)熱怎么做?

所謂緩存預(yù)熱,就是提前把數(shù)據(jù)庫(kù)里的數(shù)據(jù)刷到緩存里,通常有這些方法:

  • 直接寫個(gè)緩存刷新頁(yè)面或者接口,上線時(shí)手動(dòng)操作
  • 數(shù)據(jù)量不大,可以在項(xiàng)目啟動(dòng)的時(shí)候自動(dòng)進(jìn)行加載(我們目前就是執(zhí)行這種操作,通過繼承InitializingBean實(shí)現(xiàn))
  • 定時(shí)任務(wù)刷新緩存.

11. 熱點(diǎn)key重建問題了解過?你是如何解決的呢?

開發(fā)的時(shí)候一般使用“緩存+過期時(shí)間”的策略,既可以加速數(shù)據(jù)讀寫,又保證數(shù)據(jù)的定期更新,這種模式基本能夠滿足絕大部分需求。

但是有兩個(gè)問題如果同時(shí)出現(xiàn),可能就會(huì)出現(xiàn)比較大的問題:

  • 當(dāng)前key是一個(gè)熱點(diǎn)key(例如一個(gè)熱門的娛樂新聞),并發(fā)量非常大。
  • 重建緩存不能在短時(shí)間完成,可能是一個(gè)復(fù)雜計(jì)算,例如復(fù)雜的 SQL、多次IO、多個(gè)依賴等。 在緩存失效的瞬間,有大量線程來重建緩存,造成后端負(fù)載加大,甚至可能會(huì)讓應(yīng)用崩潰。

要解決這個(gè)問題也不是很復(fù)雜,解決問題的要點(diǎn)在于:

  • 減少重建緩存的次數(shù)。
  • 數(shù)據(jù)盡可能一致。
  • 較少的潛在危險(xiǎn)。 所以一般采用如下方式:
  • 互斥鎖(mutex key) 這種方法只允許一個(gè)線程重建緩存,其他線程等待重建緩存的線程執(zhí)行完,重新從緩存獲取數(shù)據(jù)即可。
  • 永遠(yuǎn)不過期 “永遠(yuǎn)不過期”包含兩層意思:
  • 從緩存層面來看,確實(shí)沒有設(shè)置過期時(shí)間,所以不會(huì)出現(xiàn)熱點(diǎn)key過期后產(chǎn)生的問題,也就是“物理”不過期,注意數(shù)據(jù)更新后要實(shí)時(shí)加鎖更新。

從功能層面來看,為每個(gè)value設(shè)置一個(gè)邏輯過期時(shí)間,當(dāng)發(fā)現(xiàn)超過邏輯過期時(shí)間后,會(huì)使用單獨(dú)的線程去構(gòu)建緩存。

四、詳解Redis日常運(yùn)維

1. Redis阻塞問題如何解決

(1) API或數(shù)據(jù)結(jié)構(gòu)使用不合理:通常Redis執(zhí)行命令速度非???,但是不合理地使用命令,可能會(huì)導(dǎo)致執(zhí)行速度很慢,導(dǎo)致阻塞,對(duì)于高并發(fā)的場(chǎng)景,應(yīng)該盡量避免在大對(duì)象上執(zhí)行算法復(fù)雜 度超過O(n)的命令。

對(duì)慢查詢的處理分為兩步:

  • 發(fā)現(xiàn)慢查詢: slowlog get{n}命令可以獲取最近 的n條慢查詢命令;
  • 發(fā)現(xiàn)慢查詢后,可以從兩個(gè)方向去優(yōu)化慢查詢: 1)修改為低算法復(fù)雜度的命令,如hgetall改為hmget等,禁用keys、sort等命 令 2)調(diào)整大對(duì)象:縮減大對(duì)象數(shù)據(jù)或把大對(duì)象拆分為多個(gè)小對(duì)象,防止一次命令操作過多的數(shù)據(jù)。

(2) CPU飽和的問題:?jiǎn)尉€程的Redis處理命令時(shí)只能使用一個(gè)CPU,而CPU飽和是指Redis單核CPU使用率跑到接近100%。

針對(duì)這種情況,處理步驟一般如下:

  • 判斷當(dāng)前Redis并發(fā)量是否已經(jīng)達(dá)到極限,可以使用統(tǒng)計(jì)命令`redis-cli-h{ip}-p{port}--stat`獲取當(dāng)前 Redis使用情況
  • 如果Redis的請(qǐng)求幾萬+,那么大概就是Redis的OPS已經(jīng)到了極限,應(yīng)該做集群化水品擴(kuò)展來分?jǐn)侽PS壓力
  • 如果只有幾百幾千,那么就得排查命令和內(nèi)存的使用

(3) 持久化相關(guān)的阻塞:對(duì)于開啟了持久化功能的Redis節(jié)點(diǎn),需要排查是否是持久化導(dǎo)致的阻塞。

fork阻塞 fork操作發(fā)生在RDB和AOF重寫時(shí),Redis主線程調(diào)用fork操作產(chǎn)生共享 內(nèi)存的子進(jìn)程,由子進(jìn)程完成持久化文件重寫工作。如果fork操作本身耗時(shí)過長(zhǎng),必然會(huì)導(dǎo)致主線程的阻塞。

AOF刷盤阻塞 當(dāng)我們開啟AOF持久化功能時(shí),文件刷盤的方式一般采用每秒一次,后臺(tái)線程每秒對(duì)AOF文件做fsync操作。當(dāng)硬盤壓力過大時(shí),fsync操作需要等待,直到寫入完成。如果主線程發(fā)現(xiàn)距離上一次的fsync成功超過2秒,為了 數(shù)據(jù)安全性它會(huì)阻塞直到后臺(tái)線程執(zhí)行fsync操作完成。

HugePage寫操作阻塞 對(duì)于開啟Transparent HugePages的 操作系統(tǒng),每次寫命令引起的復(fù)制內(nèi)存頁(yè)單位由4K變?yōu)?MB,放大了512 倍,會(huì)拖慢寫操作的執(zhí)行時(shí)間,導(dǎo)致大量寫操作慢查詢。

2. Redis大key問題

Redis使用過程中,有時(shí)候會(huì)出現(xiàn)大key的情況, 比如:

(1) 單個(gè)簡(jiǎn)單的key存儲(chǔ)的value很大,size超過10KBhash, set,zset,list 中存儲(chǔ)過多的元素(以萬為單位) 大key會(huì)造成什么問題呢?

  • 客戶端耗時(shí)增加,甚至超時(shí)
  • 對(duì)大key進(jìn)行IO操作時(shí),會(huì)嚴(yán)重占用帶寬和CPU
  • 造成Redis集群中數(shù)據(jù)傾斜
  • 主動(dòng)刪除、被動(dòng)刪等,可能會(huì)導(dǎo)致阻塞

(2) 如何找到大key?

  • bigkeys命令:使用bigkeys命令以遍歷的方式分析Redis實(shí)例中的所有Key,并返回整體統(tǒng)計(jì)信息與每個(gè)數(shù)據(jù)類型中Top1的大Key
  • redis-rdb-tools:redis-rdb-tools是由Python寫的用來分析Redis的rdb快照文件用的工具,它可以把rdb快照文件生成json文件或者生成報(bào)表用來分析Redis的使用詳情。

(3) 如何處理大key?

  • 刪除大key:當(dāng)Redis版本大于4.0時(shí),可使用UNLINK命令安全地刪除大Key,該命令能夠以非阻塞的方式,逐步地清理傳入的Key。 當(dāng)Redis版本小于4.0時(shí),避免使用阻塞式命令KEYS,而是建議通過SCAN命令執(zhí)行增量迭代掃描key,然后判斷進(jìn)行刪除。
  • 壓縮和拆分key:當(dāng)vaule是string時(shí),比較難拆分,則使用序列化、壓縮算法將key的大小控制在合理范圍內(nèi),但是序列化和反序列化都會(huì)帶來更多時(shí)間上的消耗。 當(dāng)value是string,壓縮之后仍然是大key,則需要進(jìn)行拆分,一個(gè)大key分為不同的部分,記錄每個(gè)部分的key,使用multiget等操作實(shí)現(xiàn)事務(wù)讀取。 當(dāng)value是list/set等集合類型時(shí),根據(jù)預(yù)估的數(shù)據(jù)規(guī)模來進(jìn)行分片,不同的元素計(jì)算后分到不同的片。

3. Redis常見的性能問題和解決方案了解嘛?

Master 最好不要做任何持久化工作,包括內(nèi)存快照和 AOF 日志文件,特別是不要啟用內(nèi)存快照做持久化。

如果數(shù)據(jù)比較關(guān)鍵,某個(gè) Slave 開啟 AOF 備份數(shù)據(jù),策略為每秒同步一次。

為了主從復(fù)制的速度和連接的穩(wěn)定性,Slave 和 Master 最好在同一個(gè)局域網(wǎng)內(nèi)。 盡量避免在壓力較大的主庫(kù)上增加從庫(kù)。

Master 調(diào)用 BGREWRITEAOF 重寫 AOF 文件,AOF 在重寫的時(shí)候會(huì)占大量的 CPU 和內(nèi)存資源,導(dǎo)致服務(wù) load 過高,出現(xiàn)短暫服務(wù)暫停現(xiàn)象。

為了 Master 的穩(wěn)定性,主從復(fù)制不要用圖狀結(jié)構(gòu),用單向鏈表結(jié)構(gòu)更穩(wěn)定,即主從關(guān)為:Master<–Slave1<–Slave2<–Slave3…,這樣的結(jié)構(gòu)也方便解決單點(diǎn)故障問題,實(shí)現(xiàn) Slave 對(duì) Master 的替換,也即,如果 Master 掛了,可以立馬啟用 Slave1 做 Master,其他不變。

4. 什么是熱Key問題,如何解決熱key問題

即同一個(gè)時(shí)間點(diǎn)上,redis中同一個(gè)key被大量訪問,導(dǎo)致流量過于集中,進(jìn)而導(dǎo)致服務(wù)器資源無法支撐嚴(yán)重情況下可能導(dǎo)致服務(wù)癱瘓。 所以,對(duì)應(yīng)的處理策略要針對(duì)不同的情況,如果是事前,我們可以根據(jù)往年經(jīng)驗(yàn)識(shí)別出對(duì)應(yīng)的熱點(diǎn)key,提前擴(kuò)充實(shí)例,做預(yù)熱緩存。 如果是事中,需要考慮線上進(jìn)行熱點(diǎn)key拆分,多級(jí)緩存、增加實(shí)例甚至通過限流等策略來解決問題。

5. 如何用Redis實(shí)現(xiàn)樂觀鎖、可重入鎖

大體可以通過以下幾個(gè)指令:

  • watch監(jiān)視一個(gè)或者多個(gè)鍵
  • get查詢數(shù)據(jù)
  • multi開始事務(wù)
  • set執(zhí)行命令
  • exec提交事務(wù)

而可重入鎖通過setnx+incr和decr指令完成上鎖和解鎖邏輯。

6. Redis實(shí)現(xiàn)分布鎖的時(shí)候,哪些問題需要考慮

  • 互斥
  • 性能
  • 誤解鎖
  • 鎖超時(shí)
  • 鎖續(xù)命
  • 單點(diǎn)故障
  • 鎖重入
  • 網(wǎng)絡(luò)分區(qū)
  • 時(shí)間漂移

7. Redis如何高效安全的遍歷所有key

加入redis中存在大量的key,使用常規(guī)的keys * 請(qǐng)求會(huì)導(dǎo)致其他的客戶端請(qǐng)求阻塞,所以針對(duì)遍歷key的需求,我們更建議使用scan,它會(huì)以游標(biāo)的方式分批次迭代鍵集合,這個(gè)概念和游標(biāo)查詢是優(yōu)點(diǎn)類似的:

這里筆者簡(jiǎn)單制造了百萬級(jí)別的熱key進(jìn)行演示:

StringRedisTemplate redisTemplate = SpringUtil.getBean(StringRedisTemplate.class);
        IntStream.rangeClosed(0, 100_0000).parallel()
                .forEach(n -> {
                    redisTemplate.opsForValue().set("key-" + n, "value-" + n);
                });

這里筆者用了keys * 嘗試了一下遍歷,可以看到耗時(shí)約70s,這也就意味則在這70s之間其他的請(qǐng)求是阻塞的:

而使用scan就可以很好的解決問題,通過scan指令從0開始,每次基于上一次的游標(biāo)進(jìn)行數(shù)據(jù)檢索獲取,通過逐批次的檢索和遍歷很好的解決keys *的阻塞問題:

雖說scan很好的解決的遍歷阻塞問題,但它對(duì)于數(shù)據(jù)實(shí)時(shí)性的把控不是很好,從上面我可以知道scan指令本質(zhì)上就是漸進(jìn)式的遍歷,這意味著在掃描過的區(qū)間上進(jìn)行的任何修改操作我們都是無法感知的。

責(zé)任編輯:趙寧寧 來源: 寫代碼的SharkChili
相關(guān)推薦

2021-12-30 08:17:27

Springboot數(shù)據(jù)訪問DataSourceB

2025-01-07 14:10:46

SpringBoot開發(fā)Java

2020-11-06 00:50:16

JavaClassLoaderJVM

2021-01-15 08:35:49

Zookeeper

2020-10-26 10:40:31

Axios前端攔截器

2021-01-06 13:52:19

zookeeper開源分布式

2024-11-04 09:00:00

Java開發(fā)

2021-04-13 08:25:12

測(cè)試開發(fā)Java注解Spring

2025-02-12 00:29:58

2024-04-23 14:25:16

Python備忘清單

2020-05-19 14:40:08

Linux互聯(lián)網(wǎng)核心

2022-10-29 08:55:19

頁(yè)面react

2022-04-08 07:51:31

JavaJVM垃圾回收

2020-10-14 10:50:50

SpringSessiJavaweb

2024-03-12 12:57:07

Redis主從架構(gòu)

2024-07-11 08:17:00

2024-06-04 14:07:00

2023-08-07 14:44:56

Socket文件描述符

2012-02-08 10:37:42

Java反射

2010-05-26 10:32:47

SVN管理
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)