透過面試題掌握Redis【持續更新中】

最近是把《Redis設計與實現》看完了,而後也對每一章寫了讀書筆記。可是總感受本身掌握得不夠,因此就在網上搜集了一些Redis相關的面試題,而後本身去翻筆記,看書,查資料嘗試着去解答,目前這篇文章尚未徹底覆蓋到全部Redis相關的面試題,以後會持續更新,一方面是我本身學習記錄,一方面是分享給你們,與你們交流。也建了一個交流羣,入羣二維碼在下面,若是二維碼失效了,你們也能夠加個人微信ruiwendelll,我拉你們進羣,但願能夠和你們一塊兒學習進步! 面試

面試題列表:

1.Redis是什麼?

2.Redis的持久化是怎麼實現的?

3.AOF和RDB的區別是什麼?

4.怎麼防止AOF文件愈來愈大?

5.Redis持久化策略該如何進行選擇?

6.AOF文件追加阻塞是什麼?

7.Redis過時key是怎麼樣清理的?

8.Redis爲何是單線程的?

9.Redis性能爲何高?

10.Linux I/O模型一共有哪些?

11.同步與異步的區別是什麼?

12.阻塞與非阻塞的區別是什麼?

13.如何解決Redis緩存穿透問題?

14.布隆過濾器是什麼?

15.如何解決Redis緩存雪崩問題?

16.Redis常見的數據結構有哪些?

17.談一談你對Redis中簡單動態字符串的理解?

18.談一談你對Redis中hash對象的理解?

19.談一談你對Redis中List的理解?

20.談一談你對Redis中Set的理解?

21.談一談你對Redis中ZSet(有序集合)的理解?

22.Redis主從同步是怎麼實現的?

23.Redis中哨兵是什麼?

24.客戶端是怎麼接入哨兵系統的?

25.Redis哨兵系統是怎麼實現自動故障轉移的?

下面是本身查閱書籍,資料,翻看讀書筆記後,嘗試寫的回答,有不對的地方歡迎指正,你們一塊兒學習進步。算法

Redis是什麼?

Redis是一個開源的,基於內存的,也可進行持久化的,基於C語言編寫的鍵值對存儲數據庫。數據庫

Redis的持久化是怎麼實現的?

Redis主要經過AOF和RDB實現持久化。數組

AOF持久化主要是Redis在修改相關的命令後,將命令添加到aof_buf緩存區的末尾,而後在每次事件循環結束時,根據appendfsync的配置(always是老是寫入,everysec是每秒寫入,no是根據操做系統來決定什麼時候寫入),判斷是否須要將aof_buf寫入AOF文件。緩存

RDB持久化指的是在知足必定的觸發條件時(在一個的時間間隔內執行修改命令達到必定的數量,或者手動執行SAVE和BGSAVE命令),對這個時間點的數據庫全部鍵值對信息生成一個壓縮文件dump.rdb,而後將舊的刪除,進行替換。安全

實現原理是fork一個子進程,而後對鍵值對進行遍歷,生成rdb文件,在生成過程當中,父進程會繼續處理客戶端發送的請求,當父進程要對數據進行修改時,會對相關的內存頁進行拷貝,修改的是拷貝後的數據。(也就是COPY ON WRITE,寫時複製技術,就是當多個調用者同時請求同一個資源,如內存或磁盤上的數據存儲,他們會共用同一個指向資源的指針,指向相同的資源,只有當一個調用者試圖修改資源的內容時,系統纔會真正複製一份專用副本給這個調用者,其餘調用者仍是使用最初的資源,在ArrayList的實現中,也有用到,添加一個新元素時過程是,加鎖,對原數組進行復制,而後添加新元素,而後替代舊數組,解鎖)bash

AOF和RDB的區別是什麼?

AOF由於是保存了全部執行的修改命令,粒度更細,進行數據恢復時,恢復的數據更加完整,可是因爲須要對全部命令執行一遍,效率比較低,一樣由於是保存了全部的修改命令,一樣的數據集,保存的文件會比RDB大,並且隨着執行時間的增長,AOF文件可能會愈來愈大,全部會經過執行BGREWRITEAOF命令來從新生成AOF文件,減少文件大小。Redis服務器故障重啓後,默認恢復數據的方式首選是經過AOF文件恢復,其次是經過RDB文件恢復。 RDB是保存某一個時間點的全部鍵值對信息,因此恢復時可能會丟失一部分數據,可是恢復效率會比較高服務器

怎麼防止AOF文件愈來愈大?

爲了防止AOF文件愈來愈大,能夠經過執行BGREWRITEAOF命令,會fork子進程出來,讀取當前數據庫的鍵值對信息,生成所需的寫命令,寫入新的AOF文件。在生成期間,父進程繼續正常處理請求,執行修改命令後,不只會將命令寫入aof_buf緩衝區,還會寫入重寫aof_buf緩衝區。當新的AOF文件生成完畢後,子進程父進程發送信號,父進程將重寫aof_buf緩衝區的修改命令寫入新的AOF文件,寫入完畢後,對新的AOF文件進行更名,原子地(atomic)地替換舊的AOF文件。微信

Redis持久化策略該如何進行選擇?

RDB持久化的特色是:文件小,恢復快,不影響性能,實時性低,兼容性差(老版本的Redis不兼容新版本的RDB文件) AOF持久化的特色是:文件大,恢復慢,性能影響大,實時性高。是目前持久化的主流(主要是當前項目開發不太能接受大量數據丟失的狀況)。 須要瞭解的是持久化選項的開啓必然會形成必定的性能消耗 RDB持久化主要在於bgsave在進行fork操做時,會阻塞Redis的主線程。以及向硬盤寫數據會有必定的I/O壓力。 AOF持久化主要在於將aof_buf緩衝區的數據同步到磁盤時會有I/O壓力,並且向硬盤寫數據的頻率會高不少。其次是,AOF文件重寫跟RDB持久化相似,也會有fork時的阻塞和向硬盤寫數據的壓力。網絡

如下是幾種持久化方案選擇的場景:

1.不須要考慮數據丟失的狀況,那麼不須要考慮持久化。

2.單機實例狀況下,能夠接受丟失十幾分鍾及更長時間的數據,能夠選擇RDB持久化,對性能影響小,若是隻能接受秒級的數據丟失,只能選擇AOF持久化。

3.在主從環境下,由於主服務器在執行修改命令後,會將命令發送給從服務器,從服務進行執行後,與主服務器保持數據同步,實現數據熱備份,在master宕掉後繼續提供服務。同時也能夠進行讀寫分離,分擔Redis的讀請求。

那麼在從服務器進行數據熱備份的狀況下,是否還須要持久化呢? 須要持久化,由於不進行持久化,主服務器,從服務器同時出現故障時,會致使數據丟失。(例如:機房所有機器斷電)。若是系統中有自動拉起機制(即檢測到服務中止後重啓該服務)將master自動重啓,因爲沒有持久化文件,那麼master重啓後數據是空的,slave同步數據也變成了空的。應儘可能避免「自動拉起機制」和「不作持久化」同時出現。

因此通常能夠採用如下方案:

主服務器不開啓持久化,使得主服務器性能更好。

從服務器開啓AOF持久化,關閉RDB持久化,而且定時對AOF文件進行備份,以及在凌晨執行bgaofrewrite命令來進行AOF文件重寫,減少AOF文件大小。(固然若是對數據丟失容忍度高也能夠開啓RDB持久化,關閉AOF持久化)

4.異地災備,通常性的故障(停電,關機)不會影響到磁盤,可是一些災難性的故障(地震,洪水)會影響到磁盤,因此須要定時把單機上或從服務器上的AOF文件,RDB文件備份到其餘地區的機房。

AOF文件追加阻塞是什麼

修改命令添加到aof_buf以後,若是配置是everysec那麼會每秒執行fsync操做,調用write寫入磁盤一次,可是若是硬盤負載太高,fsync操做可能會超過1s,Redis主線程持續高速向aof_buf寫入命令,硬盤的負載可能會愈來愈大,IO資源消耗更快,因此Redis的處理邏輯是會對比上次fsync成功的時間,若是超過2s,則主線程阻塞直到fsync同步完成,因此最多可能丟失2s的數據,而不是1s。

Redis過時key是怎麼樣清理的?

(1)惰性清除。在訪問key時,若是發現key已通過期,那麼會將key刪除。

(2)定時清理。Redis配置項hz定義了serverCron任務的執行週期,默認每次清理時間爲25ms,每次清理會依次遍歷全部DB,從db隨機取出20個key,若是過時就刪除,若是其中有5個key過時,那麼就繼續對這個db進行清理,不然開始清理下一個db。

(3)當執行寫入命令時,若是發現內存不夠,那麼就會按照配置的淘汰策略清理內存,淘汰策略主要由如下幾種:

1.noeviction,不刪除,達到內存限制時,執行寫入命令時直接返回錯誤信息。

2.allkeys-lru,在全部key中,優先刪除最少使用的key。(適合請求符合冪定律分佈,也就是一些鍵訪問頻繁,一部分鍵訪問較少)

3.allkeys-random,在全部key中,隨機刪除一部分key。(適合請求分佈比較平均)

4.volatile-lru,在設置了expire的key中,優先刪除最少使用的key。

5.volatile-random,在設置了expire的key中,隨機刪除一部分key。

6.volatile-ttl,在設置了expire的key中,優先刪除剩餘時間段的key。

4.0版本後增長如下兩種:

volatile-lfu:從已設置過時時間的數據集(server.db[i].expires)中挑選最不常用的數據淘汰。

allkeys-lfu:當內存不足以容納新寫入數據時,在鍵空間中,移除最不常用的key。

LRU算法

LRU算法的設計原則是若是一個數據近期沒有被訪問到,那麼以後一段時間都不會被訪問到。因此當元素個數達到限制的值時,優先移除距離上次使用時間最久的元素。 可使用HashMap<String, Node>+雙向鏈表Node來實現,每次訪問元素後,將元素移動到鏈表尾部,當元素滿了時,將鏈表頭部的元素移除。 使用單向鏈表能不能實現呢,也能夠,單向鏈表的節點雖然獲取不到pre節點的信息,可是能夠將下一個節點的key和value設置在當前節點上,而後把當前節點的next指針指向下下個節點。

LFU算法

LFU算法的設計原則時,若是一個數據在最近一段時間被訪問的時次數越多,那麼以後被訪問的機率會越大,實現是每一個數據 都有一個引用計數,每次數據被訪問後,引用計數加1,須要淘汰數據時,淘汰引用計數最小的數據。在Redis的實現中, 每次key被訪問後,引用計數是加一個介於0到1之間的數p,而且訪問越頻繁p值越大,並且在必定的時間間隔內, 若是key沒有被訪問,引用計數會減小。

Redis爲何是單線程的?

Redis官方FAQ回答: Redis是基於內存的操做,CPU不會成爲瓶頸所在,Redis的瓶頸最有多是機器內存的大小或者網絡帶寬。既然單線程容易實現,並且CPU不會成爲瓶頸,那就瓜熟蒂落地採用單線程的方案了。 (這裏的單線程指的是處理網絡請求的模塊是單線程,其餘模塊不必定是單線程的)

Redis採用單線程的優點:

1.Redis項目的代碼會更加清晰,處理邏輯會更加簡單。

2.不用考慮多個線程修改數據的狀況,修改數據時不用加鎖,解鎖,也不會出現死鎖的問題,致使性能消耗。

3.不存在多進程或者多線程致使的切換而形成的一些性能消耗。

Redis採用單線程的劣勢:

1.沒法充分發揮多核機器的優點,不過能夠經過在機器上啓動多個Redis實例來利用資源。

Redis性能爲何高?

根據官網的介紹,Redis單機能夠到到10W的QPS(每秒處理請求數),Redis這麼快的緣由主要有如下幾點:

1.徹底基於內存,數據所有存儲在內存中,讀取時沒有磁盤IO,因此速度很是快。

2.Redis採用單線程的模型,沒有上下文切換的開銷,也沒有競態條件,不用去考慮各類鎖的問題,不存在加鎖釋放鎖操做,沒有由於可能出現死鎖而致使的性能消耗。

3.Redis項目中使用的數據結構都是專門設計的,例如SDS(簡單動態字符串)是對C語言中的字符串頻繁修改時,會頻繁地進行內存分配,十分消耗性能,而SDS會使用空間預分配和惰性空間釋放來避免這些問題的出現。 空間預分配技術: 對SDS進行修改時,若是修改後SDS實際使用長度爲len,

當len<1M時,分配的空間會是2*len+1,也就是會預留len長度的未使用空間,其中1存儲空字符

當len>1M時,分配的空間會是len+1+1M,也就是會預留1M長度的未使用空間,其中1存儲空字符

4.採用多路複用IO模型,能夠同時監測多個流的IO事件能力,在空閒時,會把當前線程阻塞掉,當有一個或多個流有I/O事件時,就從阻塞態喚醒,輪詢那些真正發出了事件的流,並只依次順序的處理就緒的流。可讓單個線程高效的處理多個鏈接請求(儘可能減小網絡 I/O 的時間消耗)。

Linux I/O模型一共有哪些?

IO模型主要由阻塞式I/O模型,非阻塞式I/O模型,I/O複用模型,信息驅動式I/O模型,異步I/O模型。

阻塞式I/O模型

用戶態進程調用recvfrom系統調用來接受數據,若是當前內核中數據沒有準備好,那麼會一直等待,不會進行其餘操做,直到內核中的數據準備好,將數據拷貝到用戶空間內存,而後recvfrom返回成功的信號,此時用戶態進行才解除阻塞的狀態,處理收到的數據。

非阻塞時I/O模型

在非阻塞式I/O模型中,當進程等待內核的數據,而當該數據未到達的時候,進程會不斷詢問內核,直到內核準備好數據。 用戶態進程調用recvfrom接收數據,當前並無數據報文產生,此時recvfrom返回EWOULDBLOCK,用戶態進程會一直調用recvfrom詢問內核,待內核準備好數據的時候,以後用戶態進程再也不詢問內核,待數據從內核複製到用戶空間,recvfrom成功返回,用戶態進程開始處理數據。

I/O多路複用模型

I/O複用指的是多個I/O鏈接複用一個進程。 最初級的I/O複用,就是一個進程對應多個鏈接,每次從頭到尾進行遍歷,判斷是否有I/O事件須要處理,有的話就進行處理,缺點是效率比較低,若是一直沒有事件進來,會致使CPU空轉。 升級版的I/O複用模型 當沒有I/O事件時,進程處於阻塞狀態,當有I/O事件時,就會有一個代理去喚醒進程,去進行輪詢,來處理I/O事件。(這裏的代理也就是select和poll,select只能觀察1024個鏈接,poll能夠觀察無限個鏈接) epoll是對select和poll的升級版,解決了不少問題,是線程安全的,並且能夠通知進程是哪一個Socket鏈接有I/O事件,提升了查找效率。 epoll和select/poll最大區別是 (1)epoll內部使用了mmap共享了用戶和內核的部分空間,避免了數據的來回拷貝 (2)epoll基於事件驅動,epoll_wait只返回發生的事件避免了像select和poll對事件的整個輪尋操做。

信息驅動式I/O模型

是非阻塞的,當須要等待數據時,用戶態進程會給內核發送一個信號,告知本身須要的數據,而後就去執行其餘任務了,內核準備好數據後,會給用戶態發送一個信號,用戶態進程收到以後,會立馬調用recvfrom,等待數據從從內核空間複製到用戶空間,待完成以後recvfrom返回成功指示,用戶態進程才處理數據。

異步I/O模型

與信息驅動式I/O模型區別在於,是在數據從內核態拷貝到用戶空間以後,內核才通知用戶態進程來處理數據。在複製數據到用戶空間這個時間段內,用戶態進程也是不阻塞的。

同步與異步的區別是什麼?

同步與異步的區別在於調用結果的通知方式上。 同步執行一個方法後,須要等待結果返回,而後繼續執行下去。 異步執行一個方法後,不會等待結果的返回,調用方定時主動去輪詢調用結果或者被調用方在執行完成後經過回調來通知調用方。

阻塞與非阻塞的區別是什麼?

阻塞與非阻塞的區別在於進程/線程在等待消息時,進程/線程是不是掛起狀態。 阻塞調用在發出去後,消息返回以前,當前進程/線程會被掛起,直到有消息返回,當前進/線程纔會被激活。 非阻塞調用在發出去後,不會阻塞當前進/線程,而會當即返回,能夠去執行其餘任務。

如何解決Redis緩存穿透問題?

Redis 緩存穿透指的是攻擊者故意大量請求一些Redis緩存中不存在key的數據,致使請 求打到數據庫上,致使數據庫壓力過大。

1.作好參數校驗,無效的請求直接返回,只能避免一部分狀況,攻擊者老是能夠找到一些沒有覆蓋的狀況。

2.對緩存中找不到的key,須要去數據庫查找的key,緩存到Redis中,可是可能會致使Redis中緩存大量無效的key,能夠設置一個很短的過時時間,例如1分鐘。

3.也可使用布隆過濾器,將全部可能的存在的數據經過去hash值的方式存入到一個足夠大的bitmap中去,處理請求時,經過在botmap中查找,能夠將不存在的數據攔截掉。

布隆過濾器是什麼?

布隆過濾器能夠理解爲一個有偏差的set結構,使用布隆過濾器來判斷元素是否存在其中時,若是返回結果是存在,實際可能存在也可能不存在,返回結果不存在時,實際結果確定是不存在。布隆過濾器其實是一個大型的位數組,添加key時,經過幾個hash函數對key計算獲得一個下標,而後根據下標將位數組中對應的值設爲1。

如何解決Redis緩存雪崩問題?

緩存雪崩主要指的是短期內大量key失效,致使全部請求所有轉向數據庫,致使數據庫壓力過大。 解決方案:

1.在給緩存設置失效時間時加一個隨機值,避免集體失效。

2.雙緩存機制,緩存A的失效時間爲20分鐘,緩存B沒有失效時間,從緩存A讀取數據,緩存A中沒有時,去緩存B中讀取數據,而且啓動一個異步線程來更新緩存A。

如何解決Redis緩存擊穿問題?

緩存擊穿主要是某個key過時後,剛好有大量高併發的請求訪問,致使數據庫壓力過大。 解放方案: 1.訪問數據庫以前設置互斥鎖。在從緩存獲取數據失敗後,先去設置互斥鎖,設置成功,再去進行數據庫操做,不然先休眠一段時間再重試。

Redis常見的數據結構有哪些?

Redis中主要的數據結構有String字符串,Hash哈希表,List列表,Set集合,ZSet有序集合。

談一談你對Redis中簡單動態字符串的理解?

Redis中的簡單動態字符串實際上是對C語言中的字符串的封裝和優化,由於C語言的字符串有兩個缺點:

1.不是二進制安全的(由於字符串以空字符做爲結束的標誌,字符串中間不能有空字符)。

2.頻繁修改一個字符串時,會涉及到內存的重分配,比較消耗性能。(Redis中的簡單動態字符串會有內存預分配和惰性空間釋放)。

因此Redis中的簡單動態字符串結構,除了包含一個字符數組的屬性,還包含數組的長度,數組的實際使用長度等屬性,經過增長長度屬性,能夠保證字符串是二進制安全的,從而能夠保存任意類型的數據,例如一張圖片,對象序列化後的數據等等。字符串使用場景以下:

1.字符串能夠保存一些字符串數據,也能夠保存一些數字類型的數據,因此可使用INCR, DECR, INCRBY對數字進行加減,因此能夠把字符串當成計數器使用。

2.同時由於在C語言中,每一個字符是一個字節,是8個二進制位,因此能夠把簡單動態字符串做爲一個位數組來使用,經過setbit,getbit命令來對位數組進行賦值,取值,能夠以很小的空間來保存用戶一年的每日簽到數據,以及Redis中的布隆過濾器也是經過位數組來實現的。

字符串的底層存儲

在Redis中,每個Value都是一個Redis對象,對應的都是RedisObject結構,在RedisObject結構中,保存了對象的類型type,底層的編碼encoding等一些屬性,也擁有一個ptr指針,指向對象具體的存儲地址。

struct RedisObject {
    int4 type; 
    int4 encoding; 
    int24 lru; 
    int32 refcount; 
    void *ptr; 
} robj;
複製代碼

在Redis中,字符串有兩種存儲方式,int編碼,embstr編碼和raw編碼。

int編碼

當value是一個整數,而且可使用long類型(8字節)來表示時,那麼會屬於int編碼,ptr直接存儲數值。(而且Redis會進行優化,啓動時建立0~9999的字符串對象做爲共享變量。)

embstr和raw編碼

兩種存儲方式下,都RedisObject和SDS結構(簡單動態字符串)來存儲字符串,區別在於,embstr對象用於存儲較短的字符串,embstr編碼中RedisObject結構與ptr指向的SDS結構在內存中是連續的,內存分配次數和內存釋放次數均是一次,而raw編碼會分別調用兩次內存分配函數來分別建立RedisObject結構和SDS結構。

談一談你對Redis中hash對象的理解?

在Redis中,value能夠是一個hash表,底層編碼能夠是ziplist,也能夠是hashtable(默認狀況下,當元素小於512個時,底層使用ziplist存儲數據)

ziplist

元素保存的字符串長度較短且元素個數較少時(小於64字節,個數小於512),出於節約內存的考慮,hash表會使用ziplist做爲的底層實現,ziplist是一塊連續的內存,裏面每個節點保存了對應的key和value,而後每一個節點很緊湊地存儲在一塊兒,優勢是沒有冗餘空間,缺點插入新元素須要調用realloc擴展內存。(可能會進行內存重分配,將內容拷貝過去,也可能在原有地址上擴展)。

hashtable

元素比較多時就會使用hashtable編碼來做爲底層實現,這個時候RedisObject的ptr指針會指向一個dict結構,dict結構中的ht數組保存了ht[0]和ht[1]兩個元素,一般使用ht[0]保存鍵值對,ht[1]只在漸進式rehash時使用。hashtable是經過鏈地址法來解決衝突的,table數組存儲的是鏈表的頭結點(添加新元素,首先根據鍵計算出hash值,而後與數組長度取模以後獲得數組下標,將元素添加到數組下標對應的鏈表中去)。

struct dict {
    int rehashindex;
    dictht ht[2]; 
}
struct dictht {
    dictEntry** table; // 二維
    long size; // 第一維數組的長度 
    long used; // hash 表中的元素個數 ...
}
typedef struct dictEntry {
  //鍵  
  void *key;
  //值,能夠是一個指針,也能夠是一個uint64_t整數,也能夠是int64_t的整數
  union {
    void *val;
    uint64_tu64;
    int64_ts64;
  } v;
  //指向下一個節點的指針
  struct dictEntry *next;
} dictEntry;
複製代碼

漸進式rehash

進行當負載因子>=1時,會進行哈希表擴展操做(若是是在執行BGSAVE或BGREWRITEAOF命令期間,那麼須要>=5纔會進行擴展)。

進行當負載因子<0.1時,會進行哈希表收縮操做。

由於直接一次性完成rehash會對性能產生影響,因此能夠漸進式rehash,具體執行步驟是 1.首先將對dict結構中ht[1]哈希表分配空間(大小取決於最接近實際使用空間的2的n次方冪),而後將rehashindex屬性設置爲0。

2.而後每次對ht[0]中的元素查找,修改,添加時,除了執行指定操做外,還會將對應下標的全部鍵值對rehash到ht[1],而且會將rehashindex+1。

3.當ht[0]中全部鍵值對都是rehash到ht[1]中後,那麼會ht[1]和ht[0]的指針值進行交換,將rehashindex設置爲-1,表明rehash完成。 (整個rehash期間,查找,更新,刪除會先去ht[0]中執行,沒有才會到ht[1]中執行,新添加鍵值對是會被保存到ht[1]中)。

談一談你對Redis中List的理解?

在Redis中,存儲的value能夠是一個列表List,跟Java中的LinkedList很像,底層數據結構是一個鏈表,插入和刪除很快,隨機訪問較慢,時間複雜度是O(N)。Java中的列表數據進行緩存時通常是序列化成JSON,以字符串的形式存儲在Redis上,而不是使用Redis中的List來進行存儲。Redis中的List能夠做爲一個隊列來使用,也能夠做爲一個棧來使用。在實際使用中,經常使用來作異步隊列使用,將能夠延時處理的任務序列化成字符串塞進Redis的列表,另一個線程從列表中輪詢數據進行處理

quiklist

老版本中的Redis,元素較少時,使用ziplist來做爲底層編碼,元素較多時使用雙向鏈表linkedList做爲底層編碼。由於鏈表每一個節點須要prev,next指針,須要佔用16字節,並且每一個節點內存都是單獨分配,加重內存碎片化,因此新版本使用quiklist做爲底層編碼,quiklist的是一個雙向鏈表,可是它的每個節點是一個ziplist。(默認每一個ziplist最大長度爲8k字節)

談一談你對Redis中Set的理解?

Set是一個無序的,不重複的字符串集合,底層編碼有inset和hashtable兩種。

inset

當元素都爲整數,且元素個數較少時會使用inset做爲底層編碼,inset結構中的有一個contents屬性,content是是一個整數數組,從小到大保存了全部元素。

hashtable

當元素個數較多時,Set使用hashtable來保存元素,元素的值做爲key,value都是NULL。

談一談你對Redis中ZSet(有序集合)的理解?

Zset與Set的區別在於每個元素都有一個Score屬性,而且存儲時會將元素按照Score從低到高排列。

當元素較少時,ZSet的底層編碼使用ziplist實現,全部元素按照Score從低到高排序。

當元素較多時,使用skiplist+dict來實現, skiplist存儲元素的值和Score,而且將全部元素按照分值有序排列。便於以O(logN)的時間複雜度插入,刪除,更新,及根據Score進行範圍性查找。

dict存儲元素的值和Score的映射關係,便於以O(1)的時間複雜度查找元素對應的分值。

Redis主從同步是怎麼實現的?

主從節點創建鏈接後,從節點會進行判斷

1.若是這是從節點以前沒有同步過數據,屬於初次複製,會進行全量重同步 那麼從節點會向主節點發送PSYNC?-1 命令,請求主節點進行全量重同步。

2.若是這是從節點不說初次複製(例如出現掉線後重連), 這個時候從節點會將以前進行同步的Replication ID(一個隨機字符串,標識主節點上的特定數據集)和offset(從服務器當前的複製偏移量)經過PSYNC 命令發送給主節點,主節點會進行判斷,

  • 若是Replication ID跟當前的Replication ID不一致,或者是當前buffer緩衝區中不存在對應的offset,那麼會跟上面的初次複製同樣,進行全量重同步。
  • 若是Replication ID跟當前的Replication ID一致而且當前buffer緩衝區中存在對應的offset,那麼會進行部分重同步。(部分重同步是Redis 2.8以後的版本支持的,主要基於性能考慮,爲了斷線期間的小部分數據修改進行全量重同步效率比較低)
全量重同步

主節點會執行BGSAVE命令,fork出一個子進程,在後臺生成一個RDB持久化文件,完成後,發送給 從服務器,從節點接受並載入RDB文件,使得從節點的數據庫狀態更新至主節點執行BGSAVE命令時的狀態。而且在生成RDB文件期間,主節點也會使用一個緩衝區來記錄這個期間執行的全部寫命令,將這些命令發送給從節點,從節點執行命令將本身數據庫狀態更新至與主節點徹底一致。

部分重同步

由於此時從節點只是落後主節點一小段時間的數據修改,而且偏移量在複製緩衝區buffer中能夠找到,因此主節點把從節點落後的這部分數據修改命令發送給從節點,完成同步。

命令傳播

在執行全量重同步或者部分重同步之後,主從節點的數據庫狀態達到一致後,會進入到命令傳播階段。主節點執行修改命令後,會將修改命令添加到內存中的buffer緩衝區(是一個定長的環形數組,滿了時就會覆蓋前面的數據),而後異步地將buffer緩衝區的命令發送給從節點。

Redis中哨兵是什麼?

Redis中的哨兵服務器是一個運行在哨兵模式下的Redis服務器,核心功能是監測主節點和從節點的運行狀況,在主節點出現故障後, 完成自動故障轉移,讓某個從節點升級爲主節點。

客戶端是怎麼接入哨兵系統的?

首先Redis中的哨兵節點是一個配置提供者,而不是代理。 區別在於,

配置提供者

前者只負責存儲當前最新的主從節點信息,供客戶端獲取。

代理

客戶端全部請求都會通過哨兵節點。

因此實際開發中,經過在客戶端配置哨兵節點的地址+主節點的名稱(哨兵系統可能會監控多個主從節點,名稱用於區分)就能夠獲取到主節點信息, 下面的代碼在底層實現是客戶端向依次向哨兵節點發送"sentinel get-master-addr-by-name"命令, 成功得到主節點信息就不向後面的哨兵節點發送命令。 同時客戶端會訂閱哨兵節點的+switch-master頻道,一旦主節點發送故障,哨兵服務器對主節點進行自動故障轉移,會將從節點升級主節點, 而且更新哨兵服務器中存儲的主節點信息,會向+switch-master頻道發送消息,客戶端獲得消息後從新從哨兵節點獲取主節點信息,初始化鏈接池。

String masterName = "mymaster";
 Set<String> sentinels = new HashSet<>();
 sentinels.add("192.168.92.128:26379");
 sentinels.add("192.168.92.128:26380");

 JedisSentinelPool pool = new JedisSentinelPool(masterName, sentinels); //初始化過程作了不少工做
 Jedis jedis = pool.getResource();
 jedis.set("key1", "value1");
 pool.close();
複製代碼

Redis哨兵系統是怎麼實現自動故障轉移的?

1.認定主節點主觀下線

由於每隔2s,哨兵節點會給主節點發送PING命令,若是在必定時間間隔內,都沒有收到回覆,那麼哨兵節點就認爲主節點主觀下線。

2.認定主節點客觀下線

哨兵節點認定主節點主觀下線後,會向其餘哨兵節點發送sentinel is-master-down-by-addr命令,獲取其餘哨兵節點對該主節點的狀態,當認定主節點下線的哨兵數量達到必定數值時,就認定主節點客觀下線。

3.進行領導者哨兵選舉

認定主節點客觀下線後,各個哨兵之間相互通訊,選舉出一個領導者哨兵,由它來對主節點進行故障轉移操做。

選舉使用的是Raft算法,基本思路是全部哨兵節點A會先其餘哨兵節點,發送命令,申請成爲該哨兵節點B的領導者,若是B尚未贊成過其餘哨兵節點,那麼就贊成A成爲領導者,最終得票超過半數以上的哨兵節點會贏得選舉,若是本次投票,沒有選舉出領導者哨兵,那麼就開始新一輪的選舉,直到選舉出哨兵節點(實際開發中,最早斷定主節點客觀下線的哨兵節點,通常就能成爲領導者。)

4.領導者哨兵進行故障轉移

領導者哨兵節點首先會從從節點中選出一個節點做爲新的主節點。選擇的規則是:

1.首先排除一些不健康的節點。(下線的,斷線的,最近5s沒有回覆哨兵節點的INFO命令的,與舊的主服務器斷開鏈接時間較長的)

2.而後根據優先級,複製偏移量,runid最小,來選出一個從節點做爲主節點。

向這個從節點發送slaveof no one命令,讓其成爲主節點,經過slaveof 命令讓其餘從節點成爲它的從節點,將已下線的主節點更新爲新的主節點的從節點。

相關文章
相關標籤/搜索