從零單排學Redis【白銀】

Java3y發表於2018-12-03

前言

只有光頭才能變強

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

這篇主要講的內容有:

  • Redis伺服器的資料庫
  • Redis對過期鍵的處理
  • Redis持久化策略(RDB和AOF)

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

一、Redis伺服器中的資料庫

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

MySQL建立幾個資料庫

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

通過SELECT命令可以切換到0~15的資料庫

上面的命令我們也可以發現:當切換到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服務端時的示例圖

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 > GET message

"hello world"

複製程式碼

查詢鍵的示意圖

1.2鍵的過期時間

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

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

  • 設定鍵的生存時間可以通過EXPIRE或者PEXPIRE命令。
  • 設定鍵的過期時間可以通過EXPIREAT或者PEXPIREAT命令。

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

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

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


redis > PEXPIREAT message 1391234400000
(integer) 1

複製程式碼

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

新增一個過期時間的鍵

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

  • PERSIST(移除過期時間)
  • TTL(Time To Live)返回剩餘生存時間,以秒為單位
  • PTTL以毫秒為單位返回鍵的剩餘生存時間

1.2.1過期策略

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

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

  • 定時刪除(對記憶體友好,對CPU不友好)
    • 到時間點上就把所有過期的鍵刪除了。
  • 惰性刪除(對CPU極度友好,對記憶體極度不友好)
    • 每次從鍵空間取鍵的時候,判斷一下該鍵是否過期了,如果過期了就刪除。
  • 定期刪除(折中)
    • 每隔一段時間去刪除過期鍵,限制刪除的執行時長和頻率。

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

1.2.2記憶體淘汰機制

如果定期刪除漏掉了很多過期key,也沒及時去查(沒走惰性刪除),大量過期key堆積在記憶體裡,導致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可以通過這個檔案還原資料庫的資料。

RDB檔案還原資料

有兩個命令可以生成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伺服器所執行的寫命令來記錄資料庫的資料的。

AOF原理圖

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


redis> SET meg "hello"
OK

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

redis> RPUSH numbers 128 256 512
(integer) 3 

複製程式碼

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

AOF檔案

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

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

  • 命令追加:命令寫入aof_buf緩衝區
  • 檔案寫入:呼叫flushAppendOnlyFile函式,考慮是否要將aof_buf緩衝區寫入AOF檔案中
  • 檔案同步:考慮是否將記憶體緩衝區的資料真正寫入到硬碟

AOF持久化步驟

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


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

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

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

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

redis偽客戶端載入AOF檔案

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檔案的命令如下,沒有一條是多餘的

AOF重寫後的命令

2.2.2AOF後臺重寫

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

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

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

AOF後臺重寫過程

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
複製程式碼

官網文件:

三、最後

現在臨近雙十一買阿里雲伺服器就特別省錢!之前我買學生機也要9.8塊錢一個月,現在最低價只需要8.3一個月!

無論是Nginx/Elasticsearch/Redis這些技術都是在Linux下完美執行的,如果還是程式設計師新手,買一個學習Linux基礎命令,學習搭建環境也是不錯的選擇。

如果有要買伺服器的同學可通過我的連結直接享受最低價m.aliyun.com/act/team111…


如果大家有更好的理解方式或者文章有錯誤的地方還請大家不吝在評論區留言,大家互相學習交流~~~

參考資料:

  • 《Redis設計與實現》
  • 《Redis實戰》

一個堅持原創的Java技術公眾號:Java3y,歡迎大家關注

3y所有的原創文章:

相關文章