從零單排學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)參考資料:
https://www.cnblogs.com/tommy-huang/p/6051577.html
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
官網文件:
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69900354/viewspace-2222518/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- rodert單排學習redis進階【白銀一】Redis
- 從零單排學Redis【黃金】Redis
- 從零單排學Redis【鉑金二】Redis
- 從零單排學Redis【鉑金一】Redis
- 【3y】從零單排學Redis【青銅】Redis
- Mysql從零單排-1MySql
- 從零開始單排學設計模式「策略模式」黑鐵 II設計模式
- 【LeetCode從零單排】No38.CountAndSayLeetCode
- 從零開始單排學設計模式「裝飾模式」黑鐵 I設計模式
- 「從零單排canal 07」 parser模組原始碼解析原始碼
- 「從零單排canal 05」 server模組原始碼解析Server原始碼
- 【LeetCode從零單排】No.7 Reverse IntegerLeetCode
- 【LeetCode從零單排】No20.ValidParenthesesLeetCode
- 【LeetCode從零單排】No19.RemoveNthNodeFromEndofListLeetCodeREM
- 【LeetCode從零單排】No21.MergeTwoSortedListsLeetCode
- 【LeetCode從零單排】No27.Remove ElementLeetCodeREM
- 【LeetCode從零單排】No28 Implement strStr()LeetCode
- 【LeetCode從零單排】No22.Generate ParenthesesLeetCode
- 從零開始單排學設計模式「UML類圖」定級賽設計模式
- 從零開始單排學設計模式「簡單工廠設計模式」黑鐵 III設計模式
- 「從零單排canal 06」 instance模組原始碼解析原始碼
- 「從零單排canal 03」 canal原始碼分析大綱原始碼
- SpringBoot從零單排 ------初級入門篇Spring Boot
- 【LeetCode從零單排】No58.Length of Last WordLeetCodeAST
- 【LeetCode從零單排】No67.AddBinaryLeetCode
- 【LeetCode從零單排】No70.ClimbingStairsLeetCodeAI
- 【LeetCode從零單排】No.9 Palindrome NumberLeetCode
- 【LeetCode從零單排】No14.LongestCommonPrefixLeetCode
- 【LeetCode從零單排】No36 Valid SudokuLeetCode
- 【LeetCode從零單排】No221.Maximal SquareLeetCode
- 「從零單排canal 04」 啟動模組deployer原始碼解析原始碼
- 從零單排,使用 Netty 構建 IM 聊天室~Netty
- Laravel 從零單排系列教程 01 :Homestead 環境搭建Laravel
- 三分鐘從零單排js靜態檢查JS
- 【LeetCode從零單排】No15 3SumLeetCode
- 【LeetCode從零單排】No189 .Rotate ArrayLeetCode
- 【LeetCode從零單排】No88.Merge Sorted ArrayLeetCode
- 「從零單排HBase 10」HBase叢集多租戶實踐