大廠常問的Redis面試題


前言

如果你是一名后端人員,對於 Redis 應該都不會陌生,Redis實戰筆記,現在大部分的項目都已經運用到了 Redis 作為緩存層的搭建。面試過程中也越來越愛問關於 Redis 相關中的知識。今天我們一起來聊聊 Redis 集群中那些愛考,並且都需要知道的相關知識。

1、什么是Redis,Redis有哪些特點?

Redis全稱為:Remote Dictionary Server(遠程數據服務),Redis是一種支持key-value等多種數據結構的存儲系統。可用於緩存,事件發布或訂閱,高速隊列等場景。支持網絡,提供字符串,哈希,列表,隊列,集合結構直接存取,基於內存,可持久化。

特點1:豐富的數據類型

我們知道很多數據庫只能處理一種數據結構:

  • 傳統SQL數據庫處理二維關系數據;
  • MemCached數據庫,鍵和值都是字符串;
  • 文檔數據庫(MongoDB)是由Json/Bson組成的文檔。

當然不是他們這些數據庫不好,而是一旦數據庫提供數據結構不適合去做某件事情的話,程序寫起來就非常麻煩和不自然。

Redis雖然也是鍵值對數據庫,但是和Memcached不同的是:Redis的值不僅可以是字符串,它還可以是其他五中數據機構中的任意一種。通過選用不同的數據結構,用戶可以使用Redis解決各種各樣的問題,使用Redis,你碰到一個問題,首先會想到是選用那種數據結構把哪些功能問題解決掉,有了多樣的數據結構,方便你解決問題。

特點2:內存存儲

數據庫有兩種:一種是硬盤數據庫,一種是內存數據庫。

硬盤數據庫是把值存儲在硬盤上,在內存中就存儲一下索引,當硬盤數據庫想訪問硬盤的值時,它先在內存里找到索引,然后再找值。問題在於,在讀取和寫入硬盤的時候,如果讀寫比較多的時候,它會把硬盤的IO功能堵死。

內存存儲是將所有的數據都存儲在內存里面,數據讀取和寫入速度非常快。

特點3:持久化功能

將數據存儲在內存里面的數據保存到硬盤中,保證數據安全,方便進行數據備份和恢復。

2、Redis有哪些數據結構?

Redis是key-value數據庫,key的類型只能是String,但是value的數據類型就比較豐富了,主要包括五種:

  • String
  • Hash
  • List
  • Set
  • Sorted Set
 
image

(1)String字符串

SET KEY_NAME VALUE

string類型是二進制安全的。意思是redis的string可以包含任何數據。比如jpg圖片或者序列化的對象。 string類型是Redis最基本的數據類型,一個鍵最大能存儲512MB。

(2)Hash哈希

HSET KEY_NAME FIELD VALUE

Redis hash 是一個鍵值(key=>value)對集合。 Redis hash是一個string類型的field和value的映射表,hash特別適合用於存儲對象。

(3)List列表

//在 key 對應 list 的頭部添加字符串元素 LPUSH KEY_NAME VALUE1.. VALUEN //在 key 對應 list 的尾部添加字符串元素 RPUSH KEY_NAME VALUE1..VALUEN //對應 list 中刪除 count 個和 value 相同的元素 LREM KEY_NAME COUNT VALUE //返回 key 對應 list 的長度 LLEN KEY_NAME 

Redis 列表是簡單的字符串列表,按照插入順序排序。 可以添加一個元素到列表的頭部(左邊)或者尾部(右邊)

(4)Set集合

SADD KEY_NAME VALUE1...VALUEn

Redis的Set是string類型的無序集合。 集合是通過哈希表實現的,所以添加,刪除,查找的復雜度都是O(1)。

(5)Sorted Set有序集合

ZADD KEY_NAME SCORE1 VALUE1.. SCOREN VALUEN

Redis zset 和 set 一樣也是string類型元素的集合,且不允許重復的成員。 不同的是每個元素都會關聯一個double類型的分數。

redis正是通過分數來為集合中的成員進行從小到大的排序。

zset的成員是唯一的,但分數(score)卻可以重復。

3、一個字符串類型的值能存儲最大容量是多少?

查詢官方文檔(https://redis.io/topics/data-types)可以看到String類型的value值最多支持的長度為512M,所以正確的答案是512M。

 
image

4、能說一下Redis每種數據結構的使用場景嗎

(1)String的使用場景

字符串類型的使用場景:信息緩存、計數器、分布式鎖等等。

常用命令:get/set/del/incr/decr/incrby/decrby

實戰場景1:記錄每一個用戶的訪問次數,或者記錄每一個商品的瀏覽次數

方案:

常用鍵名: userid:pageview 或者 pageview:userid,如果一個用戶的id為123,那對應的redis key就為pageview:123,value就為用戶的訪問次數,增加次數可以使用命令:incr。

使用理由:每一個用戶訪問次數或者商品瀏覽次數的修改是很頻繁的,如果使用mysql這種文件系統頻繁修改會造成mysql壓力,效率也低。而使用redis的好處有二:使用內存,很快;單線程,所以無競爭,數據不會被改亂。

實戰場景2:緩存頻繁讀取,但是不常修改的信息,如用戶信息,視頻信息

方案:

業務邏輯上:先從redis讀取,有值就從redis讀取,沒有則從mysql讀取,並寫一份到redis中作為緩存,注意要設置過期時間。

鍵值設計上:

直接將用戶一條mysql記錄做序列化(通常序列化為json)作為值,userInfo:userid 作為key,鍵名如:userInfo:123,value存儲對應用戶信息的json串。如 key為:"user:id :name:1", value為"{"name":"leijia","age":18}"。

實戰場景3:限定某個ip特定時間內的訪問次數

方案:

用key記錄IP,value記錄訪問次數,同時key的過期時間設置為60秒,如果key過期了則重新設置,否則進行判斷,當一分鍾內訪問超過100次,則禁止訪問。

實戰場景4:分布式session

我們知道session是以文件的形式保存在服務器中的;如果你的應用做了負載均衡,將網站的項目放在多個服務器上,當用戶在服務器A上進行登陸,session文件會寫在A服務器;當用戶跳轉頁面時,請求被分配到B服務器上的時候,就找不到這個session文件,用戶就要重新登陸。

如果想要多個服務器共享一個session,可以將session存放在redis中,redis可以獨立於所有負載均衡服務器,也可以放在其中一台負載均衡服務器上;但是所有應用所在的服務器連接的都是同一個redis服務器。

(2)Hash的使用場景

以購物車為例子,用戶id設置為key,那么購物車里所有的商品就是用戶key對應的值了,每個商品有id和購買數量,對應hash的結構就是商品id為field,商品數量為value。如圖所示:

 
image

如果將商品id和商品數量序列化成json字符串,那么也可以用上面講的string類型存儲。下面對比一下這兩種數據結構:

|

對比項

|

string(json)

|

hash

|

總結一下:

當對象的某個屬性需要頻繁修改時,不適合用string+json,因為它不夠靈活,每次修改都需要重新將整個對象序列化並賦值;如果使用hash類型,則可以針對某個屬性單獨修改,沒有序列化,也不需要修改整個對象。比如,商品的價格、銷量、關注數、評價數等可能經常發生變化的屬性,就適合存儲在hash類型里。

(3)List的使用場景

列表本質是一個有序的,元素可重復的隊列。

實戰場景:定時排行榜

list類型的lrange命令可以分頁查看隊列中的數據。可將每隔一段時間計算一次的排行榜存儲在list類型中,如QQ音樂內地排行榜,每周計算一次存儲在list類型中,訪問接口時通過page和size分頁轉化成lrange命令獲取排行榜數據。

 
image

但是,並不是所有的排行榜都能用list類型實現,只有定時計算的排行榜才適合使用list類型存儲,與定時計算的排行榜相對應的是實時計算的排行榜,list類型不能支持實時計算的排行榜,下面介紹有序集合sorted set的應用場景時會詳細介紹實時計算的排行榜的實現。

(4)Set的使用場景

集合的特點是無序性和確定性(不重復)。

實戰場景:收藏夾

例如QQ音樂中如果你喜歡一首歌,點個『喜歡』就會將歌曲放到個人收藏夾中,每一個用戶做一個收藏的集合,每個收藏的集合存放用戶收藏過的歌曲id。

key為用戶id,value為歌曲id的集合。

 
image

(5)Sorted Set的使用場景

有序集合的特點是有序,無重復值。與set不同的是sorted set每個元素都會關聯一個score屬性,redis正是通過score來為集合中的成員進行從小到大的排序。

實戰場景:實時排行榜

QQ音樂中有多種實時榜單,比如飆升榜、熱歌榜、新歌榜,可以用redis key存儲榜單類型,score為點擊量,value為歌曲id,用戶每點擊一首歌曲會更新redis數據,sorted set會依據score即點擊量將歌曲id排序。

 
image

5、Redis如何做持久化的?能說一下RDB和AOF的實現原理嗎?

什么是持久化?

持久化(Persistence),即把數據(如內存中的對象)保存到可永久保存的存儲設備中(如磁盤)。持久化的主要應用是將內存中的對象存儲在數據庫中,或者存儲在磁盤文件中、XML數據文件中等等。

還可以從如下兩個層面簡單的理解持久化 :

  • 應用層:如果關閉(shutdown)你的應用然后重新啟動則先前的數據依然存在。
  • 系統層:如果關閉(shutdown)你的系統(電腦)然后重新啟動則先前的數據依然存在。

Redis為什么要持久化?

Redis是內存數據庫,為了保證效率所有的操作都是在內存中完成。數據都是緩存在內存中,當你重啟系統或者關閉系統,之前緩存在內存中的數據都會丟失再也不能找回。因此為了避免這種情況,Redis需要實現持久化將內存中的數據存儲起來。

Redis如何實現持久化?

Redis官方提供了不同級別的持久化方式:

  • RDB持久化:能夠在指定的時間間隔能對你的數據進行快照存儲。
  • AOF持久化:記錄每次對服務器寫的操作,當服務器重啟的時候會重新執行這些命令來恢復原始的數據,AOF命令以redis協議追加保存每次寫的操作到文件末尾。Redis還能對AOF文件進行后台重寫,使得AOF文件的體積不至於過大。
  • 不使用持久化:如果你只希望你的數據在服務器運行的時候存在,你也可以選擇不使用任何持久化方式。
  • 同時開啟RDB和AOF:你也可以同時開啟兩種持久化方式,在這種情況下當redis重啟的時候會優先載入AOF文件來恢復原始的數據,因為在通常情況下AOF文件保存的數據集要比RDB文件保存的數據集要完整。

這么多持久化方式我們應該怎么選?在選擇之前我們需要搞清楚每種持久化方式的區別以及各自的優劣勢。

RDB持久化

RDB(Redis Database)持久化是把當前內存數據生成快照保存到硬盤的過程,觸發RDB持久化過程分為手動觸發和自動觸發。

(1)手動觸發

手動觸發對應save命令,會阻塞當前Redis服務器,直到RDB過程完成為止,對於內存比較大的實例會造成長時間阻塞,線上環境不建議使用。

(2)自動觸發

自動觸發對應bgsave命令,Redis進程執行fork操作創建子進程,RDB持久化過程由子進程負責,完成后自動結束。阻塞只發生在fork階段,一般時間很短。

在redis.conf配置文件中可以配置:

save <seconds> <changes> 

表示xx秒內數據修改xx次時自動觸發bgsave。 如果想關閉自動觸發,可以在save命令后面加一個空串,即:

save ""

還有其他常見可以觸發bgsave,如:

  • 如果從節點執行全量復制操作,主節點自動執行bgsave生成RDB文件並發送給從節點。
  • 默認情況下執行shutdown命令時,如果沒有開啟AOF持久化功能則 自動執行bgsave。

bgsave工作機制

(1)執行bgsave命令,Redis父進程判斷當前是否存在正在執行的子進程,如RDB/AOF子進程,如果存在,bgsave命令直接返回。

(2)父進程執行fork操作創建子進程,fork操作過程中父進程會阻塞,通 過info stats命令查看latest_fork_usec選項,可以獲取最近一個fork操作的耗時,單位為微秒

(3)父進程fork完成后,bgsave命令返回“Background saving started”信息並不再阻塞父進程,可以繼續響應其他命令。

(4)子進程創建RDB文件,根據父進程內存生成臨時快照文件,完成后對原有文件進行原子替換。執行lastsave命令可以獲取最后一次生成RDB的 時間,對應info統計的rdb_last_save_time選項。

(5)進程發送信號給父進程表示完成,父進程更新統計信息,具體見 info Persistence下的rdb_*相關選項。

-- RDB持久化完 --

AOF持久化

AOF(append only file)持久化:以獨立日志的方式記錄每次寫命令, 重啟時再重新執行AOF文件中的命令達到恢復數據的目的。AOF的主要作用是解決了數據持久化的實時性,目前已經是Redis持久化的主流方式。

AOF持久化工作機制

開啟AOF功能需要配置:appendonly yes,默認不開啟。

AOF文件名 通過appendfilename配置設置,默認文件名是appendonly.aof。保存路徑同 RDB持久化方式一致,通過dir配置指定。

AOF的工作流程操作:命令寫入 (append)、文件同步(sync)、文件重寫(rewrite)、重啟加載 (load)。

(1)所有的寫入命令會追加到aof_buf(緩沖區)中。

(2)AOF緩沖區根據對應的策略向硬盤做同步操作。

AOF為什么把命令追加到aof_buf中?Redis使用單線程響應命令,如果每次寫AOF文件命令都直接追加到硬盤,那么性能完全取決於當前硬盤負載。先寫入緩沖區aof_buf中,還有另一個好處,Redis可以提供多種緩沖區同步硬盤的策略,在性能和安全性方面做出平衡。

(3)隨着AOF文件越來越大,需要定期對AOF文件進行重寫,達到壓縮的目的。

(4)當Redis服務器重啟時,可以加載AOF文件進行數據恢復。

AOF重寫(rewrite)機制

重寫的目的:

  • 減小AOF文件占用空間;
  • 更小的AOF 文件可以更快地被Redis加載恢復。

AOF重寫可以分為手動觸發和自動觸發:

  • 手動觸發:直接調用bgrewriteaof命令。
  • 自動觸發:根據auto-aof-rewrite-min-size和auto-aof-rewrite-percentage參數確定自動觸發時機。

auto-aof-rewrite-min-size:表示運行AOF重寫時文件最小體積,默認 為64MB。

auto-aof-rewrite-percentage:代表當前AOF文件空間 (aof_current_size)和上一次重寫后AOF文件空間(aof_base_size)的比值。

自動觸發時機

當aof_current_size>auto-aof-rewrite-minsize 並且(aof_current_size-aof_base_size)/aof_base_size>=auto-aof-rewritepercentage。

其中aof_current_size和aof_base_size可以在info Persistence統計信息中查看。

 
image

AOF文件重寫后為什么會變小?

(1)舊的AOF文件含有無效的命令,如:del key1, hdel key2等。重寫只保留最終數據的寫入命令。

(2)多條命令可以合並,如lpush list a,lpush list b,lpush list c可以直接轉化為lpush list a b c。

數據恢復流程說明:

(1)AOF持久化開啟且存在AOF文件時,優先加載AOF文件。

(2)AOF關閉或者AOF文件不存在時,加載RDB文件。

(3)加載AOF/RDB文件成功后,Redis啟動成功。

(4)AOF/RDB文件存在錯誤時,Redis啟動失敗並打印錯誤信息。

-- AOF持久化完 --

RDB和AOF的優缺點

RDB優點

  • RDB 是一個非常緊湊的文件,它保存了某個時間點的數據集,非常適用於數據集的備份,比如你可以在每個小時報保存一下過去24小時內的數據,同時每天保存過去30天的數據,這樣即使出了問題你也可以根據需求恢復到不同版本的數據集。
  • RDB 是一個緊湊的單一文件,很方便傳送到另一個遠端數據中心,非常適用於災難恢復。
  • RDB 在保存 RDB 文件時父進程唯一需要做的就是 fork 出一個子進程,接下來的工作全部由子進程來做,父進程不需要再做其他 IO 操作,所以 RDB 持久化方式可以最大化 Redis 的性能。
  • 與AOF相比,在恢復大的數據集的時候,RDB 方式會更快一些。

AOF優點

  • 你可以使用不同的 fsync 策略:無 fsync、每秒 fsync 、每次寫的時候 fsync .使用默認的每秒 fsync 策略, Redis 的性能依然很好( fsync 是由后台線程進行處理的,主線程會盡力處理客戶端請求),一旦出現故障,你最多丟失1秒的數據。
  • AOF文件是一個只進行追加的日志文件,所以不需要寫入seek,即使由於某些原因(磁盤空間已滿,寫的過程中宕機等等)未執行完整的寫入命令,你也也可使用redis-check-aof工具修復這些問題。
  • Redis 可以在 AOF 文件體積變得過大時,自動地在后台對 AOF 進行重寫: 重寫后的新 AOF 文件包含了恢復當前數據集所需的最小命令集合。 整個重寫操作是絕對安全的,因為 Redis 在創建新 AOF 文件的過程中,會繼續將命令追加到現有的 AOF 文件里面,即使重寫過程中發生停機,現有的 AOF 文件也不會丟失。 而一旦新 AOF 文件創建完畢,Redis 就會從舊 AOF 文件切換到新 AOF 文件,並開始對新 AOF 文件進行追加操作。
  • AOF 文件有序地保存了對數據庫執行的所有寫入操作, 這些寫入操作以 Redis 協議的格式保存, 因此 AOF 文件的內容非常容易被人讀懂, 對文件進行分析(parse)也很輕松。 導出(export) AOF 文件也非常簡單: 舉個例子, 如果你不小心執行了 FLUSHALL 命令, 但只要 AOF 文件未被重寫, 那么只要停止服務器, 移除 AOF 文件末尾的 FLUSHALL 命令, 並重啟 Redis , 就可以將數據集恢復到 FLUSHALL 執行之前的狀態。

RDB缺點

  • Redis 要完整的保存整個數據集是一個比較繁重的工作,你通常會每隔5分鍾或者更久做一次完整的保存,萬一在 Redis 意外宕機,你可能會丟失幾分鍾的數據。
  • RDB 需要經常 fork 子進程來保存數據集到硬盤上,當數據集比較大的時候, fork 的過程是非常耗時的,可能會導致 Redis 在一些毫秒級內不能響應客戶端的請求。

AOF缺點

  • 對於相同的數據集來說,AOF 文件的體積通常要大於 RDB 文件的體積。
  • 數據恢復(load)時AOF比RDB慢,通常RDB 可以提供更有保證的最大延遲時間。

RDB和AOF簡單對比總結

RDB優點:

  • RDB 是緊湊的二進制文件,比較適合備份,全量復制等場景
  • RDB 恢復數據遠快於 AOF

RDB缺點:

  • RDB 無法實現實時或者秒級持久化;
  • 新老版本無法兼容 RDB 格式。

AOF優點:

  • 可以更好地保護數據不丟失;
  • appen-only 模式寫入性能比較高;
  • 適合做災難性的誤刪除緊急恢復。

AOF缺點:

  • 對於同一份文件,AOF 文件要比 RDB 快照大;
  • AOF 開啟后,會對寫的 QPS 有所影響,相對於 RDB 來說 寫 QPS 要下降;
  • 數據庫恢復比較慢, 不合適做冷備。
    redis 內部使用文件事件處理器 file event handler,這個文件事件處理器是單線程的,所以 redis 才叫做單線程的模型。它采用 IO 多路復用機制同時監聽多個 socket,根據 socket 上的事件來選擇對應的事件處理器進行處理。

如果面試官繼續追問為啥 redis 單線程模型也能效率這么高?

  • 純內存操作
  • 核心是基於非阻塞的 IO 多路復用機制
  • 單線程反而避免了多線程的頻繁上下文切換問題

在實際生產環境中有時會遇到緩存穿透、緩存擊穿、緩存雪崩等異常場景,為了避免異常帶來巨大損失,我們需要了解每種異常發生的原因以及解決方案,幫助提升系統可靠性和高可用。

(1)緩存穿透

什么是緩存穿透?

緩存穿透是指用戶請求的數據在緩存中不存在即沒有命中,同時在數據庫中也不存在,導致用戶每次請求該數據都要去數據庫中查詢一遍,然后返回空。

如果有惡意攻擊者不斷請求系統中不存在的數據,會導致短時間大量請求落在數據庫上,造成數據庫壓力過大,甚至擊垮數據庫系統。

緩存穿透常用的解決方案

(1)布隆過濾器(推薦)

布隆過濾器(Bloom Filter,簡稱BF)由Burton Howard Bloom在1970年提出,是一種空間效率高的概率型數據結構。

布隆過濾器專門用來檢測集合中是否存在特定的元素。

如果在平時我們要判斷一個元素是否在一個集合中,通常會采用查找比較的方法,下面分析不同的數據結構查找效率:

  • 采用線性表存儲,查找時間復雜度為O(N)
  • 采用平衡二叉排序樹(AVL、紅黑樹)存儲,查找時間復雜度為O(logN)
  • 采用哈希表存儲,考慮到哈希碰撞,整體時間復雜度也要Olog(n/m)

當需要判斷一個元素是否存在於海量數據集合中,不僅查找時間慢,還會占用大量存儲空間。接下來看一下布隆過濾器如何解決這個問題。

布隆過濾器設計思想

布隆過濾器由一個長度為m比特的位數組(bit array)與k個哈希函數(hash function)組成的數據結構。位數組初始化均為0,所有的哈希函數都可以分別把輸入數據盡量均勻地散列。

當要向布隆過濾器中插入一個元素時,該元素經過k個哈希函數計算產生k個哈希值,以哈希值作為位數組中的下標,將所有k個對應的比特值由0置為1。

當要查詢一個元素時,同樣將其經過哈希函數計算產生哈希值,然后檢查對應的k個比特值:如果有任意一個比特為0,表明該元素一定不在集合中;如果所有比特均為1,表明該集合有可能性在集合中。為什么不是一定在集合中呢?因為不同的元素計算的哈希值有可能一樣,會出現哈希碰撞,導致一個不存在的元素有可能對應的比特位為1,這就是所謂“假陽性”(false positive)。相對地,“假陰性”(false negative)在BF中是絕不會出現的。

總結一下:布隆過濾器認為不在的,一定不會在集合中;布隆過濾器認為在的,可能在也可能不在集合中。

舉個例子:下圖是一個布隆過濾器,共有18個比特位,3個哈希函數。集合中三個元素x,y,z通過三個哈希函數散列到不同的比特位,並將比特位置為1。當查詢元素w時,通過三個哈希函數計算,發現有一個比特位的值為0,可以肯定認為該元素不在集合中。

 
image

布隆過濾器優缺點

優點:

  • 節省空間:不需要存儲數據本身,只需要存儲數據對應hash比特位
  • 時間復雜度低:插入和查找的時間復雜度都為O(k),k為哈希函數的個數

缺點:

  • 存在假陽性:布隆過濾器判斷存在,可能出現元素不在集合中;判斷准確率取決於哈希函數的個數
  • 不能刪除元素:如果一個元素被刪除,但是卻不能從布隆過濾器中刪除,這也是造成假陽性的原因了

布隆過濾器適用場景

  • 爬蟲系統url去重
  • 垃圾郵件過濾
  • 黑名單

(2)返回空對象

當緩存未命中,查詢持久層也為空,可以將返回的空對象寫到緩存中,這樣下次請求該key時直接從緩存中查詢返回空對象,請求不會落到持久層數據庫。為了避免存儲過多空對象,通常會給空對象設置一個過期時間。

這種方法會存在兩個問題:

  • 如果有大量的key穿透,緩存空對象會占用寶貴的內存空間。
  • 空對象的key設置了過期時間,在這段時間可能會存在緩存和持久層數據不一致的場景。

(3)緩存擊穿

什么是緩存擊穿?

緩存擊穿,是指一個key非常熱點,在不停的扛着大並發,大並發集中對這一個點進行訪問,當這個key在失效的瞬間,持續的大並發就穿破緩存,直接請求數據庫,就像在一個屏障上鑿開了一個洞。

緩存擊穿危害

數據庫瞬時壓力驟增,造成大量請求阻塞。

如何解決?

方案一:使用互斥鎖(mutex key)

這種思路比較簡單,就是讓一個線程回寫緩存,其他線程等待回寫緩存線程執行完,重新讀緩存即可。

 
image

同一時間只有一個線程讀數據庫然后回寫緩存,其他線程都處於阻塞狀態。如果是高並發場景,大量線程阻塞勢必會降低吞吐量。這種情況如何解決?大家可以在留言區討論。

如果是分布式應用就需要使用分布式鎖。

方案二:熱點數據永不過期

永不過期實際包含兩層意思:

  • 物理不過期,針對熱點key不設置過期時間
  • 邏輯過期,把過期時間存在key對應的value里,如果發現要過期了,通過一個后台的異步線程進行緩存的構建
 
image

從實戰看這種方法對於性能非常友好,唯一不足的就是構建緩存時候,其余線程(非構建緩存的線程)可能訪問的是老數據,對於不追求嚴格強一致性的系統是可以接受的。

(3)緩存雪崩

什么是緩存雪崩?

緩存雪崩是指緩存中數據大批量到過期時間,而查詢數據量巨大,請求直接落到數據庫上,引起數據庫壓力過大甚至宕機。和緩存擊穿不同的是,緩存擊穿指並發查同一條數據,緩存雪崩是不同數據都過期了,很多數據都查不到從而查數據庫。

緩存雪崩解決方案

常用的解決方案有:

  • 均勻過期
  • 加互斥鎖
  • 緩存永不過期
  • 雙層緩存策略

(1)均勻過期

設置不同的過期時間,讓緩存失效的時間點盡量均勻。通常可以為有效期增加隨機值或者統一規划有效期。

(2)加互斥鎖

跟緩存擊穿解決思路一致,同一時間只讓一個線程構建緩存,其他線程阻塞排隊。

(3)緩存永不過期

跟緩存擊穿解決思路一致,緩存在物理上永遠不過期,用一個異步的線程更新緩存。

(4)雙層緩存策略

使用主備兩層緩存:

主緩存:有效期按照經驗值設置,設置為主讀取的緩存,主緩存失效后從數據庫加載最新值。

備份緩存:有效期長,獲取鎖失敗時讀取的緩存,主緩存更新時需要同步更新備份緩存。

(4)緩存預熱

什么是緩存預熱?

緩存預熱就是系統上線后,將相關的緩存數據直接加載到緩存系統,這樣就可以避免在用戶請求的時候,先查詢數據庫,然后再將數據回寫到緩存。

如果不進行預熱, 那么 Redis 初始狀態數據為空,系統上線初期,對於高並發的流量,都會訪問到數據庫中, 對數據庫造成流量的壓力。

緩存預熱的操作方法

  • 數據量不大的時候,工程啟動的時候進行加載緩存動作;
  • 數據量大的時候,設置一個定時任務腳本,進行緩存的刷新;
  • 數據量太大的時候,優先保證熱點數據進行提前加載到緩存。

緩存降級是指緩存失效或緩存服務器掛掉的情況下,不去訪問數據庫,直接返回默認數據或訪問服務的內存數據。

在項目實戰中通常會將部分熱點數據緩存到服務的內存中,這樣一旦緩存出現異常,可以直接使用服務的內存數據,從而避免數據庫遭受巨大壓力。

降級一般是有損的操作,所以盡量減少降級對於業務的影響程度。

8、Redis的內存淘汰機制

Redis內存淘汰策略是指當緩存內存不足時,通過淘汰舊數據處理新加入數據選擇的策略。

如何配置最大內存?

(1)通過配置文件配置

修改redis.conf配置文件

maxmemory 1024mb //設置Redis最大占用內存大小為1024M 

注意:maxmemory默認配置為0,在64位操作系統下redis最大內存為操作系統剩余內存,在32位操作系統下redis最大內存為3GB。

(2)通過動態命令配置

Redis支持運行時通過命令動態修改內存大小:

127.0.0.1:6379> config set maxmemory 200mb //設置Redis最大占用內存大小為200M 127.0.0.1:6379> config get maxmemory //獲取設置的Redis能使用的最大內存大小 1) "maxmemory" 2) "209715200" 

淘汰策略的分類

Redis最大占用內存用完之后,如果繼續添加數據,如何處理這種情況呢?實際上Redis官方已經定義了八種策略來處理這種情況:

noeviction

默認策略,對於寫請求直接返回錯誤,不進行淘汰。

allkeys-lru

lru(less recently used), 最近最少使用。從所有的key中使用近似LRU算法進行淘汰。

volatile-lrulru(less recently used), 最近最少使用。從設置了過期時間的key中使用近似LRU算法進行淘汰。

allkeys-random

從所有的key中隨機淘汰。

volatile-random

從設置了過期時間的key中隨機淘汰。

volatile-ttl

ttl(time to live),在設置了過期時間的key中根據key的過期時間進行淘汰,越早過期的越優先被淘汰。

allkeys-lfu

lfu(Least Frequently Used),最少使用頻率。從所有的key中使用近似LFU算法進行淘汰。從Redis4.0開始支持。

volatile-lfu

lfu(Least Frequently Used),最少使用頻率。從設置了過期時間的key中使用近似LFU算法進行淘汰。從Redis4.0開始支持。

注意:當使用volatile-lru、volatile-random、volatile-ttl這三種策略時,如果沒有設置過期的key可以被淘汰,則和noeviction一樣返回錯誤。

LRU算法

LRU(Least Recently Used),即最近最少使用,是一種緩存置換算法。在使用內存作為緩存的時候,緩存的大小一般是固定的。當緩存被占滿,這個時候繼續往緩存里面添加數據,就需要淘汰一部分老的數據,釋放內存空間用來存儲新的數據。這個時候就可以使用LRU算法了。其核心思想是:如果一個數據在最近一段時間沒有被用到,那么將來被使用到的可能性也很小,所以就可以被淘汰掉。

LRU在Redis中的實現

Redis使用的是近似LRU算法,它跟常規的LRU算法還不太一樣。近似LRU算法通過隨機采樣法淘汰數據,每次隨機出5個(默認)key,從里面淘汰掉最近最少使用的key。

可以通過maxmemory-samples參數修改采樣數量, 如:maxmemory-samples 10

maxmenory-samples配置的越大,淘汰的結果越接近於嚴格的LRU算法,但因此耗費的CPU也很高。

Redis為了實現近似LRU算法,給每個key增加了一個額外增加了一個24bit的字段,用來存儲該key最后一次被訪問的時間。

Redis3.0對近似LRU的優化

Redis3.0對近似LRU算法進行了一些優化。新算法會維護一個候選池(大小為16),池中的數據根據訪問時間進行排序,第一次隨機選取的key都會放入池中,隨后每次隨機選取的key只有在訪問時間小於池中最小的時間才會放入池中,直到候選池被放滿。當放滿后,如果有新的key需要放入,則將池中最后訪問時間最大(最近被訪問)的移除。

當需要淘汰的時候,則直接從池中選取最近訪問時間最小(最久沒被訪問)的key淘汰掉就行。

LFU算法

LFU(Least Frequently Used),是Redis4.0新加的一種淘汰策略,它的核心思想是根據key的最近被訪問的頻率進行淘汰,很少被訪問的優先被淘汰,被訪問的多的則被留下來。

LFU算法能更好的表示一個key被訪問的熱度。假如你使用的是LRU算法,一個key很久沒有被訪問到,只剛剛是偶爾被訪問了一次,那么它就被認為是熱點數據,不會被淘汰,而有些key將來是很有可能被訪問到的則被淘汰了。如果使用LFU算法則不會出現這種情況,因為使用一次並不會使一個key成為熱點數據。

9、Redis有事務機制嗎?

  • 有事務機制。Redis事務生命周期: 開啟事務:使用MULTI開啟一個事務
  • 命令入隊列:每次操作的命令都會加入到一個隊列中,但命令此時不會真正被執行
  • 提交事務:使用EXEC命令提交事務,開始順序執行隊列中的命令

10、Redis事務到底是不是原子性的?

先看關系型數據庫ACID 中關於原子性的定義:原子性:一個事務(transaction)中的所有操作,要么全部完成,要么全部不完成,不會結束在中間某個環節。事務在執行過程中發生錯誤,會被恢復(Rollback)到事務開始前的狀態,就像這個事務從來沒有執行過一樣。

官方文檔對事務的定義:

  • 事務是一個單獨的隔離操作:事務中的所有命令都會序列化、按順序地執行。事務在執行的過程中,不會被其他客戶端發送來的命令請求所打斷。
  • 事務是一個原子操作:事務中的命令要么全部被執行,要么全部都不執行。EXEC 命令負責觸發並執行事務中的所有命令:如果客戶端在使用 MULTI 開啟了一個事務之后,卻因為斷線而沒有成功執行 EXEC ,那么事務中的所有命令都不會被執行。另一方面,如果客戶端成功在開啟事務之后執行 EXEC ,那么事務中的所有命令都會被執行。

官方認為Redis事務是一個原子操作,這是站在執行與否的角度考慮的。但是從ACID原子性定義來看,嚴格意義上講Redis事務是非原子型的,因為在命令順序執行過程中,一旦發生命令執行錯誤Redis是不會停止執行然后回滾數據。

11、Redis為什么不支持回滾(roll back)?

在事務運行期間雖然Redis命令可能會執行失敗,但是Redis依然會執行事務內剩余的命
令而不會執行回滾操作。如果你熟悉mysql關系型數據庫事務,你會對此非常疑惑,
Redis官方的理由如下: 只有當被調用的Redis命令有語法錯誤時,這條命令才會執行失
敗(在將這個命令放入事務隊列期間,Redis能夠發現此類問題),或者對某個鍵執行不
符合其數據類型的操作:實際上,這就意味着只有程序錯誤才會導致Redis命令執行失
敗,這種錯誤很有可能在程序開發期間發現,一般很少在生產環境發現。 支持事務回滾
能力會導致設計復雜,這與Redis的初衷相違背,Redis的設計目標是功能簡化及確保更
快的運行速度。

對於官方的這種理由有一個普遍的反對觀點:程序有bug怎么辦?但其實回歸不能解決程序的bug,比如某位粗心的程序員計划更新鍵A,實際上最后更新了鍵B,回滾機制是沒法解決這種人為錯誤的。正因為這種人為的錯誤不太可能進入生產系統,所以官方在設計Redis時選用更加簡單和快速的方法,沒有實現回滾的機制。

12、Redis事務相關的命令有哪幾個?

(1)WATCH可以為Redis事務提供 check-and-set (CAS)行為。被WATCH的鍵會被監視,並會發覺這些鍵是否被改動過了。 如果有至少一個被監視的鍵在 EXEC 執行之前被修改了, 那么整個事務都會被取消, EXEC 返回nil-reply來表示事務已經失敗。

(2)MULTI

用於開啟一個事務,它總是返回OK。MULTI執行之后,客戶端可以繼續向服務器發送任意多條命令, 這些命令不會立即被執行,而是被放到一個隊列中,當 EXEC命令被調用時, 所有隊列中的命令才會被執行。

(3)UNWATCH

取消 WATCH 命令對所有 key 的監視,一般用於DISCARD和EXEC命令之前。如果在執行 WATCH 命令之后, EXEC 命令或 DISCARD 命令先被執行了的話,那么就不需要再執行 UNWATCH 了。因為 EXEC 命令會執行事務,因此 WATCH 命令的效果已經產生了;而 DISCARD 命令在取消事務的同時也會取消所有對 key 的監視,因此這兩個命令執行之后,就沒有必要執行 UNWATCH 了。

(4)DISCARD

當執行 DISCARD 命令時, 事務會被放棄, 事務隊列會被清空,並且客戶端會從事務狀態中退出。

(5)EXEC

負責觸發並執行事務中的所有命令:

如果客戶端成功開啟事務后執行EXEC,那么事務中的所有命令都會被執行。

如果客戶端在使用MULTI開啟了事務后,卻因為斷線而沒有成功執行EXEC,那么事務中的所有命令都不會被執行。需要特別注意的是:即使事務中有某條/某些命令執行失敗了,事務隊列中的其他命令仍然會繼續執行,Redis不會停止執行事務中的命令,而不會像我們通常使用的關系型數據庫一樣進行回滾。

13、什么是Redis主從復制?

主從復制,是指將一台Redis服務器的數據,復制到其他的Redis服務器。前者稱為主節點(master),后者稱為從節點(slave);數據的復制是單向的,只能由主節點到從節點。

主從復制的作用

  • 數據冗余:主從復制實現了數據的熱備份,是持久化之外的一種數據冗余方式。
  • 故障恢復:當主節點出現問題時,可以由從節點提供服務,實現快速的故障恢復;實際上是一種服務的冗余。
  • 負載均衡:在主從復制的基礎上,配合讀寫分離,可以由主節點提供寫服務,由從節點提供讀服務,分擔服務器負載;尤其是在寫少讀多的場景下,通過多個從節點分擔讀負載,可以大大提高Redis服務器的並發量。
  • 高可用基石:主從復制還是哨兵和集群能夠實施的基礎,因此說主從復制是Redis高可用的基礎。

主從復制實現原理

主從復制過程主要可以分為3個階段:連接建立階段、數據同步階段、命令傳播階段。

連接建立階段

該階段的主要作用是在主從節點之間建立連接,為數據同步做好准備。

步驟1:保存主節點信息

slaveof命令是異步的,在從節點上執行slaveof命令,從節點立即向客戶端返回ok,從節點服務器內部維護了兩個字段,即masterhost和masterport字段,用於存儲主節點的ip和port信息。

步驟2:建立socket連接

從節點每秒1次調用復制定時函數replicationCron(),如果發現了有主節點可以連接,便會根據主節點的ip和port,創建socket連接。

從節點為該socket建立一個專門處理復制工作的文件事件處理器,負責后續的復制工作,如接收RDB文件、接收命令傳播等。

主節點接收到從節點的socket連接后(即accept之后),為該socket創建相應的客戶端狀態,並將從節點看做是連接到主節點的一個客戶端,后面的步驟會以從節點向主節點發送命令請求的形式來進行。

步驟3:發送ping命令

從節點成為主節點的客戶端之后,發送ping命令進行首次請求,目的是:檢查socket連接是否可用,以及主節點當前是否能夠處理請求。

從節點發送ping命令后,可能出現3種情況:

(1)返回pong:說明socket連接正常,且主節點當前可以處理請求,復制過程繼續。

(2)超時:一定時間后從節點仍未收到主節點的回復,說明socket連接不可用,則從節點斷開socket連接,並重連。

(3)返回pong以外的結果:如果主節點返回其他結果,如正在處理超時運行的腳本,說明主節點當前無法處理命令,則從節點斷開socket連接,並重連。

步驟4:身份驗證

如果從節點中設置了masterauth選項,則從節點需要向主節點進行身份驗證;沒有設置該選項,則不需要驗證。從節點進行身份驗證是通過向主節點發送auth命令進行的,auth命令的參數即為配置文件中的masterauth的值。

如果主節點設置密碼的狀態,與從節點masterauth的狀態一致(一致是指都存在,且密碼相同,或者都不存在),則身份驗證通過,復制過程繼續;如果不一致,則從節點斷開socket連接,並重連。

步驟5:發送從節點端口信息

身份驗證之后,從節點會向主節點發送其監聽的端口號(前述例子中為6380),主節點將該信息保存到該從節點對應的客戶端的slave_listening_port字段中;該端口信息除了在主節點中執行info Replication時顯示以外,沒有其他作用。

數據同步階段

主從節點之間的連接建立以后,便可以開始進行數據同步,該階段可以理解為從節點數據的初始化。具體執行的方式是:從節點向主節點發送psync命令(Redis2.8以前是sync命令),開始同步。

數據同步階段是主從復制最核心的階段,根據主從節點當前狀態的不同,可以分為全量復制和部分復制,后面再講解這兩種復制方式以及psync命令的執行過程,這里不再詳述。

命令傳播階段

數據同步階段完成后,主從節點進入命令傳播階段;在這個階段主節點將自己執行的寫命令發送給從節點,從節點接收命令並執行,從而保證主從節點數據的一致性。

需要注意的是,命令傳播是異步的過程,即主節點發送寫命令后並不會等待從節點的回復;因此實際上主從節點之間很難保持實時的一致性,延遲在所難免。數據不一致的程度,與主從節點之間的網絡狀況、主節點寫命令的執行頻率、以及主節點中的repl-disable-tcp-nodelay配置等有關。

14、Sentinel(哨兵模式)的原理你能講一下嗎?

Redis 的主從復制模式下,一旦主節點由於故障不能提供服務,需要手動將從節點晉升為主節點,同時還要通知客戶端更新主節點地址,這種故障處理方式從一定程度上是無法接受的。

Redis 2.8 以后提供了 Redis Sentinel 哨兵機制來解決這個問題。

Redis Sentinel 是 Redis 高可用的實現方案。Sentinel 是一個管理多個 Redis 實例的工具,它可以實現對 Redis 的監控、通知、自動故障轉移。

Redis Sentinel架構圖如下:

 
image

 

哨兵模式的原理

哨兵模式的主要作用在於它能夠自動完成故障發現和故障轉移,並通知客戶端,從而實現高可用。哨兵模式通常由一組 Sentinel 節點和一組(或多組)主從復制節點組成。

心跳機制

(1)Sentinel與Redis Node

Redis Sentinel 是一個特殊的 Redis 節點。在哨兵模式創建時,需要通過配置指定 Sentinel 與 Redis Master Node 之間的關系,然后 Sentinel 會從主節點上獲取所有從節點的信息,之后 Sentinel 會定時向主節點和從節點發送 info 命令獲取其拓撲結構和狀態信息。

(2)Sentinel與Sentinel

基於 Redis 的訂閱發布功能, 每個 Sentinel 節點會向主節點的 sentinel:hello 頻道上發送該 Sentinel 節點對於主節點的判斷以及當前 Sentinel 節點的信息 ,同時每個 Sentinel 節點也會訂閱該頻道, 來獲取其他 Sentinel 節點的信息以及它們對主節點的判斷。

通過以上兩步所有的 Sentinel 節點以及它們與所有的 Redis 節點之間都已經彼此感知到,之后每個 Sentinel 節點會向主節點、從節點、以及其余 Sentinel 節點定時發送 ping 命令作為心跳檢測, 來確認這些節點是否可達。

故障轉移

每個 Sentinel 都會定時進行心跳檢查,當發現主節點出現心跳檢測超時的情況時,此時認為該主節點已經不可用,這種判定稱為主觀下線。

之后該 Sentinel 節點會通過 sentinel ismaster-down-by-addr 命令向其他 Sentinel 節點詢問對主節點的判斷, 當 quorum(法定人數) 個 Sentinel 節點都認為該節點故障時,則執行客觀下線,即認為該節點已經不可用。這也同時解釋了為什么必須需要一組 Sentinel 節點,因為單個 Sentinel 節點很容易對故障狀態做出誤判。

這里 quorum 的值是我們在哨兵模式搭建時指定的,后文會有說明,通常為 Sentinel節點總數/2+1,即半數以上節點做出主觀下線判斷就可以執行客觀下線。

因為故障轉移的工作只需要一個 Sentinel 節點來完成,所以 Sentinel 節點之間會再做一次選舉工作, 基於 Raft 算法選出一個 Sentinel 領導者來進行故障轉移的工作。

被選舉出的 Sentinel 領導者進行故障轉移的具體步驟如下:

(1)在從節點列表中選出一個節點作為新的主節點

  • 過濾不健康或者不滿足要求的節點;
  • 選擇 slave-priority(優先級)最高的從節點, 如果存在則返回, 不存在則繼續;
  • 選擇復制偏移量最大的從節點 , 如果存在則返回, 不存在則繼續;
  • 選擇 runid 最小的從節點。

(2)Sentinel 領導者節點會對選出來的從節點執行 slaveof no one 命令讓其成為主節點。

(3)Sentinel 領導者節點會向剩余的從節點發送命令,讓他們從新的主節點上復制數據。

(4)Sentinel 領導者會將原來的主節點更新為從節點, 並對其進行監控, 當其恢復后命令它去復制新的主節點。

15、Cluster(集群)的原理你能講一下嗎?

引入Cluster模式的原因: 不管是主從模式還是哨兵模式都只能由一個master在寫數據,在海量數據高並發場景,一個節點寫數據容易出現瓶頸,引入Cluster模式可以實現多個節點同時寫數據。

Redis-Cluster采用無中心結構,每個節點都保存數據,節點之間互相連接從而知道整個集群狀態。

 
image

如圖所示Cluster模式其實就是多個主從復制的結構組合起來的,每一個主從復制結構可以看成一個節點,那么上面的Cluster集群中就有三個節點。

16、Memcache與Redis的區別都有哪些?

存儲方式Memecache把數據全部存在內存之中,斷電后會掛掉,數據不能超過內存大小。

Redis有部份存在硬盤上,這樣能保證數據的持久性。

數據支持類型

Memcache對數據類型支持相對簡單。

Redis有豐富的數據類型。

使用底層模型不同

它們之間底層實現方式 以及與客戶端之間通信的應用協議不一樣。

Redis直接自己構建了VM 機制 ,因為一般的系統調用系統函數的話,會浪費一定的時間去移動和請求。

17、假如Redis里面有1億個key,其中有10w個key是以某個固定的已知的前綴開頭的,如果將它們全部找出來?

使用keys指令可以掃出指定模式的key列表:
keys pre*

18、如果這個redis正在給線上的業務提供服務,那使用keys指令會有什么問題?

redis 的單線程的。keys 指令會導致線 程阻塞一段時間,線上服務會停頓,直到指令執行完畢,服務才能恢復。這個時 候可以使用 scan 指令,scan 指令可以無阻塞的提取出指定模式的 key 列表,但是會有一定的重復概率,在客戶端做一次去重就可以了,但是整體所花費的時間 會比直接用 keys 指令長。

19、如果有大量的key需要設置同一時間過期,一般需要注意什么?

如果大量的key過期時間設置的過於集中,到過期的那個時間點,Redis可能會出現短暫的卡頓現象(因為redis是單線程的)。嚴重的話可能會導致服務器雪崩,所以我們一般在過期時間上加一個隨機值,讓過期時間盡量分散。

20、Redis常用的客戶端有哪些?

Jedis:是老牌的Redis的Java實現客戶端,提供了比較全面的Redis命令的支持。

Redisson:實現了分布式和可擴展的Java數據結構。

Lettuce:高級Redis客戶端,用於線程安全同步,異步和響應使用,支持集群,Sentinel,管道和編碼器。

優點:

Jedis:比較全面的提供了Redis的操作特性。

Redisson:促使使用者對Redis的關注分離,提供很多分布式相關操作服務,例如,分布式鎖,分布式集合,可通過Redis支持延遲隊列。

Lettuce:基於Netty框架的事件驅動的通信層,其方法調用是異步的。Lettuce的API是線程安全的,所以可以操 作單個Lettuce連接來完成各種操作。

總結

文章到這里就結束了!以上Redis的面試題要是刷的不過癮



作者:架構師小麒
鏈接:https://www.jianshu.com/p/de78aead9d9b
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM