2021年最新Redis面試題匯總(3)

1、Redis 怎麼保證高可用、有哪些集群模式

主從復制、哨兵模式、集群模式。

2、主從復制

在當前最新的 Redis 6.0 中,主從復制的完整過程如下:

1)開啟主從復制

通常有以下三種方式:

  • 在 slave 直接執行命令:slaveof <masterip> <masterport>
  • 在 slave 配置文件中加入:slaveof <masterip> <masterport>
  • 使用啟動命令:–slaveof <masterip> <masterport>

註:在 Redis 5.0 之後,slaveof 相關命令和配置已經被替換成 replicaof,例如 replicaof <masterip> <masterport>。為瞭兼容舊版本,通過配置的方式仍然支持 slaveof,但是通過命令的方式則不行瞭。

2)建立套接字(socket)連接

slave 將根據指定的 IP 地址和端口,向 master 發起套接字(socket)連接,master 在接受(accept) slave 的套接字連接之後,為該套接字創建相應的客戶端狀態,此時連接建立完成。

3)發送PING命令

slave 向 master 發送一個 PING 命令,以檢査套接字的讀寫狀態是否正常、 master 能否正常處理命令請求。

4)身份驗證

slave 向 master 發送 AUTH password 命令來進行身份驗證。

5)發送端口信息

在身份驗證通過後後, slave 將向 master 發送自己的監聽端口號, master 收到後記錄在 slave 所對應的客戶端狀態的 slave_listening_port 屬性中。

6)發送IP地址

如果配置瞭 slave_announce_ip,則 slave 向 master 發送 slave_announce_ip 配置的 IP 地址, master 收到後記錄在 slave 所對應的客戶端狀態的 slave_ip 屬性。

該配置是用於解決服務器返回內網 IP 時,其他服務器無法訪問的情況。可以通過該配置直接指定公網 IP。

7)發送CAPA

CAPA 全稱是 capabilities,這邊表示的是同步復制的能力。slave 會在這一階段發送 capa 告訴 master 自己具備的(同步)復制能力, master 收到後記錄在 slave 所對應的客戶端狀態的 slave_capa 屬性。

8)數據同步

slave 將向 master 發送 PSYNC 命令, master 收到該命令後判斷是進行部分重同步還是完整重同步,然後根據策略進行數據的同步。

9)命令傳播

當完成瞭同步之後,就會進入命令傳播階段,這時 master 隻要一直將自己執行的寫命令發送給 slave ,而 slave 隻要一直接收並執行 master 發來的寫命令,就可以保證 master 和 slave 一直保持一致瞭。

以部分重同步為例,主從復制的核心步驟流程圖如下:

​3、哨兵

哨兵(Sentinel) 是 Redis 的高可用性解決方案:由一個或多個 Sentinel 實例組成的 Sentinel 系統可以監視任意多個主服務器,以及這些主服務器屬下的所有從服務器。

Sentinel 可以在被監視的主服務器進入下線狀態時,自動將下線主服務器的某個從服務器升級為新的主服務器,然後由新的主服務器代替已下線的主服務器繼續處理命令請求。

1)哨兵故障檢測

檢查主觀下線狀態

在默認情況下,Sentinel 會以每秒一次的頻率向所有與它創建瞭命令連接的實例(包括主服務器、從服務器、其他 Sentinel 在內)發送 PING 命令,並通過實例返回的 PING 命令回復來判斷實例是否在線。

如果一個實例在 down-after-miliseconds 毫秒內,連續向 Sentinel 返回無效回復,那麼 Sentinel 會修改這個實例所對應的實例結構,在結構的 flags 屬性中設置 SRI_S_DOWN 標識,以此來表示這個實例已經進入主觀下線狀態。

檢查客觀下線狀態

當 Sentinel 將一個主服務器判斷為主觀下線之後,為瞭確定這個主服務器是否真的下線瞭,它會向同樣監視這一服務器的其他 Sentinel 進行詢問,看它們是否也認為主服務器已經進入瞭下線狀態(可以是主觀下線或者客觀下線)。

當 Sentinel 從其他 Sentinel 那裡接收到足夠數量(quorum,可配置)的已下線判斷之後,Sentinel 就會將服務器置為客觀下線,在 flags 上打上 SRI_O_DOWN 標識,並對主服務器執行故障轉移操作。

2)哨兵故障轉移流程

當哨兵監測到某個主節點客觀下線之後,就會開始故障轉移流程。核心流程如下:

發起一次選舉,選舉出領頭 Sentinel領頭 Sentinel 在已下線主服務器的所有從服務器裡面,挑選出一個從服務器,並將其升級為新的主服務器。領頭 Sentinel 將剩餘的所有從服務器改為復制新的主服務器。領頭 Sentinel 更新相關配置信息,當這個舊的主服務器重新上線時,將其設置為新的主服務器的從服務器。

4、集群模式

哨兵模式最大的缺點就是所有的數據都放在一臺服務器上,無法較好的進行水平擴展。

為瞭解決哨兵模式存在的問題,集群模式應運而生。在高可用上,集群基本是直接復用的哨兵模式的邏輯,並且針對水平擴展進行瞭優化。

集群模式具備的特點如下:

  1. 采取去中心化的集群模式,將數據按槽存儲分佈在多個 Redis 節點上。集群共有 16384 個槽,每個節點負責處理部分槽。
  2. 使用 CRC16 算法來計算 key 所屬的槽:crc16(key,keylen) & 16383。
  3. 所有的 Redis 節點彼此互聯,通過 PING-PONG 機制來進行節點間的心跳檢測。
  4. 分片內采用一主多從保證高可用,並提供復制和故障恢復功能。在實際使用中,通常會將主從分佈在不同機房,避免機房出現故障導致整個分片出問題,下面的架構圖就是這樣設計的。
  5. 客戶端與 Redis 節點直連,不需要中間代理層(proxy)。客戶端不需要連接集群所有節點,連接集群中任何一個可用節點即可。

集群的架構圖如下所示:

​5、集群選舉

故障轉移的第一步就是選舉出新的主節點,以下是集群選舉新的主節點的方法:

1)當從節點發現自己正在復制的主節點進入已下線狀態時,會發起一次選舉:將 currentEpoch(配置紀元)加1,然後向集群廣播一條 CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST 消息,要求所有收到這條消息、並且具有投票權的主節點向這個從節點投票。

2)其他節點收到消息後,會判斷是否要給發送消息的節點投票,判斷流程如下:

  1. 當前節點是 slave,或者當前節點是 master,但是不負責處理槽,則當前節點沒有投票權,直接返回。
  2. 請求節點的 currentEpoch 小於當前節點的 currentEpoch,校驗失敗返回。因為發送者的狀態與當前集群狀態不一致,可能是長時間下線的節點剛剛上線,這種情況下,直接返回即可。
  3. 當前節點在該 currentEpoch 已經投過票,校驗失敗返回。
  4. 請求節點是 master,校驗失敗返回。
  5. 請求節點的 master 為空,校驗失敗返回。
  6. 請求節點的 master 沒有故障,並且不是手動故障轉移,校驗失敗返回。因為手動故障轉移是可以在 master 正常的情況下直接發起的。
  7. 上一次為該master的投票時間,在cluster_node_timeout的2倍范圍內,校驗失敗返回。這個用於使獲勝從節點有時間將其成為新主節點的消息通知給其他從節點,從而避免另一個從節點發起新一輪選舉又進行一次沒必要的故障轉移
  8. 請求節點宣稱要負責的槽位,是否比之前負責這些槽位的節點,具有相等或更大的 configEpoch,如果不是,校驗失敗返回。

如果通過以上所有校驗,那麼主節點將向要求投票的從節點返回一條 CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK 消息,表示這個主節點支持從節點成為新的主節點。

3)每個參與選舉的從節點都會接收 CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK 消息,並根據自己收到瞭多少條這種消息來統計自己獲得瞭多少個主節點的支持。

4)如果集群裡有N個具有投票權的主節點,那麼當一個從節點收集到大於等於N/2+1 張支持票時,這個從節點就會當選為新的主節點。因為在每一個配置紀元裡面,每個具有投票權的主節點隻能投一次票,所以如果有 N個主節點進行投票,那麼具有大於等於 N/2+1 張支持票的從節點隻會有一個,這確保瞭新的主節點隻會有一個。

5)如果在一個配置紀元裡面沒有從節點能收集到足夠多的支持票,那麼集群進入一個新的配置紀元,並再次進行選舉,直到選出新的主節點為止。

這個選舉新主節點的方法和選舉領頭 Sentinel 的方法非常相似,因為兩者都是基於 Raft 算法的領頭選舉(leader election)方法來實現的。

6、如何保證集群在線擴容的安全性?(Redis 集群要增加分片,槽的遷移怎麼保證無損)

例如:集群已經對外提供服務,原來有3分片,準備新增2個分片,怎麼在不下線的情況下,無損的從原有的3個分片指派若幹個槽給這2個分片?

Redis 使用瞭 ASK 錯誤來保證在線擴容的安全性。

在槽的遷移過程中若有客戶端訪問,依舊先訪問源節點,源節點會先在自己的數據庫裡面査找指定的鍵,如果找到的話,就直接執行客戶端發送的命令。

如果沒找到,說明該鍵可能已經被遷移到目標節點瞭,源節點將向客戶端返回一個 ASK 錯誤,該錯誤會指引客戶端轉向正在導入槽的目標節點,並再次發送之前想要執行的命令,從而獲取到結果。

ASK錯誤

在進行重新分片期間,源節點向目標節點遷移一個槽的過程中,可能會出現這樣一種情況:屬於被遷移槽的一部分鍵值對保存在源節點裡面,而另一部分鍵值對則保存在目標節點裡面。

當客戶端向源節點發送一個與數據庫鍵有關的命令,並且命令要處理的數據庫鍵恰好就屬於正在被遷移的槽時。源節點會先在自己的數據庫裡面査找指定的鍵,如果找到的話,就直接執行客戶端發送的命令。

否則,這個鍵有可能已經被遷移到瞭目標節點,源節點將向客戶端返回一個 ASK 錯誤,指引客戶端轉向正在導入槽的目標節點,並再次發送之前想要執行的命令,從而獲取到結果。

7、Redis 事務的實現

一個事務從開始到結束通常會經歷以下3個階段:

1)事務開始:multi 命令將執行該命令的客戶端從非事務狀態切換至事務狀態,底層通過 flags 屬性標識。

2)命令入隊:當客戶端處於事務狀態時,服務器會根據客戶端發來的命令執行不同的操作:

  • exec、discard、watch、multi 命令會被立即執行
  • 其他命令不會立即執行,而是將命令放入到一個事務隊列,然後向客戶端返回 QUEUED 回復。

3)事務執行:當一個處於事務狀態的客戶端向服務器發送 exec 命令時,服務器會遍歷事務隊列,執行隊列中的所有命令,最後將結果全部返回給客戶端。

不過 redis 的事務並不推薦在實際中使用,如果要使用事務,推薦使用 Lua 腳本,redis 會保證一個 Lua 腳本裡的所有命令的原子性。

8、Redis 的 Java 客戶端有哪些?官方推薦哪個?

Redis 官網展示的 Java 客戶端如下圖所示,其中官方推薦的是標星的3個:JedisRedisson lettuce

9、Redis 裡面有1億個 key,其中有 10 個 key 是包含 java,如何將它們全部找出來?

1)keys *java* 命令,該命令性能很好,但是在數據量特別大的時候會有性能問題

2)scan 0 MATCH *java* 命令,基於遊標的迭代器,更好的選擇

SCAN 命令是一個基於遊標的迭代器(cursor based iterator): SCAN 命令每次被調用之後, 都會向用戶返回一個新的遊標, 用戶在下次迭代時需要使用這個新遊標作為 SCAN 命令的遊標參數, 以此來延續之前的迭代過程。

當 SCAN 命令的遊標參數被設置為 0 時, 服務器將開始一次新的迭代, 而當服務器向用戶返回值為 0 的遊標時, 表示迭代已結束。

10、使用過 Redis 做消息隊列麼?

Redis 本身提供瞭一些組件來實現消息隊列的功能,但是多多少少都存在一些缺點,相比於市面上成熟的消息隊列,例如 Kafka、Rocket MQ 來說並沒有優勢,因此目前我們並沒有使用 Redis 來做消息隊列。

關於 Redis 做消息隊列的常見方案主要有以下:

1)Redis 5.0 之前可以使用 List(blocking)、Pub/Sub 等來實現輕量級的消息發佈訂閱功能組件,但是這兩種實現方式都有很明顯的缺點,兩者中相對完善的 Pub/Sub 的主要缺點就是消息無法持久化,如果出現網絡斷開、Redis 宕機等,消息就會被丟棄。

2)為瞭解決 Pub/Sub 模式等的缺點,Redis 在 5.0 引入瞭全新的 Stream,Stream 借鑒瞭很多 Kafka 的設計思想,有以下幾個特點:

  • 提供瞭消息的持久化和主備復制功能,可以讓任何客戶端訪問任何時刻的數據,並且能記住每一個客戶端的訪問位置,還能保證消息不丟失。
  • 引入瞭消費者組的概念,不同組接收到的數據完全一樣(前提是條件一樣),但是組內的消費者則是競爭關系。

Redis Stream 相比於 pub/sub 已經有很明顯的改善,但是相比於 Kafka,其實沒有優勢,同時存在:尚未經過大量驗證、成本較高、不支持分區(partition)、無法支持大規模數據等問題。

11、Redis 和 Memcached 的比較

1)數據結構:memcached 支持簡單的 key-value 數據結構,而 redis 支持豐富的數據結構:String、List、Set、Hash、SortedSet 等。

2)數據存儲:memcached 和 redis 的數據都是全部在內存中。

網上有一種說法 “當物理內存用完時,Redis可以將一些很久沒用到的 value 交換到磁盤,同時在內存中清除”,這邊指的是 redis 裡的虛擬內存(Virtual Memory)功能,該功能在 Redis 2.0 被引入,但是在 Redis 2.4 中被默認關閉,並標記為廢棄,而在後續版中被完全移除。

3)持久化:memcached 不支持持久化,redis 支持將數據持久化到磁盤

4)災難恢復:實例掛掉後,memcached 數據不可恢復,redis 可通過 RDB、AOF 恢復,但是還是會有數據丟失問題

5)事件庫:memcached 使用 Libevent 事件庫,redis 自己封裝瞭簡易事件庫 AeEvent

6)過期鍵刪除策略:memcached 使用惰性刪除,redis 使用惰性刪除+定期刪除

7)內存驅逐(淘汰)策略:memcached 主要為 LRU 算法,redis 當前支持8種淘汰策略,見本文第16題

8)性能比較

  • 按“CPU 單核” 維度比較:由於 Redis 隻使用單核,而 Memcached 可以使用多核,所以在比較上:在處理小數據時,平均每一個核上 Redis 比 Memcached 性能更高,而在 100k 左右的大數據時, Memcached 性能要高於 Redis。
  • 按“實例”維度進行比較:由於 Memcached 多線程的特性,在 Redis 6.0 之前,通常情況下 Memcached 性能是要高於 Redis 的,同時實例的 CPU 核數越多,Memcached 的性能優勢越大。
  • 至於網上說的 redis 的性能比 memcached 快很多,這個說法就離譜。

​總結

本篇文章就到這裡瞭,希望能給你帶來幫助,也希望您能夠多多關註WalkonNet的更多內容!

2021年最新Redis面試題匯總(1)

2021年最新Redis面試題匯總(2)

2021年最新Redis面試題匯總(4)

推薦閱讀: