《Redis設(shè)計(jì)與實(shí)現(xiàn)》筆記(更新中)

1.Redis特性

1)速度快:數(shù)據(jù)存放在內(nèi)存上、基于C語言實(shí)現(xiàn)、單線程架構(gòu)預(yù)防多線程競爭問題;
2)基于鍵值對的數(shù)據(jù)結(jié)構(gòu),支持5中數(shù)據(jù)結(jié)構(gòu):字符串(string)、列表(list)、哈希(hash)、集合(set)、有序集合,同時(shí)在字符串基礎(chǔ)上演變出位圖(Bitmaps)和HyperLogLog;
3)豐富的功能:鍵過期、發(fā)布訂閱、Lua腳本、事務(wù)、流水線;
4)持久化:RDB和AOF兩種策略將內(nèi)存數(shù)據(jù)保存到硬盤;
5)主從復(fù)制

2.基礎(chǔ)指令

  • keys *:查看所有鍵,需要遍歷數(shù)據(jù)庫O(n);
  • dbsize:查看數(shù)據(jù)庫鍵總數(shù),直接獲取內(nèi)置的鍵總數(shù)變量O(1);
  • exists key:查看鍵是否存在,存在返回1,否則0;
  • del key [key ..]:刪除鍵,返回刪除成功的個(gè)數(shù);
  • expire key seconds:對鍵添加過期時(shí)間,超期自動刪除鍵;
  • ttl key:返回鍵的剩余過期時(shí)間v,若v大于0,則代表鍵的過期時(shí)間,若v等于-1,則代表沒有設(shè)置過期時(shí)間,若等于-2,則表示鍵不存在;
  • type key:返回鍵的數(shù)據(jù)結(jié)構(gòu)類型,若鍵不存在返回none;
  • object encoding:查詢內(nèi)部編碼;

3.單線程架構(gòu)

Redis使用單線程架構(gòu)和I/O多路復(fù)用模型來實(shí)現(xiàn)高性能的內(nèi)存數(shù)據(jù)庫。為什么單線程還能這么快?
   a)純內(nèi)存訪問;
   b)非阻塞I/O,使用epoll作為I/O多路復(fù)用技術(shù)的實(shí)現(xiàn),再加上Redis自身的時(shí)間處理模型將epoll中的連接、讀寫、關(guān)閉都轉(zhuǎn)換為事件;
   c)單線程避免了線程切換和競態(tài)產(chǎn)生的消耗;

4.數(shù)據(jù)結(jié)構(gòu)的運(yùn)用及其實(shí)現(xiàn)

請參考:"Redis5種數(shù)據(jù)結(jié)構(gòu)的運(yùn)用及實(shí)現(xiàn)"(http://www.itdecent.cn/p/7d207d057d46 )

5.對象

? ? ? ?基于上述數(shù)據(jù)結(jié)構(gòu)構(gòu)建了一個(gè)對象系統(tǒng),字符串對象、列表對象、哈希對象、集合對象和有序集合對象。使用基于引用計(jì)數(shù)的內(nèi)存回收機(jī)制(不使用自動釋放)和對象共享機(jī)制(多庫鍵共享對象)。
? ? ? ?Redis中每個(gè)對象都由一個(gè)redisObject結(jié)構(gòu)表示,有三個(gè)屬性和保存數(shù)據(jù)有關(guān),分別是:type屬性、encoding屬性和ptr屬性,一個(gè)與內(nèi)存回收有關(guān)的屬性refcount,一個(gè)lru屬性記錄對象最后一次被命令程序訪問的時(shí)間;

  • 類型(type):字符串對象、列表對象、哈希對象、集合對象和有序集合對象的其中一個(gè),鍵總是字符串對象;
  • 編碼(encoding):對象的ptr指針指向?qū)ο蟮牡讓訉?shí)現(xiàn)數(shù)據(jù)結(jié)構(gòu),由encoding屬性決定,主要包括int、embstr、raw、ht、linkedlist、ziplist、intset、skiplist;
  • 引用計(jì)數(shù)(refcount):由于C不具備自動內(nèi)存回收功能,Redis自己構(gòu)建了一個(gè)引用計(jì)數(shù)技術(shù)來實(shí)現(xiàn)內(nèi)存回收機(jī)制,創(chuàng)建對象時(shí),引用計(jì)數(shù)被初始化為1,當(dāng)對象被一個(gè)新程序使用時(shí),計(jì)數(shù)值加以,不再使用時(shí)減一,當(dāng)計(jì)數(shù)值變?yōu)?時(shí),對象所占用的內(nèi)存會被釋放;同時(shí)引用計(jì)數(shù)屬性還有對象共享的作用,Redis在初始化時(shí)創(chuàng)建了一萬個(gè)字符串對象,包含了從0到9999的所有整數(shù),當(dāng)需要使用這些對象時(shí),服務(wù)器會直接使用這些共享對象,并且引用計(jì)數(shù)加一,而不是新創(chuàng)建對象;

字符串對象
編碼可以是int、embstr、raw:

  • int:如果一個(gè)字符串對象保存的是可由long表示的整數(shù)值,會將該整數(shù)值保存在ptr屬性里面,并將編碼設(shè)置為int。若修改后不再是整數(shù)值而是一個(gè)字符串,則轉(zhuǎn)為raw
  • raw:如果字符串對象保存的是一個(gè)長度>32字節(jié)的字符串值,使用SDS保存該字符串,并將編碼設(shè)置為raw;
  • embstr(只讀):如果字符串對象保存的是一個(gè)長度<=32字節(jié)的字符串值,使用embstr編碼方式來保存;embstr專門用于保存短字符串的優(yōu)化編碼方式,raw和embstr都使用redisObject結(jié)構(gòu)和sdshdr結(jié)構(gòu)來表示字符串對象,但raw會調(diào)用兩次內(nèi)存分配函數(shù)來分別創(chuàng)建redisObject結(jié)構(gòu)和sdshdr結(jié)構(gòu),而embstr則通過調(diào)用一次內(nèi)存分配函數(shù)來分配一塊連續(xù)的空間,因此在創(chuàng)建和釋放時(shí)embstr都只需調(diào)用一次內(nèi)存分配/釋放,且embstr保存在連續(xù)的內(nèi)存中,更好的利用了緩存。embstr字符串對象無法修改,沒有任何修改程序,對他的修改實(shí)際上是轉(zhuǎn)成raw后進(jìn)行修改;

列表對象
編碼可以是ziplist和linkedlist,當(dāng)列表對象保存的元素個(gè)數(shù)不小于512個(gè)或者有元素的長度大于64字節(jié)則使用linkedlist(上限可改):

  • ziplist:使用壓縮列表作為底層實(shí)現(xiàn),每個(gè)壓縮列表節(jié)點(diǎn)節(jié)點(diǎn)保存一個(gè)列表元素;
  • linkedlist:使用雙端鏈表作為底層實(shí)現(xiàn),每個(gè)雙端鏈表節(jié)點(diǎn)保存了一個(gè)字符串對象;

哈希對象
編碼可以是ziplist、hashtable,當(dāng)鍵值字符串長度不小于64字節(jié)或者鍵值對數(shù)量不小于512個(gè)則使用hashtable編碼。

  • ziplist編碼的哈希對象使用壓縮列表作為底層實(shí)現(xiàn),當(dāng)有新的鍵值加入時(shí),先在表尾推入鍵,然后推入值,鍵值總是緊挨在一起;
  • hashtable編碼的哈希對象使用字典作為底層實(shí)現(xiàn),字典的鍵值均為字符串對象;

集合對象
編碼可以是intset、hashtable,當(dāng)集合對象保存的都是整數(shù)值,且集合元素不超過512個(gè)時(shí),使用intset編碼:

  • intset編碼的集合對象使用整數(shù)集合作為底層實(shí)現(xiàn),所有元素都被保存在整數(shù)集合里面;
  • hashtable編碼的集合對象使用字典作為底層實(shí)現(xiàn),每個(gè)鍵都是一個(gè)字符串對象,代表一個(gè)集合元素,而值則全部被設(shè)置為NULL;

有序集合對象
編碼可以是ziplist、skiplist,若元素?cái)?shù)量小于128個(gè)并且所有元素長度都小于64字節(jié),則使用ziplist編碼:

  • ziplist編碼的有序集合對象使用壓縮列表作為底層實(shí)現(xiàn),每個(gè)集合元素使用兩個(gè)緊挨在一起的壓縮列表節(jié)點(diǎn)來保存,第一個(gè)節(jié)點(diǎn)保存元素成員,第二節(jié)點(diǎn)保存元素分值,各個(gè)元素按分值大小從小到大排序;
  • skiplist編碼的有序集合對象使用zset作為底層實(shí)現(xiàn),一個(gè)zset同時(shí)包含一個(gè)字典和一個(gè)跳躍表,跳躍表按分值從小到大保存了所有集合元素,每個(gè)跳躍表節(jié)點(diǎn)保存一個(gè)集合元素;字典則為有序集合創(chuàng)建了一個(gè)成員到分值的映射,鍵值對保存的是集合元素的成員(鍵)和分值(值),通過字典可以O(shè)(1)的查找給定成員的分值。跳躍表和字典會通過指針共享相同元素的成員和分值,也就不會因此浪費(fèi)內(nèi)存;

6.數(shù)據(jù)庫

? ? ? ?Redis服務(wù)器將所有數(shù)據(jù)庫都保存在服務(wù)器狀態(tài)redis.h/redisServer結(jié)構(gòu)的db數(shù)組中,初始化服務(wù)器時(shí),根據(jù)結(jié)構(gòu)中的dbnum屬性確定創(chuàng)建多少個(gè)數(shù)據(jù)庫(默認(rèn)16)。每個(gè)Redis客戶端都有自己的目標(biāo)數(shù)據(jù)庫(默認(rèn)0),由客戶端狀態(tài)redisClient結(jié)構(gòu)的db屬性記錄客戶端當(dāng)前的目標(biāo)數(shù)據(jù)庫,可通過SELECT命令來切換目標(biāo)數(shù)據(jù)庫。

鍵空間
? ? ? ?Redis中每個(gè)數(shù)據(jù)庫由一個(gè)redis.h/redisDb結(jié)構(gòu)表示,其中的dict字典保存了數(shù)據(jù)庫中的所有鍵值對,稱這個(gè)字典為鍵空間,鍵空間的鍵也是數(shù)據(jù)庫的鍵,是一個(gè)字符串對象,鍵空間的值即數(shù)據(jù)庫的值,每個(gè)值可以是字符串對象、列表對象、哈希表對象、集合對象、有序集合對象。

讀寫鍵空間的維護(hù)操作

  • 讀取一個(gè)鍵后(讀寫都要讀),會根據(jù)鍵是否存在來更新服務(wù)器鍵空間的命中次數(shù)(keyspace_hits)和不命中次數(shù)(keyspace_misses);
  • 讀取一個(gè)鍵后,會更新鍵的LRU(最后一次使用)時(shí)間,用于計(jì)算鍵的閑置時(shí)間;
  • 若服務(wù)器在讀取一個(gè)鍵時(shí)發(fā)現(xiàn)鍵已過期,則先刪除這個(gè)過期鍵;
  • 若服務(wù)器使用WATCH命令監(jiān)視了某個(gè)鍵,那么對被監(jiān)視鍵進(jìn)行修改后,會將這個(gè)鍵標(biāo)記為臟,從而讓事務(wù)程序注意到這個(gè)鍵已經(jīng)被修改;
  • 服務(wù)器每修改一個(gè)鍵,會將臟鍵計(jì)數(shù)器值加1,該計(jì)數(shù)器會觸發(fā)服務(wù)器的持久化以及復(fù)制操作;
  • 若服務(wù)器開啟了數(shù)據(jù)庫通知功能,那么對鍵進(jìn)行修改了之后,服務(wù)器按配置發(fā)送相應(yīng)的數(shù)據(jù)庫通知;

鍵過期
? ? ? ?通過EXPIRE命令,客戶端可以以秒或毫秒精度為數(shù)據(jù)庫中的某個(gè)鍵設(shè)置過期時(shí)間,過期時(shí)間是一個(gè)UNIX時(shí)間戳;TTL命令接受一個(gè)帶生存時(shí)間或過期時(shí)間的鍵,返回這個(gè)鍵的剩余生存時(shí)間。
? ? ? ?在redisDb結(jié)構(gòu)的expires字典保存了數(shù)據(jù)庫中所有鍵的過期時(shí)間,稱為過期字典。過期字典的鍵是一個(gè)指針,指向鍵空間中的某個(gè)鍵對象;過期字典的值是一個(gè)Long類型的整數(shù),保存了鍵所指向的數(shù)據(jù)庫鍵的過期時(shí)間(毫米精度的UNIX時(shí)間戳)。通過直接查詢字典的過期時(shí)間與當(dāng)前時(shí)間對比來判斷鍵是否過期。

過期鍵刪除策略
? ? ? ?Redis服務(wù)器實(shí)際使用惰性刪除和定期刪除策略,通過配合使用兩種刪除策略,很好地在使用CPU和避免內(nèi)存浪費(fèi)之間取得了平衡;

  • 定時(shí)刪除:在設(shè)置鍵的過期時(shí)間的同時(shí),創(chuàng)建一個(gè)定時(shí)器,讓定時(shí)器在鍵的過期時(shí)間來臨時(shí),立即執(zhí)行對鍵的刪除操作;
  • 惰性刪除:放任鍵過期不管,每次從鍵空間獲取鍵時(shí),都檢查取得的鍵是否過期,如果過期則刪除該鍵,沒有則返回該鍵;
  • 定期刪除:每隔一段時(shí)間,程序?qū)?shù)據(jù)庫進(jìn)行一次檢查,刪除里面的過期鍵;

7.RDB持久化

? ? ? ?RDB持久化功能所生成的RDB文件是一個(gè)經(jīng)過壓縮的二進(jìn)制文件,通過該文件可以還原生成RDB文件時(shí)的數(shù)據(jù)庫狀態(tài)(將非空數(shù)據(jù)庫以及它們的鍵值對統(tǒng)稱為數(shù)據(jù)庫狀態(tài))。

RDB文件的創(chuàng)建與載入
? ? ? ?RDB文件的載入工作在服務(wù)器啟動時(shí)自動執(zhí)行的,創(chuàng)建RDB文件有兩個(gè)命令可以用于生成RDB文件:

  • SAVE:阻塞Redis服務(wù)器進(jìn)程,直到RDB文件創(chuàng)建完畢為止,在服務(wù)器進(jìn)程阻塞期間,服務(wù)器不能處理任何命令請求;
  • BGSAVE:與SAVE的直接阻塞服務(wù)器進(jìn)程不同,BGSAVE命令會派生出一個(gè)子進(jìn)程來創(chuàng)建RDB文件,Redis服務(wù)器仍然可以繼續(xù)處理客戶端的命令請求。但是對于SAVE、BGSAVE命令會被拒絕,防止同時(shí)調(diào)用rdbsave函數(shù)產(chǎn)生競爭條件;對于BGREWRITEAOF,如果BGSAVE正在執(zhí)行,則BGREWRITEAOF會被延遲到前者執(zhí)行完成,如果BGREWRITEAOF正在執(zhí)行,BGSAVE會被拒絕;

? ? ? ?由于AOF文件的更新頻率通常比RDB文件的更新頻率高,所以:

  • 如果服務(wù)器開啟了AOF持久化功能,那么服務(wù)器會使用AOF文件來還原數(shù)據(jù)庫狀態(tài);
  • 只有AOF持久化功能處于關(guān)閉狀態(tài)時(shí),服務(wù)器才會使用RDB文件來還原數(shù)據(jù)庫狀態(tài),由rdb.c/rdbLoad函數(shù)完成;

自動間隔性保存
? ? ? ?BGSAVE可以在不阻塞服務(wù)器進(jìn)程的情況下執(zhí)行,所以Redis允許用戶通過設(shè)置服務(wù)器配置的save選項(xiàng),讓服務(wù)器每隔一段時(shí)間自動執(zhí)行一次BGSAVE命令。用戶可以通過save選項(xiàng)設(shè)置多個(gè)保存條件,但只要其中任意一個(gè)條件被滿足,服務(wù)器就會執(zhí)行BGSAVE命令。

save    900    1  //服務(wù)器在900秒之內(nèi),對數(shù)據(jù)庫進(jìn)行了至少1次修改  
save    300    10  //服務(wù)器在300秒之內(nèi),對數(shù)據(jù)庫進(jìn)行了至少10次修改  
save    60    10000  //服務(wù)器在60秒之內(nèi),對數(shù)據(jù)庫進(jìn)行了至少10000次修改  

? ? ? ?保存條件保存在服務(wù)器狀態(tài)redisServer結(jié)構(gòu)中的saveparams屬性中,saveparams是1個(gè)數(shù)組,數(shù)組中的每個(gè)元素都是一個(gè)savepara結(jié)構(gòu),代表1個(gè)保存條件;

struct    savaparam {
    //秒數(shù)
    time_t    seconds;  
    //修改數(shù)
    int    changes;  
}

dirty計(jì)數(shù)器和lastsave屬性

  • dirty計(jì)數(shù)器:記錄距離上一次成功執(zhí)行SAVE或BGSAVE命令之后,服務(wù)器對數(shù)據(jù)庫狀態(tài)進(jìn)行了多少次修改;
  • lastsave屬性:一個(gè)UNIX時(shí)間戳,記錄了服務(wù)器上一次成功執(zhí)行SAVE或BGSAVE命令的時(shí)間;

? ? ? ?Redis服務(wù)器周期性操作函數(shù)serverCron,默認(rèn)每個(gè)100毫秒執(zhí)行一次對保存條件的檢查,會遍歷檢查saveparams數(shù)組內(nèi)的所有保存條件,只要有任意一個(gè)條件被滿足,那么服務(wù)器就會執(zhí)行BGSAVE命令。

RDB文件結(jié)構(gòu)

REDIS db_version databases EOF check_sum

注:全大寫單詞表示常量,全小寫表示變量和數(shù)據(jù)

  1. REDIS:RDB文件的最開頭部分,長度5字節(jié),保存著"REDIS"這5個(gè)字符,方便在載入時(shí)快速檢查文件是否RDB文件;
  2. db_version:長度4字節(jié),值是一個(gè)字符串表示的整數(shù),記錄了RDB文件的版本號;
  3. databases:包含0個(gè)或任意多個(gè)數(shù)據(jù)庫,以及各個(gè)數(shù)據(jù)庫中的鍵值對數(shù)據(jù)。若服務(wù)器的數(shù)據(jù)庫狀態(tài)為空,則對應(yīng)這部分也為空;若數(shù)據(jù)庫狀態(tài)非空,則根據(jù)數(shù)據(jù)庫所保存鍵值對的數(shù)量、類型和內(nèi)容不同,對應(yīng)長度也不同;
  4. EOF:長度1字節(jié),標(biāo)志著RDB文件正文內(nèi)容的結(jié)束;
  5. check_sum:長度8字節(jié),無符號整數(shù),保存著一個(gè)校驗(yàn)和,由前面4部分的內(nèi)容進(jìn)行計(jì)算得出;

? ? ? ?每個(gè)非空數(shù)據(jù)庫在RDB文件中都可以保存為SELECTDBdb_number、key_value_pairs;

  1. SELECTDB:長度1字節(jié)的常量,表示接下來要讀入的將是一個(gè)數(shù)據(jù)庫號碼;

  2. db_number:保存著1個(gè)數(shù)據(jù)庫號碼,根據(jù)號碼的不同,長度可以是1,2,5字節(jié),用于切換數(shù)據(jù)庫,使對應(yīng)數(shù)據(jù)庫的鍵值對能存入正確;

  3. key_value_pairs:保存了數(shù)據(jù)庫中的所有鍵值對數(shù)據(jù),若鍵值對有過期時(shí)間,則過期時(shí)間也會和鍵值對保存在一起。鍵值對由[EXPIRETIME_MS、ms]、TYPE、key、value組成:

    • EXPIRETIME_MS:(若無過期時(shí)間則不存在此屬性) 常量,長度1字節(jié),告知程序接下來讀入的將是以毫秒為單位的過期時(shí)間;
    • ms:(若無過期時(shí)間則不存在此屬性) 8字節(jié)的帶符號整數(shù),記錄一個(gè)以毫秒為單位的UNIX時(shí)間戳,代表鍵值對的過期時(shí)間;
    • TYPE:記錄了value的類型,長度為1字節(jié);
    • key:字符串對象,以REDIS_RDB_TYPE_STRING類型編碼,長度隨內(nèi)容變化;
    • value:根據(jù)TYPE類型的不同和保存內(nèi)容的不同,結(jié)構(gòu)和長度也會不同;

value的編碼

  1. 字符串對象(REDIS_RDB_TYPE_STRING)
    ? ? ? ?字符串對象的編碼可以是REDIS_ENCODING_INTREDIS_ENCODING_RAW。

    • 若為REDIS_ENCODING_INT,說明對象保存的是長度超過32位的整數(shù),編碼對象按[EBCODING,integer]形式保存,ENCODING可以是ENCODING_RDB_ENC_INT8/16/32,分別代表使用8,16,32位來保存整數(shù)值
    • 若為REDIS_ENCODING_RAW編碼,代表對象所保存的是1個(gè)字符串值,根據(jù)字符串長度不同,有壓縮(字符串長度>20字節(jié))和不壓縮(字符串長度<=20字節(jié),直接原樣保存)兩種方法保存;
      • 對于沒有被壓縮的字符串,RDB程序會以[len string]結(jié)構(gòu)來保存該字符串,string部分保存了字符串值本身,len保存了字符串值的長度;
      • 對于壓縮后的字符串,RDB程序會以一下結(jié)構(gòu)保存字符串的長度。REDIS_RDB_ENC_LZF常量標(biāo)志著字符串已經(jīng)被LZF算法壓縮過,會根據(jù)壓縮后長度compressed_len、原長度origin_len、壓縮后字符串compressed_string對字符串進(jìn)行解壓;
REDIS_RDB_ENC_LZF compressed_len origin_len compressed_string

2.列表對象
? ? ? ?Type的值為REDIS_RDB_TYPE_LIST,那么value保存的就是一個(gè)REDIS_ENCODING_LINKEDLIST編碼的列表對象,結(jié)構(gòu)如下,list_length記錄了列表的長度,item代表列表的項(xiàng),每個(gè)列表項(xiàng)都是一個(gè)字符串對象。

list_length item1 item2 itemN

3.集合對象
? ? ? ?Type的值為REDIS_RDB_TYPE_SET,那么value保存的就是一個(gè)REDIS_ENCODING_HT編碼的集合對象,結(jié)構(gòu)如下,set_size記錄了集合的大小,elem代理集合元素,每一個(gè)集合都是一個(gè)字符串對象。

set_size elem1 elem2 elemN

4.哈希表對象
? ? ? ?Type的值為REDIS_RDB_TYPE_HASH,那么value保存的是一個(gè)REDIS_ENCODING_HT編碼的集合對象,結(jié)構(gòu)如下,hash_size記錄了哈希表的大小,key_value_pair代表哈希表中的鍵值對,鍵和值均為字符串對象。

hash_size key_value_pair1 key_value_pair2 key_value_pairN

5.有序集合對象
? ? ? ?Type的值為REDIS_RED_TYPE_ZSET,那么value保存的是REDIS_ENCODING_SKIPLIST編碼的有序集合對象,結(jié)構(gòu)如下,sorted_set_size記錄了有序集合的大小,element代表有序集合中的元素,每個(gè)元素分為成員和分值,成員是一個(gè)字符串對象,分值則是一個(gè)double類型的浮點(diǎn)數(shù)。

sorted_set_size element1 element2 elementN

6.整數(shù)集合(INTSET編碼)
? ? ? ?Type的值為REDIS_RDB_TYPE_SET_INTSET,那么value保存的是一個(gè)整數(shù)集合對象,保存方法是將整數(shù)集合轉(zhuǎn)換為字符串對象,讀取時(shí)再由字符串對象轉(zhuǎn)回整數(shù)集合。

7.ZIPLIST編碼的列表、哈希表或者有序集合
? ? ? ?Type的值為REDIS_RDB_TYPE_LIST_ZIPLIST、REDIS_RDB_TYPE_HASH_ZIPLIST或者REDIS_RDB_TYPE_ZSET_ZIPLIST,那么value保存的是一個(gè)壓縮列表對象,保存方法是將壓縮列表轉(zhuǎn)換一個(gè)字符串對象后保存到RDB文件,讀取時(shí)再由字符串轉(zhuǎn)換回原來的壓縮列表對象,再根據(jù)TYPE的值設(shè)置壓縮列表對象的類型。

8.AOF持久化

? ? ? ?RDB持久化通過保存數(shù)據(jù)庫中的鍵值對來記錄數(shù)據(jù)庫狀態(tài),AOF持久化通過保存Redis服務(wù)器執(zhí)行的寫命令來記錄服務(wù)器狀態(tài)。服務(wù)器在啟動時(shí),可以通過載入和執(zhí)行AOF文件中保存的命令來還原服務(wù)器關(guān)閉之前的數(shù)據(jù)庫狀態(tài)。

1)AOF持久化功能的實(shí)現(xiàn)

  • 命令追加:當(dāng)AOF持久化功能打開時(shí),服務(wù)器在執(zhí)行完一個(gè)寫命令之后,會以協(xié)議格式將被執(zhí)行的寫命令追加到服務(wù)器狀態(tài)的aof_buf緩沖區(qū)的末尾。
  • AOF文件的寫入和同步:Redis的服務(wù)器進(jìn)程就是一個(gè)事件循環(huán),循環(huán)中的文件事件負(fù)責(zé)接收客戶端的命令請求并向客戶端發(fā)送命令回復(fù),時(shí)間事件則負(fù)責(zé)執(zhí)行定時(shí)任務(wù)。
    ? ? ? ?當(dāng)處理文件事件時(shí)執(zhí)行了寫命令,會使其被追加到aof_buf緩沖區(qū)里面,所以服務(wù)器每次結(jié)束一個(gè)事件循環(huán)之前,都會調(diào)用flushAppendOnlyFile函數(shù)考慮是否需要將aof_buf緩沖區(qū)中的內(nèi)容寫入到AOF文件里面。
    ? ? ? ?由服務(wù)器配置的appendfsync選項(xiàng)的值決定是否將緩沖區(qū)中的內(nèi)容同步到AOF文件中。
appendfsync flushAppendOnlyFile函數(shù)行為
always 將aof_buf緩沖區(qū)中的所有內(nèi)容寫入并同步到AOF文件
everysec(默認(rèn)) 將aof_buf緩沖區(qū)中的所有內(nèi)容寫入并同步到AOF文件,若上次同步距離現(xiàn)在超過一秒,再次同步,該同步操作由一個(gè)線程專門負(fù)責(zé)執(zhí)行
no 將aof_buf緩沖區(qū)中的所有內(nèi)容寫入并同步到AOF文件,但不對其進(jìn)行同步,何時(shí)同步由操作系統(tǒng)決定

2)AOF文件的載入和數(shù)據(jù)還原
? ? ? ?AOF文件里面包含了重建數(shù)據(jù)庫狀態(tài)所需的所有寫命令,所以服務(wù)器只要讀入并重新執(zhí)行一遍AOF文件里面保存的寫命令,就可以還原服務(wù)器關(guān)閉之前的數(shù)據(jù)庫狀態(tài)。詳細(xì)步驟如下:

  • 創(chuàng)建一個(gè)不帶網(wǎng)絡(luò)連接的偽客戶端,因?yàn)镽edis的命令只能在客戶端上下文中執(zhí)行;
  • 從AOF文件中分析并讀取出一條寫命令;
  • 使用偽客戶端執(zhí)行被讀出的寫命令;
  • 重復(fù)步驟2,3,直到AOF文件中的所有寫命令都被處理完畢;

3)AOF重寫
? ? ? ?隨著服務(wù)器運(yùn)行,AOF文件記錄的內(nèi)容越來越多、體積越來越大。為了解決AOF文件體積膨脹問題,Redis提供了AOF文件重寫功能,使新的AOF代替舊AOF,兩者保存的數(shù)據(jù)庫狀態(tài)相同,但新AOF不包含任何浪費(fèi)空間的冗余命令。

AOF文件重寫的實(shí)現(xiàn):重寫功能并不需要讀取和分析舊AOF文件,僅需對數(shù)據(jù)庫從讀取鍵值,并通過一條寫命令來表示,這樣就可以將多次寫入的數(shù)據(jù)通過一條寫入命令表示,節(jié)省了AOF文件的空間。但是當(dāng)重寫程序處理集合、列表、哈希表等時(shí),會先檢查元素?cái)?shù)量,若超過REDIS_AOF_REWRITE_ITEMS_PER_CMD(默認(rèn)64)時(shí),會用多條命令來記錄該集合,每條命令設(shè)置的元素?cái)?shù)量也為64個(gè)。

AOF后臺重寫
? ? ? ?由于Redis服務(wù)器使用單個(gè)線程來處理命令請求,所以直接由服務(wù)器直接調(diào)用aof_rewrite會使服務(wù)器在重寫期間無法執(zhí)行客戶端請求。
? ? ? ?因此Redis將重寫程序放到子進(jìn)程里執(zhí)行,這樣服務(wù)器進(jìn)程可以繼續(xù)處理請求命令;且子進(jìn)程帶有服務(wù)器進(jìn)程的數(shù)據(jù)副本,使用子進(jìn)程而不是線程,可以避免使用鎖的情況下,保證數(shù)據(jù)安全性。但子進(jìn)程重寫期間,服務(wù)器進(jìn)程新處理的命令會對現(xiàn)有數(shù)據(jù)庫狀態(tài)進(jìn)行修改,使得數(shù)據(jù)庫狀態(tài)與重寫后的AOF文件所保存的數(shù)據(jù)庫狀態(tài)不一致。因此在子進(jìn)程重寫期間,服務(wù)器進(jìn)程需要執(zhí)行以下三個(gè)工作:

  • 執(zhí)行客戶端發(fā)來的命令;
  • 將執(zhí)行后的寫命令追加到AOF緩沖區(qū);
  • 將執(zhí)行后的寫命令追加到AOF重寫緩沖區(qū);

? ? ? ?這樣就保證了AOF緩沖區(qū)的內(nèi)容會定期被寫入和同步到AOF文件;從創(chuàng)建子進(jìn)程開始,服務(wù)器執(zhí)行的所有寫命令都會被記錄到AOF重寫緩沖區(qū)。當(dāng)子進(jìn)程完成AOF重寫之后,會向服務(wù)器進(jìn)程發(fā)生信號,服務(wù)器進(jìn)程接到信號執(zhí)行信號處理函數(shù):

  • 將AOF重寫緩沖區(qū)的內(nèi)容寫入到新AOF,此時(shí)AOF和服務(wù)器保存的數(shù)據(jù)庫狀態(tài)一致;
  • 對新AOF文件進(jìn)行改名,原子地覆蓋現(xiàn)有的AOF文件,完成新舊AOF的替換;

9.事件

1)文件事件
? ? ? ?Redis服務(wù)器通過套接字與客戶端/其他服務(wù)器進(jìn)行連接,文件事件就是服務(wù)器對套接字操作的抽象。文件事件處理器主要由套接字、I/O多路復(fù)用程序、文件事件分派器以及事件處理器構(gòu)成。

  • 文件事件處理器使用I/O多路復(fù)用來同時(shí)監(jiān)聽多個(gè)套接字,并根據(jù)套接字目前執(zhí)行的任務(wù)來為套接字關(guān)聯(lián)不同的事件處理器;
  • 當(dāng)被監(jiān)聽的套接字準(zhǔn)備好執(zhí)行連接應(yīng)答、讀取、寫入、關(guān)閉等操作時(shí),與操作相對應(yīng)的文件事件就會產(chǎn)生來處理這些事件;

? ? ? ?盡管多個(gè)文件事件可能會并發(fā)地出現(xiàn),但I(xiàn)/O多路復(fù)用程序總是會將所有產(chǎn)生事件的套接字放到一個(gè)隊(duì)列里面,然后通過這個(gè)隊(duì)列,以有序、同步、每次一個(gè)套接字的方式向文件事件分派器傳送套接字。

文件事件的處理器

  • 連接應(yīng)答處理器:用于對連接服務(wù)器監(jiān)聽套接字的客戶端進(jìn)行應(yīng)答,當(dāng)Redis服務(wù)器進(jìn)行初始化時(shí),會將此連接應(yīng)答處理器和服務(wù)器監(jiān)聽套接字的AE_READABLE事件關(guān)聯(lián)起來;
  • 命令請求處理器:負(fù)責(zé)從套接字中讀入客戶端發(fā)生的命令請求內(nèi)容,當(dāng)客戶端通過連接應(yīng)答處理器成功連接到服務(wù)器后,會將客戶端套接字的AR_READABLE事件和命令請求處理器關(guān)聯(lián)起來;
  • 命令回復(fù)處理器:負(fù)責(zé)將服務(wù)器執(zhí)行命令后得到的命令回復(fù)通過套接字返回給客戶端,當(dāng)服務(wù)器有命令回復(fù)需要傳送給客戶端的時(shí)候,服務(wù)器會將客戶端套接字的AE_WRITABLE時(shí)間和命令回復(fù)處理器關(guān)聯(lián);

2)時(shí)間事件
? ? ? ?Redis的時(shí)間事件分為定時(shí)事件、周期性事件,一個(gè)時(shí)間事件主要由以下三個(gè)屬性組成:

  • id:服務(wù)器為時(shí)間事件創(chuàng)建的全局唯一ID;
  • when:毫秒精度的UNIX時(shí)間戳,記錄了時(shí)間事件的到達(dá)時(shí)間;
  • timeProc:時(shí)間事件處理器,當(dāng)時(shí)間事件到達(dá)時(shí),服務(wù)器會調(diào)用相應(yīng)的處理器來處理時(shí)間;

? ? ? ?一個(gè)時(shí)間事件是定時(shí)還是周期主要取決于時(shí)間事件處理器的返回值,服務(wù)器將所有時(shí)間事件都放在一個(gè)無序鏈表(不按when屬性排序)中,每當(dāng)時(shí)間事件執(zhí)行器運(yùn)行時(shí),遍歷整個(gè)鏈表,查找所有已到達(dá)的時(shí)間事件,并調(diào)用相應(yīng)的事件處理器。

  • 返回ae.h/AE_NOMORE,則為定時(shí)事件,在達(dá)到一次之后就會被刪除,之后不再到達(dá);
  • 返回非AE_NOMORE的整數(shù),則為周期事件,當(dāng)一個(gè)時(shí)間事件到達(dá)之后,服務(wù)器會根據(jù)事件處理器返回的值,對時(shí)間事件的when屬性進(jìn)行更新;

3)事件的調(diào)度和執(zhí)行
? ? ? ?由于服務(wù)器同時(shí)存在文件事件和時(shí)間事件兩種事件類型,所有服務(wù)器必須對這兩種事件進(jìn)行調(diào)度,決定何時(shí)處理何種事件。

  • aeApiPoll函數(shù)的最大阻塞事件由到達(dá)時(shí)間最接近當(dāng)前時(shí)間的時(shí)間事件決定,這樣既可避免服務(wù)器對時(shí)間事件進(jìn)行頻繁的輪詢,也可確保aeApiPoll不會阻塞過長時(shí)間;
  • 由于文件事件時(shí)隨機(jī)出現(xiàn)的,如果等待并處理完一次文件事件之后,仍未有任何時(shí)間事件到達(dá),則服務(wù)器再次等待并處理文件事件;
  • 對文件事件和時(shí)間事件的處理都是同步、有序、原子地執(zhí)行的,服務(wù)器不會中途中斷事件處理,也不會對事件進(jìn)行搶占。盡可能的減少程序的阻塞時(shí)間,并在有需要時(shí)主動讓出執(zhí)行權(quán),降低造成事件饑餓的可能性;
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Redis的內(nèi)存優(yōu)化 聲明:本文內(nèi)容來自《Redis開發(fā)與運(yùn)維》一書第八章,如轉(zhuǎn)載請聲明。 Redis所有的數(shù)據(jù)都...
    meng_philip123閱讀 19,060評論 2 29
  • 參考來源 Redis的內(nèi)存優(yōu)化 Redis所有的數(shù)據(jù)都在內(nèi)存中,而內(nèi)存又是非常寶貴的資源。對于如何優(yōu)化內(nèi)存使用一直...
    秦漢郵俠閱讀 1,369評論 0 2
  • 本文為筆者對在學(xué)習(xí)Redis過程中所收集資料的一個(gè)總結(jié),目的是為了以后方便回顧相關(guān)的知識,大部分為非原創(chuàng)內(nèi)容。特此...
    EakonZhao閱讀 14,630評論 0 9
  • 聲明:本文內(nèi)容來自《Redis開發(fā)與運(yùn)維》一書第八章,如轉(zhuǎn)載請聲明。Redis所有的數(shù)據(jù)都在內(nèi)存中,而內(nèi)存又是非常...
    yoqu閱讀 1,609評論 0 2
  • 說起火鍋來,那在中國的美食文化中一直占有著重要地位。早在東漢時(shí)期,就有了火鍋這種吃法,到了宋代就已經(jīng)流行開來。而隨...
    你腦袋里有花生閱讀 304評論 0 0

友情鏈接更多精彩內(nèi)容