Redis的一些常見面試題總結

1. Redis為什么這么快?

  1. 完全基于內存,絕大部分請求是純粹的內存操作,執(zhí)行效率非常高。數(shù)據(jù)存在內存中,類似于HashMap,HashMap的優(yōu)勢就是查找和操作的時間復雜度都是O(1);

  2. 數(shù)據(jù)結構簡單,對數(shù)據(jù)操作也簡單,Redis中的數(shù)據(jù)結構是專門進行設計的;

  3. 采用單線程(該單線程指的是處理網絡請求的線程),避免了不必要的上下文切換和競爭條件,也不存在多進程或者多線程導致的切換而消耗 CPU,不用去考慮各種鎖的問題,不存在加鎖釋放鎖操作,沒有因為可能出現(xiàn)死鎖而導致的性能消耗;

  4. 使用多路I/O復用模型,非阻塞IO;

2. Redis中常用數(shù)據(jù)類型

查看詳細

3. 從海量Key里查詢出某一固定前綴的Key

留意細節(jié):

  • 摸清數(shù)據(jù)規(guī)模,即問清楚邊界;

使用keys指令來掃出指定模式的key列表,

使用keys對線上的業(yè)務的影響:

KEYS pattern:查找所有符合給定模式pattern的key

缺點:

  • KEYS指令一次性返回所有匹配的key;

  • 鍵的數(shù)量過大會使得服務卡頓;

這時可以使用SCAN指令:

SCAN cursor [MATCH pattern] [COUNT count]

  • 基于游標的迭代器,需要基于上一次的游標延續(xù)之前的迭代過程;

  • 以0作為游標開始一次新的迭代,直到命令返回游標0完成一次遍歷;

  • 不保證每次執(zhí)行都返回某個給定數(shù)量的元素,支持模糊查詢;

  • 對于增量式迭代命令,一次返回的數(shù)量不可控,只能是大概率符合count參數(shù);

>scan 0 match k1* count 10

4. 如何通過Redis實現(xiàn)分布式鎖

分布式鎖需要解決的問題:

  • 互斥性

  • 安全性

  • 死鎖:一個持有鎖的客戶端宕機而導致其他客戶端再也無法獲得鎖,從而導致的死鎖;

  • 容錯

如何實現(xiàn):

SETNX(Set if not exsist) key value:如果key不存在,則創(chuàng)建并賦值。因為SETNX有上述功能,并且操作都是原子的,因此在初期的時候可以用來實現(xiàn)分布式鎖。

  • 時間復雜度:O(1)

  • 返回值:設置成功,返回1,表明此時沒有其他線程占用該資源;設置失敗,返回0,表示此時有別的線程正在占用該資源。

使用EXPIRE key seconds來解決SETNX長期有效的問題:

  • 設置key的生存時間,當key過期時(生存時間為0),會被自動刪除;
RedisService redisService = SpringUtils.getBean(RedisService.class);
long status = redisService.setnx(key,"1");

if(status == 1){
    redisService.expire(key,expire);
    //執(zhí)行獨占資源邏輯
    doOcuppiedWork();
}
  • 以上方法的缺點:原子性無法得到滿足

從Redis 2.1.6 以后,原子操作set:

SET key value [EX seconds] [PX milliseconds] [NX|XX]

  • EX seconds:設置鍵的過期時間為second(秒)

  • PX milliseconds:設置鍵的過期時間為millisecond(毫秒)

  • NX:只在鍵不存在的時候,才對鍵進行設置操作,效果等同于setnx

  • XX:只在鍵已存在的時候,才對鍵進行設置操作

  • SET操作成功完成時,返回OK,否則則返回nil

> set lock 123 ex 10 nx
OK
> set lock 122 ex 10 nx
(nil)

代碼實現(xiàn)例如:

RedisService redisService = SpringUtils.getBean(RedisService.class);
String result = redisService.set(lockKey,requestId,SET_IF_NOT_EXIST,
                                 SET_WITH_WITH_EXPIRE_TIME,expireTime);
if("OK".equals(result)){
    //執(zhí)行獨占資源邏輯
    doOcuppiedWork();
}

大量key同時過期的注意事項:

集中過期,由于清楚大量key很耗時,會出現(xiàn)短暫的卡頓現(xiàn)象。

解決方法:在設置key的過期時間的時候,給每個key加上一個隨機值。

5. 如何使用Redis做異步隊列

使用List作為隊列,RPUSH生產消息,LPOP消費消息。

  • 缺點:沒有等待隊列中有值就直接消費;

  • 彌補:可以在應用層引入Sleep機制去調用LPOP重試

BLPOP key [key ...] timeout:阻塞直到隊列有消息就能夠返回或超時

  • 缺點:只能供一個消費者消費

pub/sub:主題發(fā)布-訂閱模式

  • 發(fā)送者(publish)發(fā)送消息,訂閱者(subscribe)接收消息;

  • 訂閱者可以訂閱任意數(shù)量的頻道(Topic);

?

?

  • 缺點:消息的發(fā)布是無狀態(tài)的,無法保證可達性,即發(fā)送完該消息無法保證該消息被接收到。若想解決該問題需要使用專業(yè)的消息隊列,例如Kafka等。

6. Redis如何做持久化

Redis有三種持久化的方式:

  1. RDB(快照)持久化:保存某個時間點的全量數(shù)據(jù)快照;

缺點:

  • 內存數(shù)據(jù)的全量同步,數(shù)據(jù)量大會由于I/O而嚴重影響性能;

  • 可能會因為Redis掛掉而丟失從當前至最近一次快照期間的數(shù)據(jù);

redis.conf文件中:

save 900 1 #900秒之內如果有1條寫入指令就觸發(fā)一次快照
save 300 10
save 60 10000
?

stop-writes-on-bgsave-error yes #表示備份進程出錯的時候,主進程就停止接收新的寫入操作,是為了保護持久化數(shù)據(jù)的一致性
?
rdbcompression no #RDB的壓縮設置為no,因為壓縮會占用更多的CPU資源

手動觸發(fā):

  • SAVE:阻塞Redis的服務器進程,知道RDB文件被創(chuàng)建完畢,很少被使用;

  • BGSAVE:Fork出一個子進程來創(chuàng)建RDB文件,不會阻塞服務器主進程。

自動觸發(fā):

  • 根據(jù)redis.conf配置里的Save m n 定時觸發(fā)(用的是BGSAVE)

  • 主從復制,主節(jié)點自動觸發(fā)。從節(jié)點全量復制時,主節(jié)點發(fā)送RDB文件給從節(jié)點完成復制操作,主節(jié)點這時候會觸發(fā)BGSAVE;

  • 執(zhí)行Debug Reload;

  • 執(zhí)行Shutdown且沒有開啟AOF持久化

BGSAVE原理:

?

?

  • 檢查子進程的目的是:為了防止子進程之間的競爭;

  • 系統(tǒng)(Linux)調用fork():創(chuàng)建進程,實現(xiàn)Copy-on-write(寫時復制)。傳統(tǒng)方式下,在fork進程時直接把所有資源全部復制給子進程,這種實現(xiàn)方式簡單但是效率低下。Linux為了降低創(chuàng)建子進程的成本,改進fork實現(xiàn),當父進程創(chuàng)建子進程時,內核只為子進程創(chuàng)建虛擬空間,父子兩個進程使用的是相同的物理空間,只有子進程發(fā)生更改的時候,才會為子進程分配獨立的物理空間。

Copy-on-write:

如果有多個調用者同時要求相同資源(如內存或磁盤上的數(shù)據(jù)存儲),他們會共同獲取相同的指針指向相同的資源,直到某個調用者試圖修改資源的內容時,系統(tǒng)才會真正復制一份專用副本給該調用者,而其他調用者所見到的最初的資源仍然保持不變。

  1. AOF(Append-Only-File)持久化:保持寫狀態(tài)
  • 記錄下除了查詢以外的所有變更數(shù)據(jù)庫狀態(tài)的指令,所有寫入AOF的指令都是以Redis協(xié)議格式來保存的;

  • 以append的形式追加保存到AOF文件中(增量),就算遇到停電的情況也能盡最大全力去保證數(shù)據(jù)的無損;

日志重寫解決AOF文件大小不斷增大的問題,原理如下:

  • 調用fork(),創(chuàng)建一個子進程;

  • 子進程把新的AOF寫到一個臨時文件里面,不依賴于原來的AOF文件;

  • 主進程持續(xù)將新的變動同時寫到內存buffer中和原來的AOF文件里;

  • 主進程獲取子進程重寫AOF的完成信號,往新的AOF文件同步增量變動;

  • 使用新的AOF文件替換舊的AOF文件

?

?

對于上圖有幾個關鍵點:

  • 在重寫期間,由于主進程依然在響應命令,為了保證最終備份的完整性。因此它依然會寫入舊的AOF file中,如果重寫失敗,能夠保證數(shù)據(jù)不丟失。

  • 為了把重寫期間響應的寫入信息也寫入到新的文件中,因此也會為子進程保留一個buf,防止新寫的file丟失數(shù)據(jù)。

  • 重寫是直接把當前內存的數(shù)據(jù)生成對應命令,并不需要讀取老的AOF文件進行分析、命令合并。

  • AOF文件直接采用的文本協(xié)議,主要是兼容性好、追加方便、可讀性高。

RDB和AOF文件共存情況下的恢復流程

?

?

RDB和AOF的優(yōu)缺點:

類別 優(yōu)點 缺點
RDB 全量數(shù)據(jù)快照,文件小,恢復快 無法保存最近一次快照之后的數(shù)據(jù),會丟失這部分的數(shù)據(jù)
AOF 可讀性高,適合保存增量數(shù)據(jù),數(shù)據(jù)不易丟失 文件體積大,恢復時間長
  1. RDB-AOF混合持久化方式

在Redis 4.0之后推出了混合持久化方式,而且作為默認的配置方式。先以RDB方式從管道寫全量數(shù)據(jù)再使用AOF方式從管道追加。AOF文件先半段是RDB形式的全量數(shù)據(jù),后半段是Redis命令形式的增量數(shù)據(jù)。

  • BGSAVE做鏡像全量持久化,AOF做增量持久化。因為BGSAVE需要耗費大量的時間,不夠實時,在停機的時候會造成大量數(shù)據(jù)丟失,這時需要AOF配合使用。在Redis實例重啟的時候,會使用BGSAVE持久化文件重新構建內容,再使用AOF重放近期的操作指令,來實現(xiàn)完整恢復重啟之前的狀態(tài)。

7. 使用Pineline的好處

  • Pineline和Linux的管道類似;

  • Redis基于請求/響應模型,單個請求處理需要一一應答;

  • Pineline批量執(zhí)行指令,節(jié)省了多次I/O往返的時間;

  • 有順序依賴的指令建議分批發(fā)送;

Redis的同步機制:

主從同步原理

全同步過程:

  • Salve發(fā)送sync命令到Master;

  • Master啟動一個后臺進程,將Redis中的數(shù)據(jù)快照保存到文件中(BGSAVE);

  • Master將保存數(shù)據(jù)快照期間接收到的寫命令(增量數(shù)據(jù))緩存起來;

  • Master完成寫文件操作后,將該文件發(fā)送給Salve;

  • Salve接收到文件之后,使用新的AOF文件替換掉舊的AOF文件;

  • Master將這期間收集的增量寫命令發(fā)送給Salve端,進行回放。

全同步完成后,后續(xù)所有寫操作都是在Master上進行,讀操作都是在Salve上進行。

增量同步過程:

  • Master接收到用戶的操作指令,判斷是否需要傳播到Slave;

  • 首先將操作轉換成Redis內部協(xié)議格式并以字符串的形式存儲,然后將字符串存儲的操作紀錄追加到AOF文件;

  • 將操作傳播到其他Slave:1. 對齊主從庫,確保從數(shù)據(jù)庫的該操作的數(shù)據(jù)庫;2. 將命令和參數(shù)按照Redis內部協(xié)議格式往響應緩存中寫入指令;

  • 將緩存中的數(shù)據(jù)發(fā)送給Slave。

主從模式的弊端在于不具有高可用性,當Master掛掉以后,Redis將不能對外提供寫入操作。

Redis Sentinel(Redis哨兵)

解決主從同步Master宕機后的主從切換問題:

  • 監(jiān)控:檢查主從服務器是否運行正常;

  • 提醒:通過API向管理員或者其他應用程序發(fā)送故障通知;

  • 自動故障遷移:主從切換;

留言協(xié)議Gossip

在雜亂無章中尋求一致

  • 每個節(jié)點都隨機地與對方通信,最終所有節(jié)點的狀態(tài)都會達到一致;

  • 種子節(jié)點定期(每秒)隨機向其他節(jié)點發(fā)送節(jié)點列表以及需要傳播的消息;

  • 不保證信息一定會傳遞給所有節(jié)點,但是最終會趨于一致。

8. Redis的集群原理

如何從海量數(shù)據(jù)里快速找到所需?

  • 分片:按照某種規(guī)則去劃分數(shù)據(jù),分散存儲在多個節(jié)點上;

  • 常規(guī)的按照哈希劃分無法實現(xiàn)節(jié)點的動態(tài)增減,為了解決這個問題引入了一致性哈希算法。

一致性哈希算法:對2^32取模,將哈希值空間組織成虛擬的圓環(huán)

?

?

將數(shù)據(jù)key使用相同的函數(shù)Hash計算出哈希值,具體可以選擇服務器的ip或主機名作為關鍵字進行hash,這樣就能確定每個服務器在Hash環(huán)上的位置。接下來,對數(shù)據(jù)使用同樣的方法進行Hash去定位訪問到的服務器,沿環(huán)順時針行走第一臺遇到的服務器就是存儲的目標服務器。

綜上所述,一致性哈希算法對于節(jié)點的增減都只需要定位環(huán)空間中的一小部分數(shù)據(jù),具有較好的容錯性和擴展性。

缺點:服務節(jié)點較少的時候,Hash環(huán)的數(shù)據(jù)傾斜問題

?

?

解決方法:引入虛擬節(jié)點,即對每個服務器節(jié)點計算多個hash,計算結果的位置都放置一個節(jié)點稱為虛擬節(jié)點。具體做法,例如在服務器ip或主機名后添加后綴名。

?

?

參考資料

https://coding.imooc.com/class/303.html

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

相關閱讀更多精彩內容

  • 一、Redis高可用概述 在介紹Redis高可用之前,先說明一下在Redis的語境中高可用的含義。 我們知道,在w...
    空語閱讀 1,673評論 0 2
  • 企業(yè)級redis集群架構的特點 海量數(shù)據(jù) 高并發(fā) 高可用 要達到高可用,持久化是不可減少的,持久化主要是做災難恢復...
    lucode閱讀 2,280評論 0 7
  • 原帖地址:http://www.itdecent.cn/p/2f14bc570563 redis概述 Redis...
    onlyHalfSoul閱讀 2,228評論 0 28
  • 因前兩天服務器掛掉了,導致項目使用的redis數(shù)據(jù)庫異常,丟失了部分數(shù)據(jù),為了能夠更好的使用Redis,結合網上的...
    呦_小宋啊閱讀 2,390評論 2 9
  • 輝達閱讀 251評論 0 0

友情鏈接更多精彩內容