Redis高頻面試題(來自字節跳動,騰訊,百度面試題總結)

原創文章首發於公衆號:「碼農富哥」,致力於分享後端技術 (高併發架構, 中間件, Linux, TCP/IP, HTTP, MySQL, Redis), 高性能,分佈式,微服務等原創乾貨面試指南

概述

什麼是Redis

Redis 是一個使用 C 語言寫成的,開源的 key-value 數據庫。和Memcached類似,它支持存儲的value類型相對更多,包括string(字符串)、list(鏈表)、set(集合)、zset(sorted set --有序集合)和hash(哈希類型)。這些數據類型都支持push/pop、add/remove及取交集並集和差集及更豐富的操作,而且這些操作都是原子性的。在此基礎上,redis支持各種不同方式的排序。與memcached一樣,爲了保證效率,數據都是緩存在內存中。區別的是redis會週期性的把更新的數據寫入磁盤或者把修改操作寫入追加的記錄文件,並且在此基礎上實現了master-slave(主從)同步。目前,Vmware在資助着redis項目的開發和維護。

Redis爲什麼這麼快

1、完全基於內存,絕大部分請求是純粹的內存操作,非常快速。數據存在內存中,類似於 HashMap,HashMap 的優勢就是查找和操作的時間複雜度都是O(1);

2、數據結構簡單,對數據操作也簡單,Redis 中的數據結構是專門進行設計的;

3、採用單線程,避免了不必要的上下文切換和競爭條件,也不存在多進程或者多線程導致的切換而消耗 CPU,不用去考慮各種鎖的問題,不存在加鎖釋放鎖操作,沒有因爲可能出現死鎖而導致的性能消耗;

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

5、使用底層模型不同,它們之間底層實現方式以及與客戶端之間通信的應用協議不一樣,Redis 直接自己構建了 VM 機制 ,因爲一般的系統調用系統函數的話,會浪費一定的時間去移動和請求;

Redis 數據類型

Redis有哪些數據類型?

Redis目前支持5種數據類型,分別是:
String(字符串):

  • 說明:String是簡單的 key-value 鍵值對,value 不僅可以是 String,也可以是數字。
  • 使用場景:String是最常用的一種數據類型,普通的key/value存儲都可以歸爲此類

List(列表):

  • 說明:Redis列表是簡單的字符串列表,簡單的說就是一個鏈表或者說是一個隊列。可以從頭部或尾部向Redis列表添加元素。Redis list的實現爲一個雙向鏈表,即可以支持反向查找和遍歷,更方便操作,不過帶來了部分額外的內存開銷,Redis內部的很多實現,包括髮送緩衝隊列等也都是用的這個數據結構。
  • 使用場景:比如twitter的關注列表、粉絲列表等都可以用Redis的list結構來實現,再比如有的應用使用Redis的list類型實現一個簡單的輕量級消息隊列,生產者push,消費者pop/bpop。

Hash(字典):

  • 說明:類似C#中的dict類型或者C++中的hash_map類型。
  • 使用場景:假設有多個用戶及對應的用戶信息,可以用來存儲以用戶ID爲key,將用戶信息序列化爲比如json格式做爲value進行保存。

Set(集合):

  • 說明:可以理解爲一堆值不重複的列表,類似數學領域中的集合概念,且Redis也提供了針對集合的求交集、並集、差集等操作。
    set 的內部實現是一個 value永遠爲null的HashMap,實際就是通過計算hash的方式來快速排重的,這也是set能提供判斷一個成員是否在集合內的原因。
  • 使用場景:Redis set對外提供的功能與list類似是一個列表的功能,特殊之處在於set是可以自動排重的,當你需要存儲一個列表數據,又不希望出現重複數據時,set是一個很好的選擇,並且set提供了判斷某個成員是否在一個set集合內的重要接口,這個也是list所不能提供的。
    又或者在微博應用中,每個用戶關注的人存在一個集合中,就很容易實現求兩個人的共同好友功能。

Sorted Set(有序集合):

  • 說明:Redis有序集合類似Redis集合,不同的是增加了一個功能,即集合是有序的。一個有序集合的每個成員帶有分數,用於進行排序。
  • 使用場景:Redis sorted set的使用場景與set類似,區別是set不是自動有序的,而sorted set可以通過用戶額外提供一個優先級(score)的參數來爲成員排序,並且是插入有序的,即自動排序。當你需要一個有序的並且不重複的集合列表,那麼可以選擇sorted set數據結構,比如twitter 的public timeline可以以發表時間作爲score來存儲,這樣獲取時就是自動按時間排好序的。
    又比如用戶的積分排行榜需求就可以通過有序集合實現。還有上面介紹的使用List實現輕量級的消息隊列,其實也可以通過Sorted Set實現有優先級或按權重的隊列。

Redis的zset實現原理及時間複雜度

數據量少的時候使用壓縮鏈表ziplist實現,有序集合使用緊挨在一起的壓縮列表節點來保存,第一個節點保存member,第二個保存score。ziplist內的集合元素按score從小到大排序,score較小的排在表頭位置。
數據量大的時候使用跳躍列表skiplist和哈希表hash_map結合實現,查找刪除插入的時間複雜度都是O(longN)

持久化原理

什麼是持久化

Redis 是一種內存數據庫,將數據保存在內存中,一旦進程退出,Redis 的數據就會丟失。
爲了解決這個問題,Redis 提供了 RDB 和 AOF 兩種持久化方案,將內存中的數據保存到磁盤中,避免數據丟失。

持久化方式RDB和AOF底層原理

Redis 提供了不同級別的持久化方式:RDB(默認方式)和AOF

RDB 持久化方式能夠在指定的時間間隔能對你的數據進行快照(snapshotting)存儲,將內存中的數據不斷寫入二進制文件中,默認文件dump.rdb,可配置Redis在n秒內如果超過m個key被修改就自動保存快照。(性能高,但是可能會出現數據丟失)

save 900 1 #900秒內如果超過1個key被修改,則發起快照保存。
save 300 10 #300秒內如果超過10個key被修改,則快照保存。

RDB持久化只會週期性的保存數據,在未觸發下一次存儲時服務宕機,就會丟失增量數據。當數據量較大的情況下,fork子進程這個操作很消耗cpu,可能會發生長達秒級別的阻塞情況。

SAVE是阻塞式持久化,執行命令時Redis主進程把內存數據寫入到RDB文件中直到創建完畢,期間Redis不能處理任何命令。

BGSAVE屬於非阻塞式持久化,創建一個子進程把內存中數據寫入RDB文件裏同時主進程處理命令請求。

如圖展示了RDB使用save 或者 bgsave 進行fork子進程進行持久化的流程:
在這裏插入圖片描述

AOF(Append-only file) 持久化方式記錄每次對服務器寫的操作,當服務器重啓的時候會重新執行這些命令來恢復原始的數據,AOF命令以redis協議追加保存每次寫的操作到文件末尾.Redis還能對AOF文件進行後臺重寫,使得AOF文件的體積不至於過大。(類似於MySql的日誌方式,記錄每次更新的日誌)(性能低,但是數據完整)

當開啓AOF後,服務端每執行一次寫操作就會把該條命令追加到一個單獨的AOF緩衝區的末尾,然後把AOF緩衝區的內容寫入AOF文件裏,由於磁盤緩衝區的存在寫入AOF文件之後,並不代表數據已經落盤了,而何時進行文件同步則是根據配置的appendfsync來進行配置:

appendfsync選項:always、everysec和no:
always:服務器在每執行一個事件就把AOF緩衝區的內容強制性的寫入硬盤上的AOF文件裏,保證了數據持久化的完整性,效率是最慢的但最安全的;

everysec:服務端每隔一秒纔會進行一次文件同步把內存緩衝區裏的AOF緩存數據真正寫入AOF文件裏,兼顧了效率和完整性,極端情況服務器宕機只會丟失一秒內對Redis數據庫的寫操作;

no:表示默認系統的緩存區寫入磁盤的機制,不做程序強制,數據安全性和完整性差一些。

RDB和AOF的優缺點和使用場景

RDB優點:

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

RDB缺點:

  • RDB的數據安全性是不如AOF的,保存整個數據集的過程是比繁重的,根據配置可能要幾分鐘才快照一次,如果服務器宕機,那麼就可能丟失幾分鐘的數據
  • Redis數據集較大時,fork的子進程要完成快照會比較耗CPU、耗時

AOF 優點:

  • 使用AOF 會讓你的Redis更加耐久: 你可以使用不同的fsync策略:無fsync,每秒fsync,每次寫的時候fsync.使用默認的每秒fsync策略,Redis的性能依然很好(fsync是由後臺線程進行處理的,主線程會盡力處理客戶端請求),一旦出現故障,你最多丟失1秒的數據.(由於os會在內核中緩存write做的修改,所以可能不是立即寫到磁盤上,這樣aof方式的持久化也還是有可能會丟失一部分數據。可以通過配置文件告訴redis我們想要通過fsync函數強制os寫入到磁盤的時機)

  • AOF文件是一個只進行追加的日誌文件,所以不需要寫入seek,即使由於某些原因(磁盤空間已滿,寫的過程中宕機等等)未執行完整的寫入命令,你也也可使用redis-check-aof工具修復這些問題.

  • Redis 可以在 AOF 文件體積變得過大時,通過命令bgrewriteaof自動地在後臺對 AOF 進行重寫: 重寫後的新 AOF 文件包含了恢復當前數據集所需的最小命令集合。 整個重寫操作是絕對安全的,因爲 Redis 在創建新 AOF 文件的過程中,會繼續將命令追加到現有的 AOF 文件裏面,即使重寫過程中發生停機,現有的 AOF 文件也不會丟失。 而一旦新 AOF 文件創建完畢,Redis 就會從舊 AOF 文件切換到新 AOF 文件,並開始對新 AOF 文件進行追加操作。
    redis AOF的日誌重寫流程:
    在這裏插入圖片描述

  • AOF 文件有序地保存了對數據庫執行的所有寫入操作, 這些寫入操作以 Redis 協議的格式保存, 因此 AOF 文件的內容非常容易被人讀懂, 對文件進行分析(parse)也很輕鬆。 導出(export) AOF 文件也非常簡單: 舉個例子, 如果你不小心執行了 FLUSHALL 命令, 但只要 AOF 文件未被重寫, 那麼只要停止服務器, 移除 AOF 文件末尾的 FLUSHALL 命令, 並重啓 Redis , 就可以將數據集恢復到 FLUSHALL 執行之前的狀態。

AOF 缺點:

  • 對於相同的數據集來說,AOF 文件的體積通常要大於 RDB 文件的體積。
  • 根據所使用的 fsync 策略,AOF 的速度可能會慢於 RDB 。 在一般情況下, 每秒 fsync 的性能依然非常高, 而關閉 fsync 可以讓 AOF 的速度和 RDB 一樣快, 即使在高負荷之下也是如此。 不過在處理巨大的寫入載入時,RDB 可以提供更有保證的最大延遲時間(latency)。

如何選擇RDB和AOF

  • 如果是數據不那麼敏感,且可以從其他地方重新生成補回的,那麼可以關閉持久化
  • 如果是數據比較重要,不想再從其他地方獲取,且可以承受數分鐘的數據丟失,比如緩存等,那麼可以只使用RDB
  • 如果是用做內存數據庫,要使用Redis的持久化,建議是RDB和AOF都開啓,或者定期執行bgsave做快照備份,RDB方式更適合做數據的備份,AOF可以保證數據的不丟失

RDB 和 AOF在數據恢復時的優先級?

數據恢復時 AOF 優先於 RDB, 因爲AOF的同步頻率相對高,可靠性高

事務

什麼是事務?

  • 事務是一個單獨的隔離操作:事務中的所有命令都會序列化、按順序地執行。事務在執行的過程中,不會被其他客戶端發送來的命令請求所打斷。

  • 事務是一個原子操作:事務中的命令要麼全部被執行,要麼全部都不執行。

Redis事務的概念

Redis 事務的本質是通過MULTI、EXEC、WATCH等一組命令的集合。事務支持一次執行多個命令,一個事務中所有命令都會被序列化。在事務執行過程,會按照順序串行化執行隊列中的命令,其他客戶端提交的命令請求不會插入到事務執行命令序列中。
總結說:redis事務就是一次性、順序性、排他性的執行一個隊列中的一系列命令。

Redis事務命令

命令 格式 作用 返回結果
WATCH WATCH key [key …] 將給出的Keys標記爲監測態,作爲事務執行的條件 always OK.
UNWATCH UNWATCH 清除事務中Keys的 監測態,如果調用了EXEC or DISCARD,則沒有必要再手動調用UNWATCH always OK.
MULTI MULTI 顯式開啓redis事務,後續commands將排隊,等候使用EXEC進行原子執行 always OK.
EXEC EXEC 執行事務中的commands隊列,恢復連接狀態。如果WATCH在之前被調用,只有監測中的Keys沒有被修改,命令纔會被執行,否則停止執行 成功: 返回數組 —— 每個元素對應着原子事務中一個 command的返回結果;失敗: 返回NULL;
DISCARD DISCARD 清除事務中的commands隊列,恢復連接狀態。如果WATCH在之前被調用,釋放 監測中的Keys always OK.

Redis事務使用方法

Redis事務功能是通過MULTI、EXEC、DISCARD和WATCH 四個原語實現的

Redis會將一個事務中的所有命令序列化,然後按順序執行。

  • 使用MULTI命令進入一個Redis事務,這個命令的返回值總是OK。
  • 用戶可以發出多個Redis命令。Redis會將這些命令放入隊列,而不是直接執行這些命令
  • 如果調用EXEC命令,那麼Redis就會按順序執行事務中的所有命令。
  • 如果調用DISCARD命令將會清除事務隊列,然後退出事務。

以下示例會原子化地遞增foo鍵和bar鍵的值:
http://ghoulich.xninja.org/wp-content/uploads/sites/2/2016/10/image-01_transaction-example.png

Redis事務中出錯會怎樣?

事務期間,可能會遇到幾種命令錯誤:

  • 命令可能存在語法錯誤,進入隊列的命令有誤,比如參數數量錯誤,錯誤的命令名稱
  • 執行EXEC運行時候時出錯,比如給一個list類型的變量 執行incr + 1,這樣的命令語法上沒問題,只有在運行的時候才能發行

對於第一種錯誤,客戶端會在EXEC調用之前檢測, 通過檢查排隊命令的狀態回覆,如果命令使用QUEUED進行響應,則它已正確排隊;否則Redis將返回錯誤。

對於第二種錯誤,服務端會記住在累積命令期間發生的錯誤,當EXEC命令調用時,將拒絕執行事務,並返回這些錯誤,同時自動清除命令隊列。即使事務中的某些命令執行失敗,其他命令仍會被正常執行。(包括出錯命令之後的命令)

爲什麼Redis事務不支持回滾?

事實上Redis命令在事務執行時可能會失敗,但仍會繼續執行剩餘命令而不是Rollback(事務回滾)。如果你使用過關係數據庫,這種情況可能會讓你感到很奇怪。然而針對這種情況具備很好的解釋:

  • Redis命令可能會執行失敗,僅僅是由於錯誤的語法被調用(命令排隊時檢測不出來的錯誤),或者使用錯誤的數據類型操作某個Key: 這意味着,實際上失敗的命令都是編程錯誤造成的,都是開發中能夠被檢測出來的,生產環境中不應該存在。(這番話,徹底甩鍋,“都是你們自己編程錯誤,與我們無關”。)
  • 由於不必支持Rollback,Redis內部簡潔並且更加高效。

“如果錯誤就是發生了呢?”這是一個反對Redis觀點的爭論。然而應該指出的是,通常情況下,回滾並不能挽救編程錯誤。鑑於沒有人能夠挽救程序員的錯誤,並且Redis命令失敗所需的錯誤類型不太可能進入生產環境,所以我們選擇了不支持錯誤回滾(Rollback)這種更簡單快捷的方法。

Redis事務支持隔離性嗎

Redis 是單進程程序,並且它保證在執行事務時,不會對事務進行中斷,事務可以運行直到執行完所有事務隊列中的命令爲止。因此,Redis 的事務是總是帶有隔離性的。

Redis事務其他實現

基於Lua腳本,Redis可以保證腳本內的命令一次性、按順序地執行,
其同時也不提供事務運行錯誤的回滾,執行過程中如果部分命令運行錯誤,剩下的命令還是會繼續運行完
基於中間標記變量,通過另外的標記變量來標識事務是否執行完成,讀取數據時先讀取該標記變量判斷是否事務執行完成。但這樣會需要額外寫代碼實現,比較繁瑣

樂觀鎖與悲觀鎖的區別?

Redis中的管道有什麼用

一次請求/響應服務器能實現處理新的請求即使舊的請求還未被響應。這樣就可以將多個命令發送到服務器,而不用等待回覆,最後在一個步驟中讀取該答覆。這就是管道(pipelining),是一種幾十年來廣泛使用的技術。例如許多POP3協議已經實現支持這個功能,大大加快了從服務器下載新郵件的過程。

緩存雪崩,緩存擊穿,緩存穿透現象及解決方案

緩存雪崩:

  • 現象:影響輕則,查詢變慢,重則當請求併發更高時,出來大面積服務不可用。
  • 原因: 同一時間緩存大面積失效,就像沒有緩存一樣,所有的請求直接打到數據庫上來,DB扛不住掛了,如果是重要的庫,例如用戶庫,那牽聯就一大片了,瞬間倒一片。
  • 案例:電商首頁緩存,如果首頁的key全部都在某一時刻失效,剛好在那一時刻有秒殺活動,那這樣的話就所有的請求都被打到了DB。併發大的情況下DB必然扛不住,導致服務不可用。
  • 解決方案:批量往redis存數據的時候,把每個key的失效時間加上個隨機數,這樣的話就能保證數據不會在同一個時間大面積失效。

緩存穿透:

  • 現象與原因: 指用戶不斷髮起請求的數據,在緩存和DB中都沒有,比如DB中的用戶ID是自增的,但是用戶請求傳了-1,或者是一個特別大的數字,這個時候用戶很有可能就是一個攻擊者,這樣的功擊會導致DB的壓力過大,嚴重的話就是把DB搞掛了。因爲每次都繞開了緩存直接查詢DB

  • 解決方案:

    • 方法一:在接口層增加校驗,不合法的參數直接返回。不相信任務調用方,根據自己提供的API接口規範來,作爲被調用方,要考慮可能任何的參數傳值。
    • 方法二:在緩存查不到,DB中也沒有的情況,可以將對應的key的value寫爲null,或者其他特殊值寫入緩存,同時將過期失效時間設置短一點,以免影響正常情況。這樣是可以防止反覆用同一個ID來暴力攻擊。
    • 方法三:正常用戶是不會這樣暴力功擊,只有是惡意者纔會這樣做,可以在網關NG作一個配置項,爲每一個IP設置訪問閥值。
    • 方法四:高級用戶布隆過濾器(Bloom Filter),這個也能很好地防止緩存穿透。原理就是利用高效的數據結構和算法快速判斷出你這個Key是否在DB中存在,不存在你return就好了,存在你就去查了DB刷新KV再return。

緩存擊穿:

  • 現象與原因:跟緩存雪崩類似,但是又有點不一樣。雪崩是因爲大面積緩存失效,請求全打到DB;而緩存擊穿是指一個key是熱點,不停地扛住大併發請求,全都集中訪問此key,而當此key過期瞬間,持續的大併發就擊穿緩存,全都打在DB上。就又引發雪崩的問題。
  • 解決方案:
    • 方法一:設置熱點數據永遠不過期。
    • 方法二:加互斥鎖,互斥鎖

緩存高可用方案

這就是三者的區別,差不多,但又有一些區別。因爲緩存雪崩、穿透和擊穿,是緩存最大的問題,要麼不出現,一旦出現就是致命性的問題
一般避免以上情況發生我們從三個時間段去分析下:

  • 事前:Redis 高可用,主從+哨兵,Redis cluster,避免全盤崩潰。
  • 事中:本地 ehcache 緩存 + Hystrix 限流+降級,避免MySQL 被打死。
  • 事後:Redis 持久化 RDB+AOF,一旦重啓,自動從磁盤上加載數據,快速恢復緩存數據。

Redis的過期策略和內存淘汰策略

Redis key過期刪除策略

使用過Redis的同學應該知道,我們在設置一個key之後,可以指定這個key的過期時間。那麼這個key到了過期時間就會立即被刪除嗎?Redis是如何刪除這些過期key的呢?
Redis是使用定期刪除 + 惰性刪除 兩者配合的過期策略。

  • 定期刪除
    定期刪除指的是Redis默認每隔100ms就隨機抽取一些設置了過期時間的key,檢測這些key是否過期,如果過期了就將其刪掉。過期掃描不會遍歷過期字典中所有的 key,而是採用了一種簡單的貪心策略。

    • 從過期字典中隨機 20 個 key;
    • 刪除這 20 個 key 中已經過期的 key;
    • 如果過期的 key 比率超過 1/4,那就重複步驟 1;

    同時,爲了保證過期掃描不會出現循環過度,導致線程卡死現象,算法還增加了掃描時間的上限,默認不會超過 25ms。
    因爲是隨機抽取一些key來刪除。這樣就有可能刪除不完,需要惰性刪除配合。

  • 惰性刪除
    惰性刪除不再是Redis去主動刪除,而是在客戶端要獲取某個key的時候,Redis會先去檢測一下這個key是否已經過期,如果沒有過期則返回給客戶端,如果已經過期了,那麼Redis會刪除這個key,不會返回給客戶端。

    所以惰性刪除可以解決一些過期了,但沒被定期刪除隨機抽取到的key。但有些過期的key既沒有被隨機抽取,也沒有被客戶端訪問,就會一直保留在數據庫,佔用內存,長期下去可能會導致內存耗盡。
    所以Redis提供了內存淘汰機制來解決這個問題。

內存淘汰策略

Redis在使用內存達到某個閾值(通過maxmemory配置)的時候,就會觸發內存淘汰機制,選取一些key來刪除。內存淘汰有許多策略,下面分別介紹這幾種不同的策略。

  • noeviction:當內存不足以容納新寫入數據時,新寫入操作會報錯。默認策略

  • allkeys-lru:當內存不足以容納新寫入數據時,在鍵空間中,移除最近最少使用的key。(這個是最常用的)

  • allkeys-random:當內存不足以容納新寫入數據時,在鍵空間中,隨機移除某個key。

  • volatile-lru:當內存不足以容納新寫入數據時,在設置了過期時間的鍵空間中,移除最近最少使用的key。

  • volatile-random:當內存不足以容納新寫入數據時,在設置了過期時間的鍵空間中,隨機移除某個key。

  • volatile-ttl:當內存不足以容納新寫入數據時,在設置了過期時間的鍵空間中,有更早過期時間的key優先移除。

如何選取合適的策略?比較推薦的是兩種lru策略。根據自己的業務需求。如果你使用Redis只是作爲緩存,不作爲DB持久化,那推薦選擇allkeys-lru;如果你使用Redis同時用於緩存和數據持久化,那推薦選擇volatile-lru。

緩存淘汰算法: LRU 和 LFU的區別

LRU是最近最少使用頁面置換算法(Least Recently Used),也就是首先淘汰最長時間未被使用的頁面!

LFU是最近最不常用頁面置換算法(Least Frequently Used),也就是淘汰一定時期內被訪問次數最少的頁!

線程模型

Redis集羣方案

Redis主從複製是什麼及作用?

  • redis的主從複製功能是支持多個數據庫之間的數據同步。一類是主數據庫(master)一類是從數據庫(slave),主數據庫可以進行讀寫操作,當發生寫操作的時候自動將數據同步到從數據庫,而從數據庫一般是隻讀的,並接收主數據庫同步過來的數據,一個主數據庫可以有多個從數據庫,而一個從數據庫只能有一個主數據庫。

  • 通過redis的複製功能可以很好的實現數據庫的讀寫分離,提高服務器的負載能力。主數據庫主要進行寫操作,而從數據庫負責讀操作。

主從複製的架構圖:
在這裏插入圖片描述

主從複製的作用:

  • 數據冗餘,實現數據的熱備份
  • 故障恢復,避免單點故障帶來的服務不可用
  • 讀寫分離,負載均衡。主節點負載讀寫,從節點負責讀,提高服務器併發量
  • 高可用基礎,是哨兵機制和集羣實現的基礎

Redis主從複製過程及原理

主從複製過程:

  • 1:當一個從數據庫啓動時,會向主數據庫發送sync命令,
  • 2:主數據庫接收到sync命令後會開始在後臺保存快照(執行rdb操作),並將保存期間接收到的命令緩存起來
  • 3:當快照完成後,redis會將快照文件和所有緩存的命令發送給從數據庫。
  • 4:從數據庫收到後,會載入快照文件並執行收到的緩存的命令。

主從複製過程如圖所示:
在這裏插入圖片描述

Redis Sentinel 哨兵機制

Redis Sentinel 是 Redis 高可用的實現方案,它是一個管理多個 Redis 實例的工具。
Redis Sentinel 的主要功能包括 主節點存活檢測、主從運行情況檢測、自動故障轉移 (failover)、主從切換。Redis 的 Sentinel 最小配置是 一主一從。
Redis 的 Sentinel 系統可以用來管理多個 Redis 服務器,該系統可以執行以下四個任務:

  • 監控: Sentinel 會不斷的檢查 主服務器 和 從服務器 是否正常運行。
  • 通知:當被監控的某個 Redis 服務器出現問題,Sentinel 通過 API 腳本 向 管理員 或者其他的 應用程序 發送通知。
  • 自動故障轉移: 當主節點不能正常工作時,Sentinel 會開始一次 自動的故障轉移操作,它會將與 失效主節點 是 主從關係 的其中一個 從節點 升級爲新的 主節點,並且將其他的 從節點 指向 新的主節點。
  • 配置提供者:在 Redis Sentinel 模式下,客戶端應用 在初始化時連接的是 Sentinel 節點集合,從中獲取 主節點 的信息。

如圖所示,Redis Sentinel 高可用架構 的示意圖:
在這裏插入圖片描述

Redis 哨兵機制如何實現故障自動轉移?

sentinel 集羣通過主觀下線和客觀下線判斷redis節點是否失效
默認情況下,每個 Sentinel 節點會以每秒一次的頻率對Redis 節點和其它的Sentinel 節點發送 PING 命令,並通過節點的 回覆 來判斷節點是否在線。

  • 主觀下線
    主觀下線 適用於所有 主節點 和 從節點。如果在 down-after-milliseconds 毫秒內,Sentinel 沒有收到 目標節點 的有效回覆,則會判定 該節點 爲 主觀下線。
  • 客觀下線
    客觀下線 只適用於 主節點。如果 主節點 出現故障,Sentinel 節點會通過 sentinel is-master-down-by-addr 命令,向其它 Sentinel 節點詢問對該節點的 狀態判斷。如果超過 個數的節點判定 主節點 不可達,則該 Sentinel 節點會判斷 主節點爲客觀下線。

當判斷某個Redis節點是客觀下線後,Sentinel會把master轉移到另外的slave節點,讓它充當新的master接受請求,從而保證高可用性。

Redis集羣的開源方案有哪些?

Twemproxy
Twemproxy 是 twitter 開源的一個 redis 和 memcache 的 中間代理服務器 程序。Twemproxy 作爲 代理,可接受來自多個程序的訪問,按照 路由規則,轉發給後臺的各個 Redis 服務器,再原路返回。Twemproxy 存在 單點故障 問題,需要結合 Lvs 和 Keepalived 做 高可用方案。
在這裏插入圖片描述

  • 優點:應用範圍廣,穩定性較高,中間代理層 高可用。
  • 缺點:無法平滑地 水平擴容/縮容,無 可視化管理界面,運維不友好,出現故障,不能 自動轉移。

Codis
Codis 是一個 分佈式 Redis 解決方案,對於上層應用來說,連接 Codis-Proxy 和直接連接 原生的 Redis-Server 沒有的區別。Codis 底層會 處理請求的轉發,不停機的進行 數據遷移 等工作。Codis 採用了無狀態的 代理層,對於 客戶端 來說,一切都是透明的。
在這裏插入圖片描述

  • 優點:實現了上層 Proxy 和底層 Redis 的 高可用,數據分片 和 自動平衡,提供 命令行接口 和 RESTful API,提供 監控 和 管理 界面,可以動態 添加 和 刪除 Redis 節點。
  • 缺點: 部署架構 和 配置 複雜,不支持 跨機房 和 多租戶,不支持 鑑權管理。

Redis Cluster 集羣架構

Redis Cluster 實現了一種 混合形式 的 查詢路由,但並不是 直接 將請求從一個 Redis 節點 轉發 到另一個 Redis 節點,而是在 客戶端 的幫助下直接 重定向( redirected)到正確的 Redis 節點。
在這裏插入圖片描述

  • 優點:無中心節點,數據按照 槽 存儲分佈在多個 Redis 實例上,可以平滑的進行節點 擴容/縮容,支持 高可用 和 自動故障轉移,運維成本低。

  • 缺點: 嚴重依賴 Redis-trib 工具,缺乏 監控管理,需要依賴 Smart Client (維護連接,緩存路由表,MultiOp 和 Pipeline 支持)。Failover 節點的 檢測過慢,不如 中心節點 ZooKeeper 及時。Gossip 消息具有一定開銷。無法根據統計區分 冷熱數據。

數據分區有哪些算法?

分佈式數據庫 首先要解決把 整個數據集 按照 分區規則 映射到 多個節點 的問題,即把 數據集 劃分到 多個節點 上,每個節點負責 整體數據 的一個 子集。
在這裏插入圖片描述
數據分佈通常有 哈希分區 和 順序分區 兩種方式,對比如下:

分區方式 特點 相關產品
哈希分區 離散程度好,數據分佈與業務無關,無法順序訪問 Redis Cluster,Cassandra,Dynamo
順序分區 離散程度易傾斜,數據分佈與業務相關,可以順序訪問 BigTable,HBase,Hypertable

節點取餘分區
使用特定的數據,如 Redis 的 鍵 或 用戶 ID,再根據 節點數量 N 使用公式:hash(key)% N 計算出 哈希值,用來決定數據 映射 到哪一個節點上。
在這裏插入圖片描述

  • 優點: 這種方式的突出優點是 簡單性,常用於 數據庫 的 分庫分表規則。一般採用 預分區 的方式,提前根據 數據量 規劃好 分區數,比如劃分爲 512 或 1024 張表,保證可支撐未來一段時間的 數據容量,再根據 負載情況 將 表 遷移到其他 數據庫 中。擴容時通常採用 翻倍擴容,避免 數據映射 全部被 打亂,導致 全量遷移 的情況。

  • 缺點: 當節點數量變化時,如 擴容 或 收縮 節點,數據節點 映射關係 需要重新計算,會導致數據的 重新遷移。

一致性哈希分區
一致性哈希 可以很好的解決 穩定性問題,可以將所有的 存儲節點 排列在 收尾相接 的 Hash 環上,每個 key 在計算 Hash 後會 順時針 找到 臨接 的 存儲節點 存放。而當有節點 加入 或 退出 時,僅影響該節點在 Hash 環上 順時針相鄰 的 後續節點。
在這裏插入圖片描述

  • 優點:加入和刪除 節點隻影響 哈希環 中 順時針方向 的 相鄰的節點,對其他節點無影響。

  • 缺點: 加減節點 會造成 哈希環 中部分數據 無法命中。當使用 少量節點 時,節點變化 將大範圍影響 哈希環 中 數據映射,不適合 少量數據節點 的分佈式方案。普通 的 一致性哈希分區 在增減節點時需要 增加一倍 或 減去一半 節點才能保證 數據 和 負載的均衡。

虛擬槽分區
虛擬槽分區 巧妙地使用了 哈希空間,使用 分散度良好 的 哈希函數 把所有數據 映射 到一個 固定範圍 的 整數集合 中,整數定義爲 槽(slot)。這個範圍一般 遠遠大於 節點數,比如 Redis Cluster 槽範圍是 0 ~ 16383。槽 是集羣內 數據管理 和 遷移 的 基本單位。採用 大範圍槽 的主要目的是爲了方便 數據拆分 和 集羣擴展。每個節點會負責 一定數量的槽,如圖所示:
在這裏插入圖片描述

當前集羣有 5 個節點,每個節點平均大約負責 3276 個 槽。由於採用 高質量 的 哈希算法,每個槽所映射的數據通常比較 均勻,將數據平均劃分到 5 個節點進行 數據分區。Redis Cluster 就是採用 虛擬槽分區。

節點1: 包含 0 到 3276 號哈希槽。
節點2:包含 3277 到 6553 號哈希槽。
節點3:包含 6554 到 9830 號哈希槽。
節點4:包含 9831 到 13107 號哈希槽。
節點5:包含 13108 到 16383 號哈希槽。

這種結構很容易 添加 或者 刪除 節點。如果 增加 一個節點 6,就需要從節點 1 ~ 5 獲得部分 槽 分配到節點 6 上。如果想 移除 節點 1,需要將節點 1 中的 槽 移到節點 2 ~ 5 上,然後將 沒有任何槽 的節點 1 從集羣中 移除 即可。

由於從一個節點將 哈希槽 移動到另一個節點並不會 停止服務,所以無論 添加刪除 或者 改變 某個節點的 哈希槽的數量 都不會造成 集羣不可用 的狀態.

說說 Redis Cluster 虛擬槽分區?

Redis Cluster 採用 虛擬槽分區,所有的 鍵 根據 哈希函數 映射到 0~16383 整數槽內,計算公式:slot = CRC16(key)& 16383。每個節點負責維護一部分槽以及槽所映射的 鍵值數據,如圖所示:

在這裏插入圖片描述
Redis虛擬槽分區的特點:

  • 解耦 數據 和 節點 之間的關係,簡化了節點 擴容 和 收縮 難度。
  • 節點自身 維護槽的 映射關係,不需要 客戶端 或者 代理服務 維護 槽分區元數據。
  • 支持 節點、槽、鍵 之間的 映射查詢,用於 數據路由、在線伸縮 等場景。

三主三從的集羣使用多少臺機器部署比較好?

土豪型: 使用6臺機器,每臺部署一個Redis節點
經濟型:使用3臺機器,每臺機器部署2個Redis節點,主從混布,即同一組主從節點,分佈在不同節點,從而保證高可用!

Redis性能優化

Redis常見性能問題和解決方案:

  • Master最好不要做任何持久化工作,如RDB內存快照和AOF日誌文件
  • 如果數據比較重要,某個Slave開啓AOF備份數據,策略設置爲每秒同步一次
  • 爲了主從複製的速度和連接的穩定性,Master和Slave最好在同一個局域網內
    儘量避免在壓力很大的主庫上增加從庫

總結

原創文章首發於公衆號:「碼農富哥」,致力於分享後端技術 (高併發架構, 中間件, Linux, TCP/IP, HTTP, MySQL, Redis), 高性能,分佈式,微服務等原創乾貨面試指南
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章