你說一下Redis爲什麼快吧,怎麼實現高可用,還有持久化怎麼做的?

前言

作爲Java程序員,在面試過程中,緩存相關的問題是躲不掉的,肯定會問,例如緩存一致性問題,緩存雪崩、擊穿、穿透等。說到緩存,那肯定少不了Redis,我在面試的時候也是被問了很多關於Redis相關的知識,但是Redis的功能太強大了,並不是一時半會兒能掌握好的,因爲有些高級特性或是知識平時並不會用到。
所以回答的不好,人家就會覺得你對自己平時使用的工具都沒有了解,自然就涼涼了。其實很早就有這個打算,打算好好總結一下Redis的知識,但也是由於自己都沒有好好的瞭解Redis呢,所以一直沒有開始。這次準備慢慢的來總結。

Redis爲什麼這麼快

Redis是一個由C語言編寫的開源的,基於內存,支持多種數據結構可持久化的NoSQL數據庫。
它速度快主要是有以下幾個原因:

  • 基於內存運行,性能高效;
  • 數據結構設計高效,例如String是由動態字符數組構成,zset內部的跳錶;
  • 採用單線程,避免了線程的上下文切換,也避免了線程競爭產生的死鎖等問題;
  • 使用I/O多路複用模型,非阻塞IO;

官網上給出單臺Redis的可以達到10w+的QPS的, 一臺服務器上在使用Redis的時候單核的就夠了,但是目前服務器都是多核CPU,要想不浪費資源,又能提交效率,可以在一臺服務器上部署多個Redis實例。

高可用方案

雖然單臺Redis的的性能很好,但是Redis的單節點並不能保證它不會掛了啊,畢竟單節點的Redis是有上限的,而且人家單節點又要讀又要寫,小身板扛不住咋辦,所以爲了保證高可用,一般都是做成集羣。

主從(Master-Slave)

Redis官方是支持主從同步的,而且還支持從從同步,從從同步也可以理解爲主從同步,只不過從從同步的主節點是另一個主從的從節點。
在這裏插入圖片描述
有了主從同步的集羣,那麼主節點就負責提供寫操作,而從節點就負責支持讀操作。

那麼他們之間是如何進行數據同步的呢?

如果Slave(從節點)是第一次跟Master進行連接,

  • 那麼會首先會向Master發送同步請求psync
  • 主節點接收到同步請求,開始fork主子進程開始進行全量同步,然後生成RDB文件;
  • 這個時候主節點同時會將新的寫請求,保存到緩存區(buffer)中;
  • 從節點接收到RDB文件後,先清空老數據,然後將RDB中數據加載到內存中;
  • 等到從節點將RDB文件同步完成後再同步緩存區中的寫請求。

這裏有一點需要注意的就是,主節點的緩存區是有限的,內部結構是一個環形數組,當數組被佔滿之後就會覆蓋掉最早之前的數據。

所以如果由於網絡或是其他原因,造成緩存區中的數據被覆蓋了,那麼當從節點處理完主節點的RDB文件後,就不得不又要進行一全量的RDB文件的複製,才能保證主從節點的數據一致。

如果不設置好合理的buffer區空間,是會造成一個RDB複製的死循環。

當主從間的數據同步完成之後,後面主節點的每次寫操作就都會同步到從節點,這樣進行增量同步了。

由於負載的不斷上升就導致了主從之間的延時變大,所以就有了上面我說的從從同步了,主節點先同步到一部分從節點,然後由從節點去同步其他的從節點。

在Redis從2.8.18開始支持無盤複製,主節點通過套接字,一邊遍歷內存中的數據,一邊讓數據發送給從節點,從節點和之前一樣,先將數據存儲在磁盤文件中,然後再一次性加載。

另外由於主從同步是異步的,所以從Redis3.0之後出現了同步複製,就是通過wait命令來進行控制,wait命令有兩個參數,第一個是從庫數量,第二個是等待從庫的複製時間,如果第二個參數設置爲0,那麼就是代表要等待所有從庫都複製完纔去執行後面的命令。
但是這樣就會存在一個隱患,當網絡異常後,wait命令會一直阻塞下去,導致Redis不可用。

哨兵(Sentinel)

哨兵可以監控Redis集羣的健康狀態,當主節點掛掉之後,選舉出新的主節點。客戶端在使用Redis的時候會先通過Sentinel來獲取主節點地址,然後再通過主節點來進行數據交互。當主節點掛掉之後,客戶端會再次向Sentinel獲取主節點,這樣客戶端就可以無感知的繼續使用了。
在這裏插入圖片描述
哨兵集羣工作過程,主節點掛掉之後會選舉出新的主節點,然後監控掛掉的節點,當掛掉的節點恢復後,原先的主節點就會變成從節點,從新的主節點那裏建立主從關係。
在這裏插入圖片描述

集羣分片(Redis Cluster)

Redis Cluster是Redis官方推薦的集羣模式,Redis Cluster將所有數據劃分到16384個槽(slots)中,每個節點負責一部分槽位的讀寫操作。

在這裏插入圖片描述

存儲

Redis Cluster默認是通過CRC16算法獲取到key的hash值,然後再對16384進行取餘(CRC16(key)%16384),獲取到的槽位在哪個節點負責的範圍內(這裏一般是會有一個槽位和節點的映射表來進行快速定位節點的,通常使用bitmap來實現),就存儲在哪個節點上。

重定向

當Redis Cluster的客戶端在和集羣建立連接的時候,也會獲得一份槽位和節點的配置關係(槽位和節點的映射表),這樣當客戶端要查找某個key時,可以直接定位到目標節點。

但是當客戶端發送請求時,如果接收請求的節點發現該數據的槽位並不在當前節點上,那麼會返回MOVED指令將正確的槽位和節點信息返回給客戶端,客戶接着請求正確的節點獲取數據。

一般客戶端在接收到MOVED指令後,也會更新自己本地的槽位和節點的映射表,這樣下次獲取數據時就可以直接命中了。這整個重定向的過程對客戶端是透明的。

數據遷移

當集羣中新增節點或刪除節點後,節點間的數據遷移是按槽位爲單位的,一個槽位一個槽位的遷移,當遷移時原節點狀態處於:magrating,目標節點處於:importing

在遷移過程中,客戶端首先訪問舊節點,如果數據還在舊節點,那麼舊節點正常處理,如果不在舊節點,就會返回一個-ASK + 目標節點地址的指令,客戶端收到這個-ASK指令後,向目標節點執行一個asking指令(告訴新節點,必須處理客戶端這個數據),然後再向目標節點執行客戶端的訪問數據的指令。
在這裏插入圖片描述

容錯

Redis Cluster可以爲每個主節點設置多個從節點,當單個主節點掛掉後,集羣會自動將其中某個從節點提升爲主節點,若沒有從節點,那麼集羣將處於不可用狀態。
Redis提供了一個參數:cluster-require-full-coverage,用來配置可以允許部分節點出問題後,還有其他節點在運行時可以正常提供服務。

另外一點比較特殊的是,Cluster中當一個節點發現某個其他節點出現失聯了,這個時候問題節點只是PFailPossibly-可能下線),然後它會把這個失聯信息廣播給其他節點,當一個節點接收到某個節點的失聯信息達到集羣的大多數時,就可以將失聯節點標記爲下線,然後將下線信息廣播給其他節點。若失聯節點爲主節點,那麼將立即對該節點進行主從切換。

Redis高可用就先說到這裏吧,後面其實還有Codis,但是目前Cluster逐漸流行起來了,Codis的競爭力逐漸被蠶食,而且對新版本的支持,更新的也比較慢,所以這裏就不說它了,感興趣的可以自己去了解一下,國人開源的Redis集羣模式Codis。

持久化

Redis持久化的意義在於,當出現宕機問題後,能將數據恢復到緩存中,它提供了兩種持久化機制:一種是快照(RDB),一種是AOF日誌。

快照是一次全量備份,而AOF是增量備份。快照是內存數據的二進制序列化形式,存儲上非常緊湊,而AOF日誌記錄的是內存數據修改的指令記錄文本。

快照備份(RDB)

因爲Redis是單線程的,所以在做快照持久化的時候,通常有兩個選擇,save命令,會阻塞線程,直到備份完成;bgsave會異步的執行備份,其實是fork出了一個子進程,用子進程去執行快照持久化操作,將數據保存在一個.rdb文件中。

子進程剛剛產生的時候,是和父進程共享內存中的數據的,但是子進程做持久化時,是不會修改數據的,而父進程是要持續提供服務的,所以父進程就會持續的修改內存中的數據,這個時候父進程就會將內存中的數據,Copy出一份來進行修改。

父進程copy的數據是以數據頁爲單位的(4k一頁),對那一頁數據進行修改就copy哪一頁的數據。

子進程由於數據沒有變化就會一直的去遍歷數據,進程持久化操作了,這就是隻保留了創建子進程的時候的快照。

那麼RDB是在什麼時候觸發的呢?

# save <seconds> <changes>
save 60 10000
save 300 10 

上這段配置就是在redis.conf文件中配置的,第一個參數是時間單位是秒,第二個參數執行數據變化的次數。
意思就是說:300秒之內至少發生10次寫操作、
60秒之內發生至少10000次寫操作,只要滿足任一條件,均會觸發bgsave

增量日誌備份(AOF)

Redis在接收到客戶端請求指令後,會先進行校驗,校驗成功後,立即將指令存儲到AOF日誌文件中,就是說,Redis是先記錄日誌,再執行命令。這樣即使命令還沒執行突然宕機了,通過AOF日誌文件也是可以恢復的。

AOF重寫

AOF日誌文件,隨着時間的推移,會越來越大,所以就需要進行重寫瘦身。AOF重寫的原理就是,fork一個子進程,對內存進行遍歷,然後生成一系列的Redis指令,然後序列化到一個新的aof文件中。然後再將遍歷內存階段的增量日誌,追加到新的aof文件中,追加完成後立即替換舊的aof文件,這樣就完成了AOF的瘦身重寫

fsync

因爲AOF是一個寫文件的IO操作,是比較耗時。所以AOF日誌並不是直接寫入到日誌文件的,而是先寫到一個內核的緩存中,然後通過異步刷髒,來將數據保存到磁盤的。

由於這個情況,就導致了會有還沒來得急刷髒然後就宕機了,導致數據丟失的風險。

所以Redis提供了一個配置,可以手動的來選擇刷髒的頻率。

  • always:每一條AOF記錄都立即同步到文件,性能很低,但較爲安全。
  • everysec每秒同步一次,性能和安全都比較中庸的方式,也是redis推薦的方式。如果遇到物理服務器故障,可能導致最多1秒的AOF記錄丟失。
  • no:Redis永不直接調用文件同步,而是讓操作系統來決定何時同步磁盤。性能較好,但很不安全。

AOF默認是關閉的,需要在配置文件中手動開啓。

# 只有在“yes”下,aof重寫/文件同步等特性纔會生效  
appendonly yes  
## 指定aof文件名稱  
appendfilename appendonly.aof  
## 指定aof操作中文件同步策略,有三個合法值:always everysec no,默認爲everysec  
appendfsync everysec 
## 在aof-rewrite期間,appendfsync是否暫緩文件同步,"no"表示“不暫緩”,“yes”表示“暫緩”,默認爲“no”  
no-appendfsync-on-rewrite no  
## aof文件rewrite觸發的最小文件尺寸(mb,gb),只有大於此aof文件大於此尺寸是纔會觸發rewrite,默認“64mb”,建議“512mb”  
auto-aof-rewrite-min-size 64mb 

Redis4.0混合持久化

Redis4.0提供了一種新的持久化機制,就是RDB和AOF結合使用,將rdb文件內容和aof文件存在一起,AOF中保存的不再是全部數據了,而是從RDB開始的到結束的增量日誌。

這樣在Redis恢復數據的時候,可以先假裝RDB文件中的內容,然後在順序執行AOF日誌中指令,這樣就將Redis重啓時恢復數據的效率得到了大幅度提升。

結尾

恩,這次就先總結到這裏吧,後面會繼續總結Redis相關知識,LRU、LFU、內存淘汰策略,管道等等。

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