論程序的健壯性——就看Redis

論程序的健壯性——就看Redis

「衆裏尋他千百度,驀然回首,那人卻在,燈火闌珊處」。多年的IT生涯,一直但願本身寫的程序可以有很強的健壯性,也一直但願能找到一個高可用的標杆程序去借鑑學習,不畏懼內存溢出、磁盤滿了、斷網、斷電、機器重啓等等狀況。但意想不到的是,這個標杆程序居然就是從一開始就在使用的分佈式緩存——Redis。redis

論程序的健壯性——就看Redis
Redis(Remote Dictionary Server ),即遠程字典服務,是 C 語言開發的一個開源的高性能鍵值對(key-value)的內存數據庫。因爲它是基於內存的因此它要比基於磁盤讀寫的數據庫效率更快。所以Redis也就成了你們解決數據庫高併發訪問、分佈式讀寫和分佈式鎖等首選解決方案。
算法

那麼既然它是基於內存的,若是內存滿了怎麼辦?程序會不會崩潰?既然它是基於內存的,若是服務器宕機了怎麼辦?數據是否是就丟失了?既然它是分佈式的,這臺Redis服務器斷網了怎麼辦?數據庫

今天咱們就一塊兒來看看Redis的設計者,一名來自意大利的小夥,是如何打造出一個超強健壯性和高可用性的程序,從而不害怕這些狀況。緩存

1、 Redis的內存管理策略——內存永不溢出

Redis主要有兩種策略機制來保障存儲的key-value數據不會把內存塞滿,它們是:過時策略和淘汰策略。安全

一、 過時策略

用過Redis的人都知道,咱們往Redis裏添加key-value的數據時,會有個選填參數——過時時間。若是設置了這個參數的值,Redis到過時時間後會自行把過時的數據給清除掉。「過時策略」指的就是Redis內部是如何實現將過時的key對應的緩存數據清除的。服務器

在Redis源碼中有三個核心的對象結構:redisObject、redisDb和serverCron。併發

  • redisObject:Redis 內部使用redisObject 對象來抽象表示全部的 key-value。簡單地說,redisObject就是string、hash、list、set、zset的父類。爲了便於操做,Redis採用redisObject結構來統一這五種不一樣的數據類型。

論程序的健壯性——就看Redis

  • redisDb:Redis是一個鍵值對數據庫服務器,這個數據庫就是用redisDb抽象表示的。redisDb結構中有不少dict字典保存了數據庫中的全部鍵值對,這些字典就叫作鍵空間。以下圖所示其中有個「expires」的字典就保存了設置過時時間的鍵值對。而Redis的過時策略也是圍繞它來進行的。異步

    論程序的健壯性——就看Redis

  • serverCron:Redis 將serverCron做爲時間事件來運行,從而確保它每隔一段時間就會自動運行一次。所以redis中全部定時執行的事件任務都在serverCron中執行。

瞭解完Redis的三大核心結構後,我們回到「過時策略」的具體實現上,其實Redis主要是靠兩種機制來處理過時的數據被清除:按期過時(主動清除)和惰性過時(被動清除)。分佈式

  • 惰性過時(被動清除):就是每次訪問的時候都去判斷一下該key是否過時,若是過時了就刪除掉。該策略就能夠最大化地節省CPU資源,可是卻對內存很是不友好。由於不實時過時了,本來該過時刪除的就可能一直堆積在內存裏面!極端狀況可能出現大量的過時key沒有再次被訪問,從而不會被清除,佔用大量內存。ide

  • 按期過時(主動清除):每隔必定的時間,會掃描Redis數據庫的expires字典中必定數量的key,並清除其中已過時的 key。Redis默認配置會每100毫秒進行1次(redis.conf 中經過 hz 配置)過時掃描,掃描並非遍歷過時字典中的全部鍵,而是採用了以下方法:

(1)從過時字典中隨機取出20個鍵;
(server.h文件下ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP配置20)

(2)刪除這20個鍵中過時的鍵;

(3)若是過時鍵的比例超過 25% ,重複步驟 1 和 2;

具體邏輯以下圖:

論程序的健壯性——就看Redis

由於Redis中同時使用了惰性過時和按期過時兩種過時策略,因此在不一樣狀況下使得 CPU 和內存資源達到最優的平衡效果的同時,保證過時的數據會被及時清除掉。

二、淘汰策略

在Redis可能沒有須要過時的數據的狀況下,仍是會把咱們的內存都佔滿。好比每一個key設置的過時時間都很長或不過時,一直添加就有可能把內存給塞滿。那麼Redis又是怎麼解決這個問題的呢?——那就是「淘汰策略」。

論程序的健壯性——就看Redis

官網地址:https://redis.io/topics/lru-cache
Reids官網上面列出的淘汰策略一共有8種,但從實質算法來看只有兩種實現算法,分別是LRU和LFU。

LRU(Least Recently Used):翻譯過來是最久未使用,根據時間軸來走,淘汰那些距離上一次使用時間最久遠的數據。
LRU的簡單原理以下圖:

論程序的健壯性——就看Redis

從上圖咱們能夠看出,在容器滿了的狀況下,距離上次讀寫時間最久遠的E被淘汰掉了。那麼數據每次讀取或者插入都須要獲取一下當前系統時間,以及每次淘汰的時候都須要拿當前系統時間和各個數據的最後操做時間作對比,這麼幹勢必會增長CPU的負荷從而影響Redis的性能。Redis的設計者爲了解決這一問題,作了必定的改善,總體的LRU思路以下:

(1)、Redis裏設置了一個全局變量 server.lruclock 用來存放系統當前的時間戳。這個全局變量經過serverCron 每100毫秒調用一次updateCachedTime()更新一次值。

(2)、每當redisObject數據被讀或寫的時候,將當前的 server.lruclock值賦值給 redisObject 的lru屬性,記錄這個數據最後的lru值。

(3)、觸發淘汰策略時,隨機從數據庫中選擇採樣值配置個數key, 淘汰其中熱度最低的key對應的緩存數據。

注:熱度就是拿當前的全局server.lruclock 值與各個數據的lru屬性作對比,相差最久遠的就是熱度最低的。

論程序的健壯性——就看Redis

Redis中全部對象結構都有一個lru字段, 且使用了unsigned的低24位,這個字段就是用來記錄對象的熱度。

LFU(Least Frequently Used):翻譯成中文就是最不經常使用。是按着使用頻次來算的,淘汰那些使用頻次最低的數據。說白了就是「末尾淘汰制」!
剛纔講過的LRU按照最久未使用雖然能達到淘汰數據釋放空間的目的,可是它有一個比較大的弊端,以下圖:

論程序的健壯性——就看Redis

如圖所示A在10秒內被訪問了5次,而B在10秒內被訪問了3 次。由於 B 最後一次被訪問的時間比A要晚,在同等的狀況下,A反而先被回收。那麼它就是不合理的。LFU就完美解決了LRU的這個弊端,具體原理以下:

論程序的健壯性——就看Redis

上圖是末尾淘汰的原理示意圖,僅是按次數這個維度作的末尾淘汰,但若是Redis僅按使用次數,也會有一個問題,就是某個數據以前被訪問過不少次好比上萬次,但後續就一直不用了,它自己按使用頻次來說是應該被淘汰的。所以Redis在實現LFU時,用兩部分數據來標記這個數據:使用頻率和上次訪問時間。總體思路就是:有讀寫我就增長熱度,一段時間內沒有讀寫我就減小相應熱度。

論程序的健壯性——就看Redis

不論是LRU仍是LFU淘汰策略,Redis都是用lru這個字段實現的具體邏輯,若是配置的淘汰策略是LFU時,lru的低8位表明的是頻率,高16位就是記錄上次訪問時間。總體的LRU思路以下:

(1)每當數據被寫或讀的時候都會調用LFULogIncr(counter)方法,增長lru低8位的訪問頻率數值;具體每次增長的數值在redis.conf中配置默認是10(# lfu-log-factor 10)

(2)還有另一個配置lfu-decay-time 默認是1分鐘,來控制每隔多久沒人訪問則熱度會遞減相應數值。這樣就規避了一個超大訪問次數的數據好久都不被淘汰的漏洞。

小結:「過時策略」 保證過時的key對應的數據會被及時清除;「淘汰策略」保證內存滿的時候會自動釋放相應空間,所以Redis的內存能夠自運行保證不會產生溢出異常。

2、 Redis的數據持久化策略——宕機可當即恢復數據到內存

有了內存不會溢出保障後,咱們再來看看Redis是如何保障服務器宕機或重啓,原來緩存在內存中的數據是不會丟失的。也就是Redis的持久化機制。

Redis 的持久化策略有兩種:RDB(快照全量持久化)和AOF(增量日誌持久化)

一、 RDB

RDB 是 Redis 默認的持久化方案。RDB快照(Redis DataBase),當觸發必定條件的時候,會把當前內存中的數據寫入磁盤,生成一個快照文件dump.rdb。Redis重啓會經過dump.rdb文件恢復數據。那那個必定的條件是啥呢?到底何時寫入rdb 文件?

觸發Redis執行rdb的方式有兩類:自動觸發和手動觸發
「自動觸發」的狀況有三種:達到配置文件觸發規則時觸發、執行shutdown命令時觸發、執行flushall命令時觸發。

注:在redis.conf中有個 SNAPSHOTTING配置,其中定義了觸發把數據保存到磁盤觸發頻率。

「手動觸發」的方式有兩種:執行save 或 bgsave命令。執行save命令在生成快照的時候會阻塞當前Redis服務器,Redis不能處理其餘命令。若是內存中的數據比較多,會形成Redis長時間的阻塞。生產環境不建議使用這個命令。

爲了解決這個問題,Redis 提供了第二種方式bgsave命令進行數據備份,執行bgsave時,Redis會在後臺異步進行快照操做,快照同時還能夠響應客戶端請求。

具體操做是Redis進程執行fork(建立進程函數)操做建立子進程(copy-on-write),RDB持久化過程由子進程負責,完成後自動結束。它不會記錄 fork 以後後續的命令。阻塞只發生在fork階段,通常時間很短。手動觸發的場景通常僅用在遷移數據時纔會用到。

咱們知道了RDB的實現的原理邏輯,那麼咱們就來分析下RDB到底有什麼優劣勢。

優點:

(1)RDB是一個很是緊湊(compact類型)的文件,它保存了redis在某個時間點上的數據集。這種文件很是適合用於進行備份和災難恢復。

(2)生成RDB文件的時候,redis主進程會fork()一個子進程來處理全部保存工做,主進程不須要進行任何磁盤IO操做。

(3)RDB在恢復大數據集時的速度比AOF的恢復速度要快。

劣勢:

RDB方式數據沒辦法作到實時持久化/秒級持久化。在必定間隔時間作一次備份,因此若是Redis意外down掉的話,就會丟失最後一次快照以後的全部修改

二、 AOF(Append Only File)

AOF採用日誌的形式來記錄每一個寫操做的命令,並追加到文件中。開啓後,執行更改 Redis數據的命令時,就會把命令寫入到AOF文件中。Redis重啓時會根據日誌文件的內容把寫指令從前到後執行一次以完成數據的恢復工做。

論程序的健壯性——就看Redis

其實AOF也不必定是徹底實時的備份操做命令,在redis.conf 咱們能夠配置選擇 AOF的執行方式,主要有三種:always、everysec和no

AOF是追加更改命令文件,那麼你們想下一直追加追加,就是會致使文件過大,那麼Redis是怎麼解決這個問題的呢?
Redis解決這個問題的方法是AOF下面有個機制叫作bgrewriteaof重寫機制,咱們來看下它是個啥

論程序的健壯性——就看Redis

注:AOF文件重寫並非對原文件進行從新整理,而是直接讀取服務器現有的鍵值對,而後用一條命令去代替以前記錄這個鍵值對的多條命令,生成一個新的文件後去替換原來的AOF文件。

咱們知道了AOF的實現原理,咱們來分析下它的優缺點。

優勢:

能最大限度的保證數據安全,就算用默認的配置everysec,也最多隻會形成1s的數據丟失。

缺點:

數據量比RDB要大不少,因此性能沒有RDB好!

論程序的健壯性——就看Redis

小結:由於有了持久化機制,所以Redis即便服務器宕機或重啓了,也能夠最大限度的恢復數據到內存中,提供給client繼續使用。

3、Redis的哨兵模式——可戰到最後一兵一卒的高可用集羣

內存滿了不會掛,服務器宕機重啓也沒問題。足見Redis的程序健壯性已經足夠強大。但Redis的設計者,在面向高可用面前,仍繼續向前邁進了一步,那就是Redis的高可用集羣方案——哨兵模式。

所謂的「哨兵模式」就是有一羣哨兵(Sentinel)在Redis服務器前面幫咱們監控這Redis集羣各個機器的運行狀況,而且哨兵間相互通告通知,並指引咱們使用那些健康的服務。

論程序的健壯性——就看Redis

Sentinel工做原理:

一、 Sentinel 默認以每秒鐘1次的頻率向Redis全部服務節點發送 PING 命令。若是在down-after-milliseconds 內都沒有收到有效回覆,Sentinel會將該服務器標記爲下線(主觀下線)。

二、 這個時候Sentinel節點會繼續詢問其餘的Sentinel節點,確認這個節點是否下線, 若是多數 Sentinel節點都認爲master下線,master才真正確認被下線(客觀下線),這個時候就須要從新選舉master。

Sentinel的做用:

一、監控:Sentinel 會不斷檢查主服務器和從服務器是否正常運行

二、故障處理:若是主服務器發生故障,Sentinel能夠啓動故障轉移過程。把某臺服務器升級爲主服務器,併發出通知

三、配置管理:客戶端鏈接到 Sentinel,獲取當前的 Redis 主服務器的地址。咱們不是直接去獲取Redis主服務的地址,而是根據sentinel去自動獲取誰是主機,即便主機發生故障後咱們也不用改代碼的鏈接!

論程序的健壯性——就看Redis

小結:有了「哨兵模式」只要集羣中有一個Redis服務器還健康存活,哨兵就能把這個健康的Redis服務器提供給咱們(如上圖的一、2兩步),那麼咱們客戶端的連接就不會出錯。所以,Redis集羣能夠戰鬥至最後一兵一卒。

這就是Redis,一個「高可用、強健壯性」的標杆程序!

做者:宜信技術學院 譚文濤

相關文章
相關標籤/搜索