掃二維碼與項目經(jīng)理溝通
我們在微信上24小時期待你的聲音
解答本文疑問/技術(shù)咨詢/運營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流
眾所周知,Redis = Remote Dictionary Server,即遠程字典服務(wù)。

是一個開源的使用ANSI C語言編寫、支持網(wǎng)絡(luò)、可基于內(nèi)存亦可持久化的日志型、Key-Value數(shù)據(jù)庫,并提供多種語言的API。
redis 會將 key:myLove value:Mia
包裝成一個 dictEntry 對象、一個 redisObject 對象,如下圖所示:
?dictEntry:眾所周知,Redis是Key-Value數(shù)據(jù)庫,因此對每個鍵值對都會有一個dictEntry,里面存儲了指向Key和Value的指針;next指向下一個dictEntry,與本Key-Value無關(guān)。
?Key:圖中右上角可見,Key("myLove")并不是直接以字符串存儲,而是存儲在SDS結(jié)構(gòu)中。
?redisObject:Value("Mia")既不是直接以字符串存儲,也不是像Key一樣直接存儲在SDS中,而是存儲在redisObject中。實際上,不論Value是5種類型的哪一種,都是通過redisObject來存儲的;而redisObject中的type字段指明了Value對象的類型,ptr字段則指向?qū)ο笏诘牡刂?。不過可以看出,字符串對象雖然經(jīng)過了redisObject的包裝,但仍然需要通過SDS存儲。
1.1.1、dictEntry
redis內(nèi)部整體的存儲結(jié)構(gòu)是一個大的hashmap,內(nèi)部是數(shù)組實現(xiàn)的hash,key沖突通過掛鏈表去實現(xiàn),每個dictEntry為一個key/value對象,value為定義的redisObject。
結(jié)構(gòu)圖如下:
dictEntry是存儲key->value的地方,再讓我們看一下dictEntry結(jié)構(gòu)體
/*
* 字典
*/
typedef struct dictEntry {
// 鍵
void *key;
// 值
union {
// 指向具體redisObject
void *val;
//
uint64_t u64;
int64_t s64;
} v;
// 指向下個哈希表節(jié)點,形成鏈表
struct dictEntry *next;
} dictEntry;1.1.2、對象封裝 redisObject
我們接著再往下看redisObject究竟是什么結(jié)構(gòu)的
/*
* Redis 對象
*/
typedef struct redisObject {
// 類型 4bits
unsigned type:4;
// 編碼方式 4bits
unsigned encoding:4;
// LRU 時間(相對于 server.lruclock) 24bits
unsigned lru:22;
// 引用計數(shù) Redis里面的數(shù)據(jù)可以通過引用計數(shù)進行共享 32bits
int refcount;
// 指向?qū)ο蟮闹?64-bit
void *ptr;
} robj;*ptr指向具體的數(shù)據(jù)結(jié)構(gòu)的地址;type表示該對象的類型,即String,List,Hash,Set,Zset中的一個,但為了提高存儲效率與程序執(zhí)行效率,每種對象的底層數(shù)據(jù)結(jié)構(gòu)實現(xiàn)都可能不止一種,encoding 表示對象底層所使用的編碼。
redis對象底層的八種數(shù)據(jù)結(jié)構(gòu):
REDIS_ENCODING_INT(long 類型的整數(shù))
REDIS_ENCODING_EMBSTR embstr (編碼的簡單動態(tài)字符串)
REDIS_ENCODING_RAW (簡單動態(tài)字符串)
REDIS_ENCODING_HT (字典)
REDIS_ENCODING_LINKEDLIST (雙端鏈表)
REDIS_ENCODING_ZIPLIST (壓縮列表)
REDIS_ENCODING_INTSET (整數(shù)集合)
REDIS_ENCODING_SKIPLIST (跳躍表和字典)查看 redisObject 詳細信息 :
# 查看 key對應(yīng)value的 redisObject 類型
type key
type myLove
# 查看 key對應(yīng)value的redisObject 詳細信息
debug object key
debug object myLovevalue 為 string 、int 類型是 redisObject 中的 type、encoding 不同表現(xiàn)形式
Value 為 string 類型時:
Value 為 int類型時:
以上兩種不同 value 類型,type 相同,encoding 不同
1.2.1、rdb 文件寫入
1.2.2、aof 緩存寫入 文件保存
默認情況下 沒有開啟 AOF ( append only file)
開啟 AOF 持久化后,每執(zhí)行一條會更改 redis 數(shù)據(jù)的命令,redis就會將寫入、修改、刪除命令寫入到硬盤中的 AOF 文件(當然并不是立即寫入文件,而是立即寫入aof緩存中,再根據(jù)aof配置的數(shù)據(jù)持久化條件進行寫入),這一過程顯然會降低 redis 的性能,但大部分情況下這個影響是能夠接受的,
另外使用快的硬盤可以提高 AOF 的性能。
配置 redis.conf
# 可以通過修改redis.conf配置文件中的appendonly參數(shù)開啟
appendonly yes
# AOF文件的保存位置和RDB文件的位置相同,都是通過dir參數(shù)設(shè)置的。 dir ./
# 默認的文件名是appendonly.aof,可以通過appendfilename參數(shù)修改 appendfilename appendonly.aof
Redis 將所有對數(shù)據(jù)庫進行過寫入的命令(及其參數(shù))記錄到 AOF 文件, 以此達到記錄數(shù)據(jù)庫狀態(tài)的 目的, 為了方便起見, 我們稱呼這種記錄過程為同步。
命令傳播:Redis 將執(zhí)行完的命令、命令的參數(shù)、命令的參數(shù)個數(shù)等信息發(fā)送到 AOF 程序中。 緩存追 加:AOF 程序根據(jù)接收到的命令數(shù)據(jù),將命令轉(zhuǎn)換為網(wǎng)絡(luò)通訊協(xié)議 RESP 的格式,然后將協(xié)議內(nèi)容追加到服務(wù)器的 AOF 緩存中。 文件寫入和保存: AOF 緩存中的內(nèi)容被寫入到 AOF 文件末尾,如果設(shè)定的 AOF 保存條件被滿足的話, fsync 函數(shù)或者 fdatasync 函數(shù)會被調(diào)用,將寫入的內(nèi)容真正地保存到磁盤中。
當一個 Redis 客戶端需要執(zhí)行命令時, 它通過網(wǎng)絡(luò)連接, 將協(xié)議文本發(fā)送給 Redis 服務(wù)器。服務(wù)器在 接到客戶端的請求之后, 它會根據(jù)協(xié)議文本的內(nèi)容, 選擇適當?shù)拿詈瘮?shù), 并將各個參數(shù)從字符串文 本轉(zhuǎn)換為 Redis 字符串對象( StringObject )。每當命令函數(shù)成功執(zhí)行之后, 命令參數(shù)都會被傳播到 AOF 程序。
當命令被傳播到 AOF 程序之后, 程序會根據(jù)命令以及命令的參數(shù), 將命令從字符串對象轉(zhuǎn)換回原來的 協(xié)議文本。協(xié)議文本生成之后, 它會被追加到 redis.h/redisServer 結(jié)構(gòu)的 aof_buf 末尾。
redisServer 結(jié)構(gòu)維持著 Redis 服務(wù)器的狀態(tài), aof_buf 域則保存著所有等待寫入到 AOF 文件的協(xié) 議文本。
Redis客戶端使用RESP(Redis的序列化協(xié)議)協(xié)議與Redis的服務(wù)器端進行通信。 雖然該協(xié)議是專門為 Redis設(shè)計的,但是該協(xié)議也可以用于其他 客戶端-服務(wù)器 (Client-Server)軟件項目。
可以通過特殊符號來區(qū)分出數(shù)據(jù)的類型:
單行回復(fù):以+號開頭。
錯誤回復(fù):以-號開頭。
整數(shù)回復(fù):以:號開頭。
批量回復(fù):以$號開頭。
多條批量回復(fù):以*號開頭。
1)間隔符號,在Linux下是\r\n,在Windows下是\n
2)簡單字符串 Simple Strings, 以 "+"加號 開頭
3)錯誤 Errors, 以"-"減號 開頭
4)整數(shù)型 Integer, 以 ":" 冒號開頭
5)大字符串類型 Bulk Strings, 以 "$"美元符號開頭,長度限制512M 6、數(shù)組類型 Arrays,以 "*"星號開頭 用SET命令來舉例說明RESP協(xié)議的格式。
實際發(fā)送的請求數(shù)據(jù):
redis> SET myLove "Mia"
"OK"
*3\r\n$3\r\nSET\r\n$6\r\nmyLove\r\n$3\r\nMia\r\n
*3
$3
SET
$5
mykey
$5
Hello實際收到的響應(yīng)數(shù)據(jù):
+OK\r\n文件寫入和保存:
每當服務(wù)器常規(guī)任務(wù)函數(shù)被執(zhí)行、 或者事件處理器被執(zhí)行時, aof.c/flushAppendOnlyFile 函數(shù)都會被 調(diào)用, 這個函數(shù)執(zhí)行以下兩個工作:
WRITE:根據(jù)條件,將 aof_buf 中的緩存寫入到 AOF 文件。 SAVE:根據(jù)條件,調(diào)用 fsync 或 fdatasync 函數(shù),將 AOF 文件保存到磁盤中。
從圖可知,在redis的數(shù)據(jù)庫中,redisDb結(jié)構(gòu)中的expires字典中保存了數(shù)據(jù)庫中所有鍵的過期時間,所以叫過期字典。
過期字典的key是一個指針,指向鍵空間的某個鍵對象(就是數(shù)據(jù)庫鍵)
過期字典的value是一個long類型的整數(shù),這個整數(shù)保存了鍵所指向的數(shù)據(jù)庫鍵的過期時間,一個毫秒精度的UNIX時間戳
過期鍵判定
通過過期字典,我們可以得到一個key是否過期:
判斷key是否存在于過期字典中
通過過期字典拿到key的過期時間,判斷當前UNIX時間戳是否大于key時間
過期key如何刪除
過期鍵的惰性刪除策略由db.c/expireIfNeeded函數(shù)實現(xiàn),所有讀寫數(shù)據(jù)庫的Redis命令在執(zhí)行之前都會調(diào)用expireIfNeeded函數(shù)對輸入鍵進行檢查:
如果輸入鍵已經(jīng)過期,那么expireIfNeeded函數(shù)將輸入鍵從數(shù)據(jù)庫中刪除。
如果輸入鍵未過期,那么expireIfNeeded函數(shù)不做動作。
expireIfNeeded函數(shù)就像一個過濾器,它可以在命令真正執(zhí)行之前,過濾掉過期的輸入鍵,從而避免命令接觸到過期鍵。
另外,因為每個被訪問的鍵都可能因為過期而被expireIfNeeded函數(shù)刪除,所以每個命令的實現(xiàn)函數(shù)都必須能同時處理鍵存在以及鍵不存在這兩種情況:
當鍵存在時,命令按照鍵存在的情況執(zhí)行。
當鍵不存在或者鍵因為過期而被expireIfNeeded函數(shù)刪除時,命令按照鍵不存在的情況執(zhí)行。
過期鍵的定期刪除策略由redis.c/activeExpireCycle函數(shù)實現(xiàn),每當Redis的服務(wù)器周期性操作redis.c/serverCron函數(shù)執(zhí)行時,activeExpireCycle函數(shù)就會被調(diào)用,它在規(guī)定的時間內(nèi),分多次遍歷服務(wù)器中的各個數(shù)據(jù)庫,從數(shù)據(jù)庫的expires字典中隨機檢查一部分鍵的過期時間,并刪除其中的過期鍵。
不好意思,哥們的愛無法刪除!

我們在微信上24小時期待你的聲音
解答本文疑問/技術(shù)咨詢/運營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流