redis 面試總結(jié)

1. redis 為什么快?

在底層上, redis 使用了 IO 多路復(fù)用技術(shù),像 select、epoll 等。能較好的保障吞吐量。而且 redis 采用了單線程處理請(qǐng)求,避免了線程切換和鎖競(jìng)爭(zhēng)鎖帶來(lái)的額外消耗。

加上 redis 本身也對(duì)一些數(shù)據(jù)結(jié)構(gòu)進(jìn)行了優(yōu)化設(shè)計(jì),所以 redis 的性能非常好,官方給出的測(cè)試報(bào)告是單機(jī)可以支持約 10w/s 的 QPS。

2. Redis 有哪些使用場(chǎng)景?應(yīng)用是怎么樣的?

Redis 的使用場(chǎng)景有很多,最常用的莫過(guò)于數(shù)據(jù)緩存了。但由于它提供了多種數(shù)據(jù)類型,因此我們還可以進(jìn)行其他場(chǎng)景的開發(fā),比如:

  • 排行榜:有序集合(sorted set)每次寫入都會(huì)進(jìn)行排序,而且不含重復(fù)值,所以我們可以將用戶的唯一標(biāo)識(shí),比如 userId 作為 key,分?jǐn)?shù)作為 score,然后就可以進(jìn)行 ZADD 操作,以得到排行榜。
  • 簽到:簽到往往只有 2 種狀態(tài),已簽到和未簽到。這就跟 0 和 1 一樣,所以 redis 的 setbit、getbit 這種對(duì)位的操作就適合簽到場(chǎng)景。
  • 計(jì)數(shù):redis 是單線程操作,這種計(jì)數(shù)功能,比如點(diǎn)贊數(shù)、粉絲數(shù)的操作可以交給 redis 以避免并發(fā)競(jìng)爭(zhēng)問(wèn)題。當(dāng)然,也得考慮持久化問(wèn)題。

3. Redis 通信協(xié)議 是怎么樣的?

redis 采用文本序列化協(xié)議,和 http 協(xié)議一樣,一個(gè)請(qǐng)求一個(gè)響應(yīng),客戶端接到響應(yīng)后再繼續(xù)請(qǐng)求。也可以發(fā)起多次請(qǐng)求,然后一次響應(yīng)回所有執(zhí)行結(jié)果,即所謂的 pipeline 管道技術(shù)。

redis 的文本序列化協(xié)議比較簡(jiǎn)單,通過(guò)一些規(guī)范格式去解析文本,大概如下:

  • \r\n 表示解析結(jié)束
  • 簡(jiǎn)單字符串,以“+”開頭
  • 錯(cuò)誤 Errors,以“-”開頭
  • 整數(shù)類型,以“:”開頭
  • 大字符串類型,以“$”開頭
  • 數(shù)組類型,以“*”開頭

例如,客戶端向服務(wù)器發(fā)送命令:

SET key value

將被解析為:

*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n

上面的命令可以看成:

*<參數(shù)數(shù)量> CR LF

$<參數(shù) 1 的字節(jié)數(shù)量> CR LF
<參數(shù) 1 的數(shù)據(jù)> CR LF
...
$<參數(shù) N 的字節(jié)數(shù)量> CR LF
<參數(shù) N 的數(shù)據(jù)> CR LF

而服務(wù)器的回復(fù)則有很多類型,一般由響應(yīng)數(shù)據(jù)的第一個(gè)字節(jié)決定:

狀態(tài)回復(fù)(status reply)的第一個(gè)字節(jié)是 "+"

錯(cuò)誤回復(fù)(error reply)的第一個(gè)字節(jié)是 "-"

整數(shù)回復(fù)(integer reply)的第一個(gè)字節(jié)是 ":"

批量回復(fù)(bulk reply)的第一個(gè)字節(jié)是 "$"

多條批量回復(fù)(multi bulk reply)的第一個(gè)字節(jié)是 "*"

例如,響應(yīng)回來(lái)的狀態(tài)回復(fù)如下:

+OK

4. redis 對(duì)外提供了哪些數(shù)據(jù)類型,它們的底層數(shù)據(jù)結(jié)構(gòu)又是怎么樣的?

為了讓開發(fā)者能更好的使用緩存,redis 支持了 5 種數(shù)據(jù)類型。底層是由 6 種數(shù)據(jù)結(jié)構(gòu)組成的。

5 種數(shù)據(jù)類型

字符串:字符串類型是 redis 里最基礎(chǔ)的數(shù)據(jù)類型,像 set name "hello" 操作后,在 get name 時(shí)返回的就是字符串,而且還支持了對(duì)位的操作。一般一個(gè)鍵能存儲(chǔ) 512MB 的值。

hash:哈希類型主要是用來(lái)存儲(chǔ)對(duì)象的,一般我們?nèi)绻幸徽麄€(gè)對(duì)象要存儲(chǔ),里面包含了多個(gè)字段,則可以使用 hash 來(lái)存儲(chǔ),因?yàn)?redis 提供了對(duì)這些字段的提取和設(shè)置,減少了開發(fā)者對(duì)它的二次處理,比如序列化反序列化操作。

list:一個(gè)簡(jiǎn)單的字符串列表,它允許我們從兩端進(jìn)行 push,pop 操作,還支持一定范圍的列表元素??梢钥闯墒请p向列表。

set:集合是一個(gè)不重復(fù)值的組合,為我們提供了交集、并集、差集等操作,像找出共同好友這種需求就可以使用集合操作了。

sorted set:有序集合,在上面集合的基礎(chǔ)上提供了排序功能,通過(guò)一個(gè) score 屬性來(lái)進(jìn)行排序。

6 種底層數(shù)據(jù)結(jié)構(gòu)

上面的數(shù)據(jù)類型實(shí)際上在 redis 底層是有對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu)來(lái)實(shí)現(xiàn)的,都是 redis 經(jīng)過(guò)精心設(shè)計(jì)的,能很好的提高處理效率。

簡(jiǎn)單動(dòng)態(tài)字符串:redis 是使用 C 語(yǔ)言寫的,而 C 語(yǔ)言里的字符串類型比較原始,比如使用 \0 作為字符結(jié)束符。所以 redis 實(shí)現(xiàn)了屬于自己的字符串類型,比如字符串長(zhǎng)度,預(yù)先分配內(nèi)存,動(dòng)態(tài)拓展等特點(diǎn),也保證了處理安全性。

鏈表:一個(gè)雙端鏈表,有 prev,next 指針去獲取前后節(jié)點(diǎn),帶有 len 屬性,能保存多種類型的值。

字典:通過(guò)哈希算法來(lái)實(shí)現(xiàn) key-value 的映射操作,采用鏈地址法解決了 hash 沖突,一般時(shí)間復(fù)雜度能達(dá)到 O(1)。

跳躍表:一個(gè)多層有序鏈表,每一層都是對(duì)下面一層的有序提取,能降低搜索次數(shù),有點(diǎn)像有序二叉樹的搜索一樣。

跳躍表

整數(shù)集合:一個(gè)有序的整數(shù)集合,不會(huì)有重復(fù)元素。

壓縮列表(ziplist):經(jīng)過(guò)特殊編碼的一塊連續(xù)內(nèi)存,能有效的節(jié)省內(nèi)存。

快速列表:將 ziplist 組織為了一個(gè)雙向鏈表,由于 ziplist 的內(nèi)部連續(xù)性,能降低鏈表的內(nèi)存碎片問(wèn)題,提高內(nèi)存利用率。

5. redis 淘汰策略有哪些?

redis 的淘汰策略主要是 LRU 淘汰、TTL 淘汰和隨機(jī)淘汰這三種機(jī)制。

  • LRU 淘汰:最近最少使用的淘汰掉
  • TTL 淘汰:越早過(guò)期的越先淘汰掉。
  • 隨機(jī)淘汰:采用隨機(jī)算法淘汰掉。

由于 redis 可以對(duì)鍵設(shè)置過(guò)期時(shí)間,也可以不設(shè)置,所以淘汰策略還得再細(xì)分:

  • volatile-lru:針對(duì)設(shè)置了過(guò)期時(shí)間的 key 執(zhí)行 LRU 淘汰策略,沒(méi)有設(shè)置過(guò)期時(shí)間的不會(huì)被淘汰。
  • volatile-ttl:只針對(duì)設(shè)置了過(guò)期時(shí)間的 key 執(zhí)行 TTL 淘汰。
  • volatile-random:只針對(duì)設(shè)置了過(guò)期時(shí)間的 key 執(zhí)行隨機(jī)淘汰。
  • allkeys-lru:針對(duì)所有鍵進(jìn)行 LRU 淘汰策略
  • allkeys-random:針對(duì)所有鍵進(jìn)行隨機(jī)淘汰策略
  • no-enviction:不執(zhí)行淘汰策略,如果有寫入操作,則報(bào)錯(cuò);讀請(qǐng)求可以繼續(xù)進(jìn)行。

在 Redis 的配置文件 redis.conf 里我們可以進(jìn)行淘汰策略的設(shè)置:

# 數(shù)據(jù)達(dá)到多大后執(zhí)行淘汰策略
maxmemory 300mb

# 淘汰策略的設(shè)置
maxmemory-policy volatile-lru

6. redis 的持久化機(jī)制有哪些?

RDB

在指定的時(shí)間間隔里將 Redis 內(nèi)存里的數(shù)據(jù)鏡像下來(lái),保存到文件里。它會(huì)先 fork 一個(gè)子進(jìn)程,將數(shù)據(jù)的寫入交給子進(jìn)程,而父進(jìn)程不會(huì)涉及到磁盤的 IO 操作,所以 RDB 的性能非常好。如果是在 Unix 系統(tǒng)上,還能充分利用寫時(shí)復(fù)制機(jī)制,節(jié)省對(duì)物理內(nèi)存的使用。

由于 RDB 文件只存儲(chǔ)了某個(gè)時(shí)刻的內(nèi)存數(shù)據(jù),并沒(méi)有什么邏輯命令,所以在進(jìn)行重啟恢復(fù)時(shí),能很快的加載進(jìn)來(lái)。

雖然 RDB 的 fork 能使得 Redis 的持久化獨(dú)立進(jìn)行,但是一旦數(shù)據(jù)量比較大的,就會(huì)一直占用 CPU,可能會(huì)影響到父進(jìn)程的進(jìn)行。

AOF

將服務(wù)器對(duì)數(shù)據(jù)的寫操作追加到文件里,相當(dāng)于將所有的邏輯操作都記錄了下來(lái)。AOF允許我們以每秒的速度進(jìn)行持久化,這樣的話可以很大程度的減少數(shù)據(jù)的丟失。同時(shí)它采用追加的方式進(jìn)行寫文件,這樣即使持久化失敗,影響較少,而且能夠使用 redis-check-aof 進(jìn)行修復(fù)。

不過(guò)日志可能會(huì)越來(lái)越大,需要靠重寫來(lái)減少對(duì)磁盤的占用。

RDB + AOF

將 RDB 和 AOF 結(jié)合起來(lái),組合它們各自的優(yōu)點(diǎn)。4.0 版本以上才支持。其文件時(shí)前半部分時(shí) RDB 格式,后半部分是 AOF 格式。

7. redis 的分布式鎖: RedLock 原理是什么?有哪些缺點(diǎn)?

RedLock 原理

客戶端依次向各個(gè) redis 節(jié)點(diǎn)獲取鎖,一旦超過(guò)一半的機(jī)器上鎖了,并且沒(méi)有超過(guò)規(guī)定的時(shí)間,則客戶端認(rèn)為是上鎖成功了。同時(shí)開始計(jì)算鎖的過(guò)期時(shí)間,過(guò)期則通知所有服務(wù)器解鎖,如果這次獲取鎖失敗,也通知所有服務(wù)器解鎖。 并且解鎖時(shí)會(huì)根據(jù)當(dāng)時(shí)帶過(guò)來(lái)的一個(gè) token 一致才解鎖,防止誤解鎖。

RedLock 缺點(diǎn)

  • 受限于 redis 的持久化機(jī)制,當(dāng)某個(gè) redis 節(jié)點(diǎn)重啟時(shí)丟失了鎖記錄,則有可能導(dǎo)致新的請(qǐng)求又獲取到了超過(guò)一半的響應(yīng),則此時(shí)將有兩個(gè)操作者同時(shí)擁有鎖資源。官方針對(duì)此建議: 延遲重啟,等待超時(shí)
  • 上面的流程涉及到了時(shí)間的判斷,如果不同機(jī)器的時(shí)間差相差太遠(yuǎn),則會(huì)出現(xiàn)超時(shí)解鎖,提前釋放資源的問(wèn)題。

8. redis 的高可用方案設(shè)計(jì)?

主從模式

在不同的機(jī)器上部署著同一 Redis 程序。在這多臺(tái)機(jī)器里,我們會(huì)選擇一個(gè)節(jié)點(diǎn)作為主節(jié)點(diǎn),它負(fù)責(zé)數(shù)據(jù)的寫入。其他節(jié)點(diǎn)作為從節(jié)點(diǎn),定時(shí)的和主節(jié)點(diǎn)同步數(shù)據(jù)。一旦主節(jié)點(diǎn)不能使用了,那么就可以在從節(jié)點(diǎn)中挑選一個(gè)作為主節(jié)點(diǎn),重新上崗服務(wù)。


主從模式

哨兵模式

上面的主從模式需要人工的進(jìn)行故障節(jié)點(diǎn)切換,這種方式對(duì)于追求完美的程序員來(lái)說(shuō),肯定是不夠的。所以有了自動(dòng)切換的哨兵模式。

哨兵模式主要實(shí)現(xiàn)了下面幾個(gè)功能:

  • 監(jiān)控:不斷的檢測(cè)主從節(jié)點(diǎn)是否能正常工作。
  • 自動(dòng)轉(zhuǎn)移故障:當(dāng)某個(gè) master 不能正常工作時(shí),Sentinel 會(huì)啟動(dòng)一個(gè)故障轉(zhuǎn)移過(guò)程,將其中的一個(gè)副本提升為 master,并通知其他從節(jié)點(diǎn)對(duì)應(yīng)新的 master 相關(guān)信息。
  • 通知:當(dāng)某個(gè)節(jié)點(diǎn)出問(wèn)題時(shí),會(huì)告知所有節(jié)點(diǎn)。如果是新的主節(jié)點(diǎn)被選舉出來(lái),還會(huì)告知已連接過(guò)來(lái)的客戶端程序關(guān)于主節(jié)點(diǎn)新的地址。
哨兵模式

集群

Redis 的集群采用了哈希槽的概念,總共會(huì)有 16384 個(gè)哈希槽。這些哈希槽會(huì)被分配到各個(gè)節(jié)點(diǎn)上,比如:

  • 節(jié)點(diǎn) 1 分配了 0 至 5500 的哈希槽。
  • 節(jié)點(diǎn) 2 分配了 5501 至 11000 的哈希槽。
  • 節(jié)點(diǎn) 3 分配了 11001 至 16384 的哈希槽。

當(dāng)有 key 過(guò)來(lái)時(shí),Redis 會(huì)對(duì)其進(jìn)行 CRC16(key) % 16384 的運(yùn)算,看當(dāng)前的 key 要分散到哪個(gè)哈希槽上,再根據(jù)當(dāng)前的哈希槽定位到對(duì)應(yīng)的節(jié)點(diǎn)上。這樣就完成了一次 key-value 的存儲(chǔ)了。

讀取也是按這規(guī)則來(lái),不同的是,如果運(yùn)算結(jié)果所對(duì)應(yīng)的節(jié)點(diǎn)不在當(dāng)前節(jié)點(diǎn)上,則會(huì)轉(zhuǎn)發(fā)給對(duì)應(yīng)的節(jié)點(diǎn)去處理。

當(dāng)有節(jié)點(diǎn)進(jìn)行新增或刪除時(shí),會(huì)重新劃分這些哈希槽,當(dāng)然,影響的只會(huì)是周圍節(jié)點(diǎn),不會(huì)造成整個(gè)集群不可用。

在這些節(jié)點(diǎn)背后還有屬于它們的從節(jié)點(diǎn),一旦主節(jié)點(diǎn)不可用,那么這些從節(jié)點(diǎn)就會(huì)被啟用,以保證系統(tǒng)的正常運(yùn)行。

集群

9. 緩存雪崩和穿透該怎么處理?

當(dāng)緩存失效,就會(huì)有大量的請(qǐng)求打到后端服務(wù),壓垮系統(tǒng),這就是緩存雪崩。

除了緩存雪崩,還有緩存穿透的可能。比如每次訪問(wèn)不一樣的數(shù)據(jù),則請(qǐng)求還是會(huì)落到后方。

為了防止緩存雪崩,我們可以對(duì)請(qǐng)求做控制,比如加入到消息隊(duì)列,慢慢消化它;又或者直接開啟限流功能,將流量控制在合理的范圍內(nèi)。

而針對(duì)緩存穿透,我們可以建立黑白名單,將一些惡意請(qǐng)求拎出來(lái),然后直接拒絕掉。如果是正常的請(qǐng)求,那可以將篩選出來(lái)的結(jié)果也暫時(shí)緩存起來(lái),即使得到的值是 NULL 值。

10. 使用 Redis 在數(shù)據(jù)并發(fā)處理上有哪些需要考慮?

由于 Redis 是以組件形式存在,所以實(shí)際上我們的程序通信可以認(rèn)為是分布式的了,也就是會(huì)有緩存和后端數(shù)據(jù)一致性的問(wèn)題。

常見的做法是在有新數(shù)據(jù)到來(lái)時(shí),將緩存 key 刪除掉,等待下次的查詢重新填補(bǔ)上緩存。

之所以在更新數(shù)據(jù)時(shí)不讓 Redis 也做更新動(dòng)作,是為了防止多個(gè)更新動(dòng)作一起發(fā)生,可能由于網(wǎng)絡(luò)原因,導(dǎo)致后更新的比前面更新的先一步達(dá)到 Redis, 這樣就會(huì)跟原來(lái)的流程不一樣了。所以只采取了刪除動(dòng)作,不做其他。

不過(guò),就算是刪除 key 這種方案也有一定概率跟上面的情況一樣,真的要嚴(yán)謹(jǐn)?shù)脑?,一般?huì)設(shè)置定時(shí)過(guò)期時(shí)間,讓數(shù)據(jù)最多在這段時(shí)間不一致。

11. redis 如何實(shí)現(xiàn)延遲隊(duì)列?

利用有序集合的 score 屬性,將時(shí)間戳設(shè)置到該屬性上,然后定時(shí)的對(duì)其排序,查看最近要執(zhí)行的記錄,如果時(shí)間到了,則取出來(lái)消費(fèi)后刪除,即可達(dá)到延遲隊(duì)列的目的。

12. redis 的事務(wù)和 db 的事務(wù)有什么不一樣?

Redis 的事務(wù)保證了 ACID 中的一致性(C)和隔離性(I),但并不保證原子性(A)和持久性(D)。

對(duì)于原子性而言,要么都成功,要么都不成功,而 redis 的事務(wù)中途某個(gè)語(yǔ)句出錯(cuò)了, 比如 key 類型 出錯(cuò)了, 還會(huì)繼續(xù)執(zhí)行其他語(yǔ)句;

對(duì)于持久性而言,redis 即使開啟了最嚴(yán)格的數(shù)據(jù)落地,由于保存是由后臺(tái)線程進(jìn)行的,主線程不會(huì)阻塞直到保存成功,所以從命令執(zhí)行成功到數(shù)據(jù)保存到硬盤之間,還是有一段非常小的間隔,所以這種模式下的事務(wù)也是不持久的。


感興趣的朋友可以搜一搜公眾號(hào)「 閱新技術(shù) 」,關(guān)注更多的推送文章。
可以的話,就順便點(diǎn)個(gè)贊、留個(gè)言、分享下,感謝各位支持!
閱新技術(shù),閱讀更多的新知識(shí)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 應(yīng)用場(chǎng)景 緩存 共享Session 消息隊(duì)列系統(tǒng) 分布式鎖 單線程的Redis為什么快 純內(nèi)存操作單線程操作,避免...
    Heng閱讀 204評(píng)論 0 0
  • 博文來(lái)源于:https://juejin.cn/post/6844903982209449991 Redis 和 ...
    小焱說(shuō)閱讀 512評(píng)論 0 0
  • Redis為什么這么快 完全基于內(nèi)存 數(shù)據(jù)結(jié)構(gòu)簡(jiǎn)單 采用單線程,避免了不必要的上下文切換和競(jìng)爭(zhēng)條件,不會(huì)因?yàn)殒i的問(wèn)...
    LegendGo閱讀 580評(píng)論 0 1
  • 什么是Redis? Redis 是一個(gè)使用 C 語(yǔ)言寫成的,開源的 key-value 數(shù)據(jù)庫(kù)。。和Memcach...
    yangfhit閱讀 280評(píng)論 0 0
  • 1 什么是redis? Redis 是一個(gè)基于內(nèi)存的高性能key-value數(shù)據(jù)庫(kù)。 (有空再補(bǔ)充,有理解錯(cuò)誤或不...
    java高并發(fā)閱讀 1,782評(píng)論 0 57

友情鏈接更多精彩內(nèi)容