從零單排學Redis【白銀】
前言
只有光頭才能變強
今天繼續來學習Redis,上一篇從零單排學Redis【青銅】已經將Redis常用的數據結構過了一遍了。如果還沒看的同學可以先去看一遍再回來~
這篇主要講的內容有:
- Redis服務器的數據庫
- Redis對過期鍵的處理
- Redis持久化策略(RDB和AOF)
本文力求簡單講清每個知識點,希望大家看完能有所收獲
一、Redis服務器中的數據庫
我們應該都用過MySQL,MySQL我們可以在里邊創建好幾個庫:
同樣地,Redis服務器中也有數據庫這么一個概念。如果不指定具體的數量,默認會有16個數據庫。
上面的命令我們也可以發現:當切換到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中對每個數據庫用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
命令。
其實EXPIRE
、PEXPIRE
、EXPIREAT
這三個命令都是通過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文件:
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文件
總結:通過手動調用SAVE
或者BGSAVE
命令或者配置條件觸發,將數據庫某一時刻的數據快照,生成RDB文件實現持久化。
2.2AOF(文件追加)
上面已經介紹了RDB持久化是通過將某一時刻數據庫的數據“快照”來實現的,下面我們來看看AOF是怎么實現的。
- AOF是通過保存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協議規范(RESP)參考資料:
AOF持久化功能的實現可以分為3個步驟:
- 命令追加:命令寫入aof_buf緩沖區
- 文件寫入:調用flushAppendOnlyFile函數,考慮是否要將aof_buf緩沖區寫入AOF文件中
- 文件同步:考慮是否將內存緩沖區的數據真正寫入到硬盤
flushAppendOnlyFile函數的行為由服務器配置的appendfsyn選項來決定的:
appendfsync always # 每次有數據修改發生時都會寫入AOF文件。
appendfsync everysec # 每秒鐘同步一次,該策略為AOF的默認策略。
appendfsync no # 從不同步。高效但是數據不會被持久化。
從字面上應該就更好理解了,這里我就不細說了…
下面來看一下AOF是如何載入與數據還原的:
- 創建一個偽客戶端(本地)來執行AOF的命令,直到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數據庫的數據如下:
新的AOF文件的命令如下,沒有一條是多余的!
2.2.2AOF后臺重寫
Redis將AOF重寫程序放到子進程里執行(BGREWRITEAOF
命令),像BGSAVE
命令一樣fork出一個子進程來完成重寫AOF的操作,從而不會影響到主進程。
AOF后臺重寫是不會阻塞主進程接收請求的,新的寫命令請求可能會導致當前數據庫和重寫后的AOF文件的數據不一致!
為了解決數據不一致的問題,Redis服務器設置了一個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
官網文檔:
三、最后
如果大家有更好的理解方式或者文章有錯誤的地方還請大家不吝在評論區留言,大家互相學習交流~~~
參考資料:
- 《Redis設計與實現》
- 《Redis實戰》
3y所有的原創文章:
- 文章的目錄導航(腦圖+海量視頻資源):https://github.com/ZhongFuCheng3y/3y
共同學習,寫下你的評論
評論加載中...
相關文章推薦