從零單排學Redis【白銀】

Java3y發表於2018-12-03

前言

只有光頭才能變強

今天繼續來學習Redis,上一篇從零單排學Redis【青銅】已經將Redis常用的資料結構過了一遍了。如果還沒看的同學可以先去看一遍再回來~

這篇主要講的內容有:

  • Redis伺服器的資料庫

  • Redis對過期鍵的處理

  • Redis持久化策略(RDB和AOF)

本文力求簡單講清每個知識點,希望大家看完能有所收穫

一、Redis伺服器中的資料庫

我們應該都用過MySQL,MySQL我們可以在裡邊建立好幾個庫:

從零單排學Redis【白銀】

同樣地,Redis伺服器中也有資料庫這麼一個概念。如果不指定具體的數量,預設會有16個資料庫。

從零單排學Redis【白銀】

上面的命令我們也可以發現:當切換到15號資料庫,存進15號庫的資料,再切換到0號資料庫時,是獲取不到的!

  • 這說明,資料庫與資料庫之間的資料是隔離的。

1.1Redis資料庫的原理

Redis伺服器用redisServer結構體來表示,其中redisDb是一個陣列,用來儲存所有的資料庫,dbnum代表資料庫的數量(這個可以配置,預設是16)

struct redisServer{  

    //redisDb陣列,表示伺服器中所有的資料庫
    redisDb *db;  

    //伺服器中資料庫的數量
    int dbnum;  

}; 

我們知道Redis是C/S結構,Redis客戶端透過redisClient結構體來表示:

typedef struct redisClient{  

    //客戶端當前所選資料庫
    redisDb *db;  

}redisClient;

Redis客戶端連線Redis服務端時的示例圖:

從零單排學Redis【白銀】

Redis中對每個資料庫用redisDb結構體來表示:

typedef struct redisDb { 
    int id;         // 資料庫ID標識
    dict *dict;     // 鍵空間,存放著所有的鍵值對              
    dict *expires;  // 過期雜湊表,儲存著鍵的過期時間                          
    dict *watched_keys; // 被watch命令監控的key和相應client    
    long long avg_ttl;  // 資料庫內所有鍵的平均TTL(生存時間)     
} redisDb;

從程式碼上我們可以發現最重要的應該是dict *dict,它用來存放著所有的鍵值對。對於dict資料結構(雜湊表)我們在上一篇也已經詳細說了。一般我們將儲存所有鍵值對的dict稱為鍵空間

鍵空間示意圖:

從零單排學Redis【白銀】

Redis的資料庫就是使用字典(雜湊表)來作為底層實現的,對資料庫的增刪改查都是構建在字典(雜湊表)的操作之上的

例如:

redis > GET message

"hello world"
從零單排學Redis【白銀】

1.2鍵的過期時間

Redis是基於記憶體,記憶體是比較昂貴的,容量肯定比不上硬碟的。就我們現在一臺普通的機子,可能就8G記憶體,但硬碟隨隨便便都1T了。

因為我們的記憶體是有限的。所以我們會幹掉不常用的資料,保留常用的資料。這就需要我們設定一下鍵的過期(生存)時間了。

  • 設定鍵的生存時間可以透過EXPIRE或者PEXPIRE命令。

  • 設定鍵的過期時間可以透過EXPIREAT或者PEXPIREAT命令。

其實EXPIREPEXPIREEXPIREAT這三個命令都是透過PEXPIREAT命令來實現的。

我們在redisDb結構體中還發現了dict *expires;屬性,存放所有鍵過期的時間。

舉個例子基本就可以理解了:

redis > PEXPIREAT message 1391234400000
(integer) 1

設定了message鍵的過期時間為1391234400000

從零單排學Redis【白銀】

既然有設定過期(生存)時間的命令,那肯定也有移除過期時間,檢視剩餘生存時間的命令了:

  • PERSIST(移除過期時間)

  • TTL(Time To Live)返回剩餘生存時間,以秒為單位

  • PTTL以毫秒為單位返回鍵的剩餘生存時間

1.2.1過期策略

上面我們已經能夠了解到:過期鍵是儲存在雜湊表中了。那這些過期鍵到了過期的時間,就會立馬被刪除掉嗎??

要回答上面的問題,需要我們瞭解一下刪除策略的知識,刪除策略可分為三種

  • 定時刪除(對記憶體友好,對CPU不友好)

    • 到時間點上就把所有過期的鍵刪除了。

  • 惰性刪除(對CPU極度友好,對記憶體極度不友好)

    • 每次從鍵空間取鍵的時候,判斷一下該鍵是否過期了,如果過期了就刪除。

  • 定期刪除(折中)

    • 每隔一段時間去刪除過期鍵,限制刪除的執行時長和頻率。

Redis採用的是惰性刪除+定期刪除兩種策略,所以說,在Redis裡邊如果過期鍵到了過期的時間了,未必被立馬刪除的!

1.2.2記憶體淘汰機制

如果定期刪除漏掉了很多過期key,也沒及時去查(沒走惰性刪除),大量過期key堆積在記憶體裡,導致redis記憶體塊耗盡了,咋整?

我們可以設定記憶體最大使用量,當記憶體使用量超出時,會施行資料淘汰策略

Redis的記憶體淘汰機制有以下幾種:

從零單排學Redis【白銀】

一般場景:

使用 Redis 快取資料時,為了提高快取命中率,需要保證快取資料都是熱點資料。可以將記憶體最大使用量設定為熱點資料佔用的記憶體量,然後啟用allkeys-lru淘汰策略,將最近最少使用的資料淘汰

二、Redis持久化

Redis是基於記憶體的,如果不想辦法將資料儲存在硬碟上,一旦Redis重啟(退出/故障),記憶體的資料將會全部丟失。

  • 我們肯定不想Redis裡頭的資料由於某些故障全部丟失(導致所有請求都走MySQL),即便發生了故障也希望可以將Redis原有的資料恢復過來,這就是持久化的作用。

Redis提供了兩種不同的持久化方法來講資料儲存到硬碟裡邊:

  • RDB(基於快照),將某一時刻的所有資料儲存到一個RDB檔案中。

  • AOF(append-only-file),當Redis伺服器執行寫命令的時候,將執行的寫命令儲存到AOF檔案中。

2.1RDB(快照持久化)

RDB持久化可以手動執行,也可以根據伺服器配置定期執行。RDB持久化所生成的RDB檔案是一個經過壓縮的二進位制檔案,Redis可以透過這個檔案還原資料庫的資料。

從零單排學Redis【白銀】

有兩個命令可以生成RDB檔案:

  • SAVE阻塞Redis伺服器程式,伺服器不能接收任何請求,直到RDB檔案建立完畢為止。

  • BGSAVE建立出一個子程式,由子程式來負責建立RDB檔案,伺服器程式可以繼續接收請求。

Redis伺服器在啟動的時候,如果發現有RDB檔案,就會自動載入RDB檔案(不需要人工干預)

  • 伺服器在載入RDB檔案期間,會處於阻塞狀態,直到載入工作完成。

除了手動呼叫SAVE或者BGSAVE命令生成RDB檔案之外,我們可以使用配置的方式來定期執行:

在預設的配置下,如果以下的條件被觸發,就會執行BGSAVE命令

    save 900 1              #在900秒(15分鐘)之後,至少有1個key發生變化,
    save 300 10            #在300秒(5分鐘)之後,至少有10個key發生變化
    save 60 10000        #在60秒(1分鐘)之後,至少有10000個key發生變化

原理大概就是這樣子的(結合上面的配置來看):

struct redisServer{
    // 修改計數器
    long long dirty;

    // 上一次執行儲存的時間
    time_t lastsave;

    // 引數的配置
    struct saveparam *saveparams;
};

遍歷引數陣列,判斷修改次數和時間是否符合,如果符合則呼叫besave()來生成RDB檔案

從零單排學Redis【白銀】

總結:透過手動呼叫SAVE或者BGSAVE命令或者配置條件觸發,將資料庫某一時刻的資料快照,生成RDB檔案實現持久化。

2.2AOF(檔案追加)

上面已經介紹了RDB持久化是透過將某一時刻資料庫的資料“快照”來實現的,下面我們來看看AOF是怎麼實現的。

  • AOF是透過儲存Redis伺服器所執行的寫命令來記錄資料庫的資料的。

從零單排學Redis【白銀】

比如說我們對空白的資料庫執行以下寫命令:

redis> SET meg "hello"
OK

redis> SADD fruits "apple" "banana" "cherry"
(integer) 3

redis> RPUSH numbers 128 256 512
(integer) 3 

Redis會產生以下內容的AOF檔案:

從零單排學Redis【白銀】

這些都是以Redis的命令請求協議格式儲存的。Redis協議規範(RESP)參考資料:

  • https://www.cnblogs.com/tommy-huang/p/6051577.html

AOF持久化功能的實現可以分為3個步驟:

  • 命令追加:命令寫入aof_buf緩衝區

  • 檔案寫入:呼叫flushAppendOnlyFile函式,考慮是否要將aof_buf緩衝區寫入AOF檔案中

  • 檔案同步:考慮是否將記憶體緩衝區的資料真正寫入到硬碟

從零單排學Redis【白銀】

flushAppendOnlyFile函式的行為由伺服器配置的appendfsyn選項來決定的:

    appendfsync always     # 每次有資料修改發生時都會寫入AOF檔案。
    appendfsync everysec   # 每秒鐘同步一次,該策略為AOF的預設策略。
    appendfsync no         # 從不同步。高效但是資料不會被持久化。

從字面上應該就更好理解了,這裡我就不細說了…

下面來看一下AOF是如何載入與資料還原的:

  • 建立一個偽客戶端(本地)來執行AOF的命令,直到AOF命令被全部執行完畢。

從零單排學Redis【白銀】

2.2.1AOF重寫

從前面的示例看出,我們寫了三條命令,AOF檔案就儲存了三條命令。如果我們的命令是這樣子的:

redis > RPUSH list "Java" "3y"
(integer)2

redis > RPUSH list "Java3y"
integer(3)

redis > RPUSH list "yyy"
integer(4)

同樣地,AOF也會儲存3條命令。我們會發現一個問題:上面的命令是可以合併起來成為1條命令的,並不需要3條。這樣就可以讓AOF檔案的體積變得更小

AOF重寫由Redis自行觸發(引數配置),也可以用BGREWRITEAOF命令手動觸發重寫操作。

  • 要值得說明的是:AOF重寫不需要對現有的AOF檔案進行任何的讀取、分析。AOF重寫是透過讀取伺服器當前資料庫的資料來實現的

比如說現在有一個Redis資料庫的資料如下:

從零單排學Redis【白銀】

新的AOF檔案的命令如下,沒有一條是多餘的

從零單排學Redis【白銀】

2.2.2AOF後臺重寫

Redis將AOF重寫程式放到子程式裡執行(BGREWRITEAOF命令),像BGSAVE命令一樣fork出一個子程式來完成重寫AOF的操作,從而不會影響到主程式。

AOF後臺重寫是不會阻塞主程式接收請求的,新的寫命令請求可能會導致當前資料庫和重寫後的AOF檔案的資料不一致

為了解決資料不一致的問題,Redis伺服器設定了一個AOF重寫緩衝區,這個快取區會在伺服器建立出子程式之後使用

從零單排學Redis【白銀】

2.3RDB和AOF對過期鍵的策略

RDB持久化對過期鍵的策略:

  • 執行SAVE或者BGSAVE命令建立出的RDB檔案,程式會對資料庫中的過期鍵檢查,已過期的鍵不會儲存在RDB檔案中

  • 載入RDB檔案時,程式同樣會對RDB檔案中的鍵進行檢查,過期的鍵會被忽略

RDB持久化對過期鍵的策略:

  • 如果資料庫的鍵已過期,但還沒被惰性/定期刪除,AOF檔案不會因為這個過期鍵產生任何影響(也就說會保留),當過期的鍵被刪除了以後,會追加一條DEL命令來顯示記錄該鍵被刪除了

  • 重寫AOF檔案時,程式會對RDB檔案中的鍵進行檢查,過期的鍵會被忽略

複製模式:

  • 主伺服器來控制從伺服器統一刪除過期鍵(保證主從伺服器資料的一致性)

2.4RDB和AOF用哪個?

RDB和AOF並不互斥,它倆可以同時使用

  • RDB的優點:載入時恢復資料快、檔案體積小。

  • RDB的缺點:會一定程度上丟失資料(因為系統一旦在定時持久化之前出現當機現象,此前沒有來得及寫入磁碟的資料都將丟失。)

  • AOF的優點:丟失資料少(預設配置只丟失一秒的資料)。

  • AOF的缺點:恢復資料相對較慢,檔案體積大

如果Redis伺服器同時開啟了RDB和AOF持久化,伺服器會優先使用AOF檔案來還原資料(因為AOF更新頻率比RDB更新頻率要高,還原的資料更完善)

可能涉及到RDB和AOF的配置:

redis持久化,兩種方式
1、rdb快照方式
2、aof日誌方式

----------rdb快照------------
save 900 1
save 300 10
save 60 10000

stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dbfilename dump.rdb
dir /var/rdb/

-----------Aof的配置-----------
appendonly no # 是否開啟 aof日誌功能

appendfsync always #每一個命令都立即同步到aof,安全速度慢
appendfsync everysec
appendfsync no 寫入工作交給作業系統,由作業系統判斷緩衝區大小,統一寫入到aof  同步頻率低,速度快


no-appendfsync-on-rewrite yes 正在匯出rdb快照的時候不要寫aof
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb 


./bin/redis-benchmark -n 20000

官網文件:

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69900354/viewspace-2222518/,如需轉載,請註明出處,否則將追究法律責任。

相關文章