自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Redis存儲(chǔ)總是心里沒(méi)底?你大概漏了這些數(shù)據(jù)結(jié)構(gòu)原理

存儲(chǔ) 存儲(chǔ)軟件 Redis
想要弄清楚Redis內(nèi)部如何支持5種數(shù)據(jù)類(lèi)型,也就是要弄清Redis到底是使用什么樣的數(shù)據(jù)結(jié)構(gòu)來(lái)存儲(chǔ)、查找我們?cè)O(shè)置在內(nèi)存中的數(shù)據(jù)。

本期內(nèi)容則會(huì)著重講講Redis內(nèi)存數(shù)據(jù)結(jié)構(gòu)與編碼,弄清Redis內(nèi)部到底是如何支持這5種數(shù)據(jù)類(lèi)型的。

[[255026]]

一、Redis內(nèi)存數(shù)據(jù)結(jié)構(gòu)與編碼

想要弄清楚Redis內(nèi)部如何支持5種數(shù)據(jù)類(lèi)型,也就是要弄清Redis到底是使用什么樣的數(shù)據(jù)結(jié)構(gòu)來(lái)存儲(chǔ)、查找我們?cè)O(shè)置在內(nèi)存中的數(shù)據(jù)。

雖然我們使用5種數(shù)據(jù)類(lèi)型來(lái)緩存數(shù)據(jù),但是Redis會(huì)根據(jù)我們存儲(chǔ)數(shù)據(jù)的不同而選用不同的數(shù)據(jù)結(jié)構(gòu)和編碼。

 

這些數(shù)據(jù)類(lèi)型在內(nèi)存中的數(shù)據(jù)結(jié)構(gòu)和編碼有很多種,隨著我們存儲(chǔ)的數(shù)據(jù)類(lèi)型的不同、數(shù)據(jù)量的大小不同都會(huì)引起內(nèi)存數(shù)據(jù)結(jié)構(gòu)的動(dòng)態(tài)調(diào)整。

本文只是做數(shù)據(jù)結(jié)構(gòu)和編碼的一般性介紹,不做過(guò)多細(xì)節(jié)討論,一方面是關(guān)于Redis源碼分析的資料網(wǎng)上有很多,還有一個(gè)原因就是Redis每一個(gè)版本的實(shí)現(xiàn)有很大差異,一旦展開(kāi)細(xì)節(jié)討論,每一個(gè)點(diǎn)每一個(gè)數(shù)據(jù)結(jié)構(gòu)都會(huì)很復(fù)雜,所以我們這里就不展開(kāi)討論這些,只是起到拋磚引玉作用。

1、OBJECT encoding key、DEBUG OBJECT key

我們知道使用type命令可以查看某個(gè)key是否是5種數(shù)據(jù)類(lèi)型之一,但是當(dāng)我們想查看某個(gè)key底層是使用哪種數(shù)據(jù)結(jié)構(gòu)和編碼來(lái)存儲(chǔ)的時(shí)候,可以使用OBJECT encoding命令。

 

同樣一個(gè)key,由于我們?cè)O(shè)置的值不同,Redis選用了不同的內(nèi)存數(shù)據(jù)結(jié)構(gòu)和編碼。雖然Redis提供String數(shù)據(jù)類(lèi)型,但是Redis會(huì)自動(dòng)識(shí)別我們cache的數(shù)據(jù)類(lèi)型是int還是String。

如果我們?cè)O(shè)置的是字符串,且這個(gè)字符串長(zhǎng)度不大于39字節(jié),那么將使用embstr來(lái)編碼;如果大于39字節(jié)將使用raw來(lái)編碼。Redis4.0將這個(gè)閥值擴(kuò)大了45個(gè)字節(jié)。

除了使用OBJECT encoding命令外,我們還可以使用DEBUG OBJECT命令來(lái)查看更多詳細(xì)信息。

 

DEBUG OBJECT能看到這個(gè)對(duì)象的refcount引用計(jì)數(shù)、serializedlength長(zhǎng)度、lru_seconds_idle時(shí)間,這些信息決定了這個(gè)key緩存清除策略。

2、簡(jiǎn)單動(dòng)態(tài)字符串(simple dynamic String)

簡(jiǎn)單動(dòng)態(tài)字符串簡(jiǎn)稱(chēng)SDS,在Redis中所有涉及到字符串的地方都是使用SDS實(shí)現(xiàn),當(dāng)然這里不包括字面量。SDS與傳統(tǒng)C字符串的區(qū)別就是SDS是結(jié)構(gòu)化的,它可以高效的處理分配、回收、長(zhǎng)度計(jì)算等問(wèn)題。

  1. struct sdshdr { 
  2.     unsigned int len; 
  3.     unsigned int free
  4.     char buf[]; 
  5. }; 

這是Redis3.0版本的sds.h頭文件定義,3.0.0之后變化比較大。len表示字符串長(zhǎng)度,free表示空間長(zhǎng)度,buf數(shù)組表示字符串。

SDS有很多優(yōu)點(diǎn),比如,獲取長(zhǎng)度的時(shí)間復(fù)雜度O(1),不需要遍歷所有charbuf[]組數(shù),直接返回len值。

  1. static inline size_t sdslen(const sds s) { 
  2.     struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr))); 
  3.     return sh->len; 

當(dāng)然還有空間分配檢查、空間預(yù)分配、空間惰性釋放等,這些都是SDS結(jié)構(gòu)化字符串帶來(lái)的強(qiáng)大的擴(kuò)展能力。

3、鏈表(linked list)

鏈表數(shù)據(jù)結(jié)構(gòu)我們是比較熟悉的,最大的特點(diǎn)就是節(jié)點(diǎn)的增、刪非常靈活。Redis List數(shù)據(jù)類(lèi)型底層就是基于鏈表來(lái)實(shí)現(xiàn)。這是Redis3.0實(shí)現(xiàn)。

  1. typedef struct list { 
  2.     listNode *head; 
  3.     listNode *tail; 
  4.     void *(*dup)(void *ptr); 
  5.     void (*free)(void *ptr); 
  6.     int (*match)(void *ptr, void *key); 
  7.     unsigned long len; 
  8. } list; 
  9.  
  10. typedef struct listNode { 
  11.     struct listNode *prev; 
  12.     struct listNode *next
  13.     void *value; 
  14. } listNode; 

在Redis3.2.0版本的時(shí)候引入了quicklist鏈表結(jié)構(gòu),結(jié)合了linkedlist和ziplist的優(yōu)勢(shì)。

  1. typedef struct quicklist { 
  2.     quicklistNode *head; 
  3.     quicklistNode *tail; 
  4.     unsigned long count;        /* total count of all entries in all ziplists */ 
  5.     unsigned int len;           /* number of quicklistNodes */ 
  6.     int fill : 16;              /* fill factor for individual nodes */ 
  7.     unsigned int compress : 16; /* depth of end nodes not to compress;0=off */ 
  8. } quicklist; 
  9.  
  10. typedef struct quicklistNode { 
  11.     struct quicklistNode *prev; 
  12.     struct quicklistNode *next
  13.     unsigned char *zl; 
  14.     unsigned int sz;             /* ziplist size in bytes */ 
  15.     unsigned int count : 16;     /* count of items in ziplist */ 
  16.     unsigned int encoding : 2;   /* RAW==1 or LZF==2 */ 
  17.     unsigned int container : 2;  /* NONE==1 or ZIPLIST==2 */ 
  18.     unsigned int recompress : 1; /* was this node previous compressed? */ 
  19.     unsigned int attempted_compress : 1; /* node can't compress; too small */ 
  20.     unsigned int extra : 10; /* more bits to steal for future usage */ 
  21. } quicklistNode; 

quicklist提供了靈活性同時(shí)也兼顧了ziplist的壓縮能力,quicklist->encoding指定了兩種壓縮算法。quickList->compress表示我們可以進(jìn)行quicklist node的深度壓縮能力。Redis提供了兩個(gè)有關(guān)于壓縮的配置:

  • List-max-zipList-size:zipList長(zhǎng)度控制;
  • List-compress-depth:控制鏈表兩端節(jié)點(diǎn)的壓縮個(gè)數(shù),越是靠近兩端的節(jié)點(diǎn)被訪(fǎng)問(wèn)的機(jī)率越大,所以可以將訪(fǎng)問(wèn)機(jī)率大的節(jié)點(diǎn)不壓縮,其他節(jié)點(diǎn)進(jìn)行壓縮。

對(duì)比Redis3.2的quicklist與Redis3.0,很明顯quicklist提供了更加豐富的壓縮功能。Redis3.0的版本是每個(gè)listnode直接緩存值,而quicklistnode還有強(qiáng)大的有關(guān)于壓縮能力。

  1. LPUSH list:products:mall 100 200 300 
  2. (integer) 3 
  3.  
  4. OBJECT encoding list:products:mall 
  5. "quicklist" 

4、字典(dict)

dict字典是基于Hash算法來(lái)實(shí)現(xiàn),是Hash數(shù)據(jù)類(lèi)型的底層存儲(chǔ)數(shù)據(jù)結(jié)構(gòu)。我們來(lái)看下Redis3.0.0版本的dict.h頭文件定義:

  1. typedef struct dict { 
  2.     dictType *type; 
  3.     void *privdata; 
  4.     dictht ht[2]; 
  5.     long rehashidx; 
  6.     int iterators;  
  7. } dict; 
  8.  
  9. typedef struct dictht { 
  10.     dictEntry **table
  11.     unsigned long size
  12.     unsigned long sizemask; 
  13.     unsigned long used; 
  14. } dictht; 
  15.  
  16. typedef struct dictEntry { 
  17.     void *key
  18.     union { 
  19.         void *val; 
  20.         uint64_t u64; 
  21.         int64_t s64; 
  22.         double d; 
  23.     } v; 
  24.     struct dictEntry *next
  25. } dictEntry; 

說(shuō)到Hash table有兩個(gè)東西是我們經(jīng)常會(huì)碰到的,首先就是Hash碰撞問(wèn)題,Redis dict是采用鏈地址法來(lái)解決,dictEntry->next就是指向下個(gè)沖突key的節(jié)點(diǎn)。

還有一個(gè)經(jīng)常碰到的就是rehash的問(wèn)題,提到rehash我們還是有點(diǎn)擔(dān)心性能的。那么Redis實(shí)現(xiàn)是非常巧妙的,采用惰性漸進(jìn)式rehash算法。

在dict struct里有一個(gè)ht[2]組數(shù),還有一個(gè)rehashidx索引。Redis進(jìn)行rehash的大致算法是這樣的:

首先會(huì)開(kāi)辟一個(gè)新的dictht空間,放在ht[2]索引上,此時(shí)將rehashidx設(shè)置為0,表示開(kāi)始進(jìn)入rehash階段,這個(gè)階段可能會(huì)持續(xù)很長(zhǎng)時(shí)間,rehashidx表示dictEntry個(gè)數(shù)。每次當(dāng)有對(duì)某個(gè)ht[1]索引中的key進(jìn)行訪(fǎng)問(wèn)時(shí),獲取、刪除、更新,Redis都會(huì)將當(dāng)前dictEntry索引中的所有key rehash到ht[2]字典中。一旦rehashidx=-1表示rehash結(jié)束。

5、跳表(skip list)

skip list是Zset的底層數(shù)據(jù)結(jié)構(gòu),有著高性能的查找排序能力。

我們都知道一般用來(lái)實(shí)現(xiàn)帶有排序的查找都是用Tree實(shí)現(xiàn),不管是各種變體的B Tree還是B+Tree,本質(zhì)都是用來(lái)做順序查找。

skip list實(shí)現(xiàn)起來(lái)簡(jiǎn)單,性能也與B Tree相接近。

  1. typedef struct zskiplistNode { 
  2.     robj *obj; 
  3.     double score; 
  4.     struct zskiplistNode *backward; 
  5.     struct zskiplistLevel { 
  6.         struct zskiplistNode *forward
  7.         unsigned int span; 
  8.     } level[]; 
  9. } zskiplistNode; 
  10.  
  11.  
  12. typedef struct zskiplist { 
  13.     struct zskiplistNode *header, *tail; 
  14.     unsigned long length; 
  15.     int level
  16. } zskiplist; 

zskiplistNode->zskiplistLevel->span這個(gè)值記錄了當(dāng)前節(jié)點(diǎn)距離下個(gè)節(jié)點(diǎn)的跨度。每一個(gè)節(jié)點(diǎn)會(huì)有最大不超過(guò)zskiplist->level節(jié)點(diǎn)個(gè)數(shù),分別用來(lái)表示不同跨度與節(jié)點(diǎn)的距離。

每個(gè)節(jié)點(diǎn)會(huì)有多個(gè)forward向前指針,只有一個(gè)backward指針。每個(gè)節(jié)點(diǎn)會(huì)有對(duì)象__*obj__和score分值,每個(gè)分值都會(huì)按照順序排列。

6、整數(shù)集合(int set)

int set整數(shù)集合是set數(shù)據(jù)類(lèi)型的底層實(shí)現(xiàn)數(shù)據(jù)結(jié)構(gòu),它的特點(diǎn)和使用場(chǎng)景很明顯,只要我們使用的集合都是整數(shù)且在一定的范圍之內(nèi),都會(huì)使用整數(shù)集合編碼。

  1. SADD set:userid 100 200 300 
  2. (integer) 3 
  3.  
  4. OBJECT encoding set:userid 
  5. "intset" 

int set使用一塊連續(xù)的內(nèi)存來(lái)存儲(chǔ)集合數(shù)據(jù),它是數(shù)組結(jié)構(gòu)不是鏈表結(jié)構(gòu)。

  1. typedef struct intset { 
  2.     uint32_t encoding; 
  3.     uint32_t length; 
  4.     int8_t contents[]; 
  5. } intset; 

intset->encoding用來(lái)確定contents[]是什么類(lèi)型的整數(shù)編碼,以下三種值之一:

  1. #define INTSET_ENC_INT16 (sizeof(int16_t)) 
  2. #define INTSET_ENC_INT32 (sizeof(int32_t)) 
  3. #define INTSET_ENC_INT64 (sizeof(int64_t)) 

Redis會(huì)根據(jù)我們?cè)O(shè)置的值類(lèi)型動(dòng)態(tài)sizeof出一個(gè)對(duì)應(yīng)的空間大小。如果我們集合原來(lái)是int16,然后往集合里添加了int32整數(shù)將觸發(fā)升級(jí),一旦升級(jí)成功不會(huì)觸發(fā)降級(jí)操作。

7、壓縮表(zip list)

zip list壓縮表是List、Zset、Hash數(shù)據(jù)類(lèi)型的底層數(shù)據(jù)結(jié)構(gòu)之一。它是為了節(jié)省內(nèi)存通過(guò)壓縮數(shù)據(jù)存儲(chǔ)在一塊連續(xù)的內(nèi)存空間中。

  1. typedef struct zlentry { 
  2.     unsigned int prevrawlensize, prevrawlen; 
  3.     unsigned int lensize, len; 
  4.     unsigned int headersize; 
  5.     unsigned char encoding; 
  6.     unsigned char *p; 
  7. } zlentry; 

它最大的優(yōu)點(diǎn)就是壓縮空間,空間利用率很高。缺點(diǎn)就是一旦出現(xiàn)更新可能就是連鎖更新,因?yàn)閿?shù)據(jù)在內(nèi)容空間中都是連續(xù)的,最極端情況下就是可能出現(xiàn)順序連鎖擴(kuò)張。

壓縮列表會(huì)由多個(gè)zlentry節(jié)點(diǎn)組成,每一個(gè)zlentry記錄上一個(gè)節(jié)點(diǎn)長(zhǎng)度和大小,當(dāng)前節(jié)點(diǎn)長(zhǎng)度lensize和大小len包括編碼encoding。

這取決于業(yè)務(wù)場(chǎng)景,Redis提供了一組配置,專(zhuān)門(mén)用來(lái)針對(duì)不同的場(chǎng)景進(jìn)行閾值控制:

  1. hash-max-ziplist-entries 512 
  2. hash-max-ziplist-value 64 
  3. list-max-ziplist-entries 512 
  4. list-max-ziplist-value 64 
  5. zset-max-ziplist-entries 128 
  6. zset-max-ziplist-value 64 

上述配置分別用來(lái)配置ziplist作為Hash、List、Zset數(shù)據(jù)類(lèi)型的底層壓縮閾值控制。

8、Redis Object類(lèi)型與映射

Redis內(nèi)部每一種數(shù)據(jù)類(lèi)型都是對(duì)象化的,也就是我們所說(shuō)的5種數(shù)據(jù)類(lèi)型其實(shí)內(nèi)部都會(huì)對(duì)應(yīng)到Redis Object對(duì)象,然后再由Redis Object來(lái)包裝具體的存儲(chǔ)數(shù)據(jù)結(jié)構(gòu)和編碼。

  1. typedef struct redisObject { 
  2.     unsigned type:4; 
  3.     unsigned encoding:4; 
  4.     unsigned lru:REDIS_LRU_BITS;  
  5.     int refcount; 
  6.     void *ptr; 
  7. } robj; 

這是一個(gè)很OO的設(shè)計(jì),redisObject->type是5種數(shù)據(jù)類(lèi)型之一,redisObject->encoding是這個(gè)數(shù)據(jù)類(lèi)型所使用的數(shù)據(jù)結(jié)構(gòu)和編碼。

我們看下Redis提供的5種數(shù)據(jù)類(lèi)型與每一種數(shù)據(jù)類(lèi)型對(duì)應(yīng)的存儲(chǔ)數(shù)據(jù)結(jié)構(gòu)和編碼。

  1. /* Object types */ 
  2. #define REDIS_STRING 0 
  3. #define REDIS_LIST 1 
  4. #define REDIS_SET 2 
  5. #define REDIS_ZSET 3 
  6. #define REDIS_HASH 4 
  7.  
  8. #define REDIS_ENCODING_RAW 0      
  9. #define REDIS_ENCODING_INT 1     
  10. #define REDIS_ENCODING_HT 2 
  11. #define REDIS_ENCODING_ZIPMAP 3 
  12. #define REDIS_ENCODING_LINKEDLIST 4 
  13. #define REDIS_ENCODING_ZIPLIST 5  
  14. #define REDIS_ENCODING_INTSET 6   
  15. #define REDIS_ENCODING_SKIPLIST 7   
  16. #define REDIS_ENCODING_EMBSTR 8  

REDIS_ENCODING_ZIPMAP 3這個(gè)編碼可以忽略了,在特定的情況下有性能問(wèn)題,在Redis 2.6版本之后已經(jīng)廢棄,為了兼容性保留。

圖是Redis 5種數(shù)據(jù)類(lèi)型與底層數(shù)據(jù)結(jié)構(gòu)和編碼的對(duì)應(yīng)關(guān)系:

 

但是這種對(duì)應(yīng)關(guān)系在每一個(gè)版本中都會(huì)有可能發(fā)生變化,這也是Redis Object的靈活性所在,有著OO的這種多態(tài)性。

  • redisObject->refcount表示當(dāng)前對(duì)象的引用計(jì)數(shù),在Redis內(nèi)部為了節(jié)省內(nèi)存采用了共享對(duì)象的方法,當(dāng)某個(gè)對(duì)象被引用的時(shí)候這個(gè)refcount會(huì)加1,釋放的時(shí)候會(huì)減1。
  • redisObject->lru表示當(dāng)前對(duì)象的空轉(zhuǎn)時(shí)長(zhǎng),也就是idle time,這個(gè)時(shí)間會(huì)是Redis lru算法用來(lái)釋放對(duì)象的時(shí)間依據(jù)??梢酝ㄟ^(guò)OBJECT idletime命令查看某個(gè)key的空轉(zhuǎn)時(shí)長(zhǎng)lru時(shí)間。

二、Redis內(nèi)存管理策略

Redis在服務(wù)端分別為不同的db index維護(hù)一個(gè)dict,這個(gè)dict稱(chēng)為key space鍵空間。每一個(gè)RedisClient只能屬于一個(gè)db index,在Redis服務(wù)端會(huì)維護(hù)每一個(gè)鏈接的RedisClient。

  1. typedef struct redisClient { 
  2.     uint64_t id; 
  3.     int fd; 
  4.     redisDb *db; 
  5. } redisClient; 

在服務(wù)端每一個(gè)Redis客戶(hù)端都會(huì)有一個(gè)指向redisDb的指針。

  1. typedef struct redisDb { 
  2.     dict *dict; 
  3.     dict *expires; 
  4.     dict *blocking_keys; 
  5.     dict *ready_keys; 
  6.     dict *watched_keys; 
  7.     struct evictionPoolEntry *eviction_pool; 
  8.     int id; 
  9.     long long avg_ttl; 
  10. } redisDb; 

key space鍵空間就是這里的redisDb->dict。redisDb->expires是維護(hù)所有鍵空間的每一個(gè)key的過(guò)期時(shí)間。

1、鍵過(guò)期時(shí)間、生存時(shí)間

對(duì)于一個(gè)key我們可以設(shè)置它多少秒、毫秒之后過(guò)期,也可以設(shè)置它在某個(gè)具體的時(shí)間點(diǎn)過(guò)期,后者是一個(gè)時(shí)間戳。例如:

  • EXPIRE命令可以設(shè)置某個(gè)key多少秒之后過(guò)期;
  • PEXPIRE命令可以設(shè)置某個(gè)key多少毫秒之后過(guò)期;
  • EXPIREAT命令可以設(shè)置某個(gè)key在多少秒時(shí)間戳之后過(guò)期;
  • PEXPIREAT命令可以設(shè)置某個(gè)key在多少毫秒時(shí)間戳之后過(guò)期;
  • PERSIST命令可以移除鍵的過(guò)期時(shí)間。

其實(shí)上述命令最終都會(huì)被轉(zhuǎn)換成對(duì)PEXPIREAT命令。在redisDb->expires指向的key字典中維護(hù)著一個(gè)到期的毫秒時(shí)間戳。

TTL、PTTL可以通過(guò)這兩個(gè)命令查看某個(gè)key的過(guò)期秒、毫秒數(shù)。

Redis內(nèi)部有一個(gè)事件循環(huán),這個(gè)事件循環(huán)會(huì)檢查鍵的過(guò)期時(shí)間是否小于當(dāng)前時(shí)間,如果小于則會(huì)刪除這個(gè)鍵。

2、過(guò)期鍵刪除策略

在使用Redis的時(shí)候我們最關(guān)心的就是鍵是如何被刪除的,如何高效準(zhǔn)時(shí)地刪除某個(gè)鍵。其實(shí)Redis提供了兩個(gè)方案來(lái)完成這件事情:惰性刪除、定期刪除雙重刪除策略。

惰性刪除:當(dāng)我們?cè)L問(wèn)某個(gè)key的時(shí)候,Redis會(huì)檢查它是否過(guò)期。

  1. robj *lookupKeyRead(redisDb *db, robj *key) { 
  2.     robj *val; 
  3.  
  4.     expireIfNeeded(db,key); 
  5.     val = lookupKey(db,key); 
  6.     if (val == NULL
  7.         server.stat_keyspace_misses++; 
  8.     else 
  9.         server.stat_keyspace_hits++; 
  10.     return val; 
  11.  
  12. int expireIfNeeded(redisDb *db, robj *key) { 
  13.     mstime_t when = getExpire(db,key); 
  14.     mstime_t now; 
  15.  
  16.     if (when < 0) return 0; /* No expire for this key */ 
  17.  
  18.     if (server.loading) return 0; 
  19.  
  20.     now = server.lua_caller ? server.lua_time_start : mstime(); 
  21.     if (server.masterhost != NULLreturn now > when
  22.  
  23.     /* Return when this key has not expired */ 
  24.     if (now <= whenreturn 0; 
  25.  
  26.     /* Delete the key */ 
  27.     server.stat_expiredkeys++; 
  28.     propagateExpire(db,key); 
  29.     notifyKeyspaceEvent(REDIS_NOTIFY_EXPIRED,"expired",key,db->id); 
  30.     return dbDelete(db,key); 

定期刪除:Redis通過(guò)事件循環(huán),周期性地執(zhí)行key的過(guò)期刪除動(dòng)作。

  1. int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) { 
  2.     /* Handle background operations on Redis databases. */ 
  3.     databasesCron(); 
  4.  
  5. void databasesCron(void) { 
  6.     /* Expire keys by random sampling. Not required for slaves 
  7.      * as master will synthesize DELs for us. */ 
  8.     if (server.active_expire_enabled && server.masterhost == NULL
  9.         activeExpireCycle(ACTIVE_EXPIRE_CYCLE_SLOW); 

要注意的是:

  • 惰性刪除是每次只要有讀取、寫(xiě)入都會(huì)觸發(fā)惰性刪除代碼;
  • 定期刪除是由Redis EventLoop來(lái)觸發(fā)的。Redis內(nèi)部很多維護(hù)性工作都是基于EventLoop。

3、AOF、RDB處理過(guò)期鍵策略

既然鍵會(huì)隨時(shí)存在過(guò)期問(wèn)題,那么涉及到持久化Redis是如何幫我們處理的?

當(dāng)Redis使用RDB方式持久化時(shí),每次持久化的時(shí)候就會(huì)檢查這些即將被持久化的key是否已經(jīng)過(guò)期,如果過(guò)期將直接忽略,持久化那些沒(méi)有過(guò)期的鍵。

當(dāng)Redis作為master主服務(wù)器啟動(dòng)的時(shí)候,載入rdb持久化鍵時(shí)也會(huì)檢查這些鍵是否過(guò)期,將忽略過(guò)期的鍵,只載入沒(méi)過(guò)期的鍵。

當(dāng)Redis使用AOF方式持久化時(shí),每次遇到過(guò)期的key Redis會(huì)追加一條DEL命令到AOF文件,也就是說(shuō)只要我們順序載入執(zhí)行AOF命令文件就會(huì)刪除過(guò)期的鍵。

如果Redis作為從服務(wù)器啟動(dòng)的話(huà),它一旦與master主服務(wù)器建立鏈接就會(huì)清空所有數(shù)據(jù)進(jìn)行完整同步。當(dāng)然新版本的Redis支持SYCN2的半同步,如果是已經(jīng)建立了master/slave主從同步之后,主服務(wù)器會(huì)發(fā)送DEL命令給所有從服務(wù)器執(zhí)行刪除操作。

4、Redis LRU算法

在使用Redis的時(shí)候我們會(huì)設(shè)置maxmemory選項(xiàng),64位的默認(rèn)是0不限制。線(xiàn)上的服務(wù)器必須要設(shè)置的,要不然很有可能導(dǎo)致Redis宿主服務(wù)器直接內(nèi)存耗盡,最后鏈接都上不去。

所以基本要設(shè)置兩個(gè)配置:

  • maxmemory最大內(nèi)存閾值;
  • maxmemory-policy到達(dá)閾值的執(zhí)行策略。

可以通過(guò)CONFIG GET maxmemory/maxmemory-policy分別查看這兩個(gè)配置值,也可以通過(guò)CONFIG SET去分別配置。

  • maxmemory-policy有一組配置,可以用在很多場(chǎng)景下:
  • noeviction:客戶(hù)端嘗試執(zhí)行會(huì)讓更多內(nèi)存被使用的命令直接報(bào)錯(cuò);
  • allkeys-lru:在所有key里執(zhí)行l(wèi)ru算法;
  • volatile-lru:在所有已經(jīng)過(guò)期的key里執(zhí)行l(wèi)ru算法;
  • allkeys-random:在所有key里隨機(jī)回收;
  • volatile-random:在已經(jīng)過(guò)期的key里隨機(jī)回收;
  • volatile-ttl:回收已經(jīng)過(guò)期的key,并且優(yōu)先回收存活時(shí)間(TTL)較短的鍵。

關(guān)于cache的命中率可以通過(guò)info命令查看鍵空間的命中率和未命中率。

  1. # Stats 
  2. keyspace_hits:33 
  3. keyspace_misses:5 

maxmemory在到達(dá)閾值的時(shí)候會(huì)采用一定的策略去釋放內(nèi)存,這些策略我們可以根據(jù)自己的業(yè)務(wù)場(chǎng)景來(lái)選擇,默認(rèn)是noeviction 。

Redis LRU算法有一個(gè)取樣的優(yōu)化機(jī)制,可以通過(guò)一定的取樣因子來(lái)加強(qiáng)回收的key的準(zhǔn)確度。CONFIG GET maxmemory-samples查看取樣配置,具體可以參考更加詳細(xì)的文章。

三、Redis持久化方式

Redis本身提供持久化功能,有兩種持久化機(jī)制,一種是數(shù)據(jù)持久化RDB,一種是命令持久化AOF,這兩種持久化方式各有優(yōu)缺點(diǎn),也可以組合使用。一旦組合使用,Redis在載入數(shù)據(jù)的時(shí)候會(huì)優(yōu)先載入aof文件,只有當(dāng)AOF持久化關(guān)閉的時(shí)候才會(huì)載入rdb文件。

1、RDB(Redis DataBase)

RDB是Redis數(shù)據(jù)庫(kù),Redis會(huì)根據(jù)一個(gè)配置來(lái)觸發(fā)持久化:

  1. #save <seconds> <changes> 
  2.  
  3. save 900 1 
  4. save 300 10 
  5. save 60 10000 
  6.  
  7. CONFIG GET save 
  8. 1) "save" 
  9. 2) "3600 1 300 100 60 10000" 

表示在多少秒之類(lèi)的變化次數(shù),一旦達(dá)到這個(gè)觸發(fā)條件Redis將觸發(fā)持久化動(dòng)作。

Redis在執(zhí)行持久化的時(shí)候有兩種模式BGSAVE、SAVE:

  • BGSAVE是后臺(tái)保存,Redis會(huì)fork出一個(gè)子進(jìn)程來(lái)處理持久化,不會(huì)block用戶(hù)的執(zhí)行請(qǐng)求;
  • SAVE則會(huì)block用戶(hù)執(zhí)行請(qǐng)求。
  1. struct redisServer { 
  2. long long dirty;/* Changes to DB from the last save */ 
  3. time_t lastsave; /* Unix time of last successful save */ 
  4. long long dirty_before_bgsave; 
  5. pid_t rdb_child_pid;/* PID of RDB saving child */ 
  6. struct saveparam *saveparams; /* Save points array for RDB */ 
  7.  
  8. struct saveparam { 
  9.     time_t seconds; 
  10.     int changes; 
  11. }; 

RedisServer包含的信息很多,其中就包含了有關(guān)于RDB持久化的信息。

redisServer->dirty至上次save到目前為止的change數(shù)。redisServer->lastsave上次save時(shí)間。

saveparam struct保存了我們通過(guò)save命令設(shè)置的參數(shù),time_t是個(gè)long時(shí)間戳。

  1. typedef __darwin_time_t     time_t; 
  2.  
  3. typedef long    __darwin_time_t;    /* time() */ 
  4.  
  5. int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) { 
  6.          for (j = 0; j < server.saveparamslen; j++) { 
  7.             struct saveparam *sp = server.saveparams+j; 
  8.             if (server.dirty >= sp->changes && 
  9.                 server.unixtime-server.lastsave > sp->seconds && 
  10.                 (server.unixtime-server.lastbgsave_try > 
  11.                  REDIS_BGSAVE_RETRY_DELAY || 
  12.                  server.lastbgsave_status == REDIS_OK)) 
  13.             { 
  14.                 redisLog(REDIS_NOTICE,"%d changes in %d seconds. Saving..."
  15.                     sp->changes, (int)sp->seconds); 
  16.                 rdbSaveBackground(server.rdb_filename); 
  17.                 break; 
  18.             } 
  19.          } 

Redis事件循環(huán)會(huì)周期性的執(zhí)行serverCron方法,這段代碼會(huì)循環(huán)遍歷server.saveparams參數(shù)鏈表。

如果server.dirty大于等于我們參數(shù)里配置的變化并且server.unixtime-server.lastsave大于參數(shù)里配置的時(shí)間,并且server.unixtime-server.lastbgsave_try減去bgsave重試延遲時(shí)間,或者當(dāng)前server.lastbgsave_status== REDIS_OK則執(zhí)行rdbSaveBackground方法。

2、AOF(Append-only file)

AOF持久化是采用對(duì)文件進(jìn)行追加對(duì)方式進(jìn)行,每次追加都是Redis處理的命令。有點(diǎn)類(lèi)似command sourcing命令溯源的模式,只要我們可以將所有的命令按照?qǐng)?zhí)行順序在重放一遍就可以還原最終的Redis內(nèi)存狀態(tài)。

AOF持久化最大的優(yōu)勢(shì)是可以縮短數(shù)據(jù)丟失的間隔,做到秒級(jí)的丟失率。RDB會(huì)丟失上一個(gè)保存周期到目前的所有數(shù)據(jù),只要沒(méi)有觸發(fā)save命令設(shè)置的save seconds changes閾值數(shù)據(jù)就會(huì)一直不被持久化。

  1. struct redisServer { 
  2.  /* AOF buffer, written before entering the event loop */ 
  3.  sds aof_buf; 
  4.  } 
  5.  
  6. struct sdshdr { 
  7.     unsigned int len; 
  8.     unsigned int free
  9.     char buf[]; 
  10. }; 

aof_buf是命令緩存區(qū),采用sds結(jié)構(gòu)緩存,每次當(dāng)有命令被執(zhí)行當(dāng)時(shí)候都會(huì)寫(xiě)一次到aof_buf中。有幾個(gè)配置用來(lái)控制AOF持久化的機(jī)制。

  1. appendonly no  
  2. appendfilename "appendonly.aof" 

appendonly用來(lái)控制是否開(kāi)啟AOF持久化,appendfilename用來(lái)設(shè)置aof文件名。

  1. appendfsync always 
  2. appendfsync everysec 
  3. appendfsync no 

appendfsync用來(lái)控制命令刷盤(pán)機(jī)制?,F(xiàn)在操作系統(tǒng)都有文件cache/buffer的概念,所有的寫(xiě)入和讀取都會(huì)走cache/buffer,并不會(huì)每次都同步刷盤(pán),因?yàn)檫@樣性能一定會(huì)受影響。所以Redis也提供了這個(gè)選項(xiàng)讓我們來(lái)自己根據(jù)業(yè)務(wù)場(chǎng)景控制。

  • always:每次將aof_buf命令寫(xiě)入aof文件并且執(zhí)行實(shí)時(shí)刷盤(pán)。
  • everysec:每次將aof_buf命令寫(xiě)入aof文件,但是每隔一秒執(zhí)行一次刷盤(pán)。
  • no:每次將aof_buf命令寫(xiě)入aof文件不執(zhí)行刷盤(pán),由操作系統(tǒng)來(lái)自行控制。

AOF也是采用后臺(tái)子進(jìn)程的方式進(jìn)行,與主進(jìn)程共享數(shù)據(jù)空間也就是aof_buf,但是只要開(kāi)始了AOF子進(jìn)程之后Redis事件循環(huán)文件事件處理器會(huì)將之后的命令寫(xiě)入另外一個(gè)aof_buf,這樣就可以做到平滑的切換。

AOF會(huì)不斷的追加命令進(jìn)aof文件,隨著時(shí)間和并發(fā)量的加大aof文件會(huì)極速膨脹,所以有必要對(duì)這個(gè)文件大小進(jìn)行優(yōu)化。Redis基于rewrite重寫(xiě)對(duì)文件進(jìn)行壓縮。

  1. no-appendfsync-on-rewrite no/yes 
  2. auto-aof-rewrite-percentage 100 
  3. auto-aof-rewrite-min-size 64mb 

no-appendfsync-on-rewrite控制是否在bgrewriteaof的時(shí)候還需要進(jìn)行命令追加,如果追加可能會(huì)出現(xiàn)磁盤(pán)IO跑高現(xiàn)象。

上面說(shuō)過(guò),當(dāng)AOF進(jìn)程在執(zhí)行的時(shí)候原來(lái)的事件循環(huán)還會(huì)正常的追加命令進(jìn)aof文件,同時(shí)還會(huì)追加命令進(jìn)另外一個(gè)aof_buf,用來(lái)做新aof文件的重寫(xiě)。這是兩條并行的動(dòng)作,如果我們?cè)O(shè)置成yes就不追加原來(lái)的aof_buf 因?yàn)樾碌腶of文件已經(jīng)包含了之后進(jìn)來(lái)的命令。

auto-aof-rewrite-percentage和auto-aof-rewrite-min -size64mb這兩個(gè)配置前者是文件增長(zhǎng)百分比來(lái)進(jìn)行rewrite,后者是按照文件大小增長(zhǎng)進(jìn)行rewrite。

責(zé)任編輯:武曉燕 來(lái)源: DBAplus社群
相關(guān)推薦

2023-11-12 21:49:10

Redis數(shù)據(jù)庫(kù)

2023-09-13 08:08:41

Redis消息隊(duì)列

2021-02-07 22:24:59

Redis數(shù)據(jù)存儲(chǔ)

2018-12-10 09:05:06

Redis數(shù)據(jù)庫(kù)百億數(shù)據(jù)量

2023-04-11 08:00:56

Redis類(lèi)型編碼

2017-05-11 11:59:12

MySQL數(shù)據(jù)結(jié)構(gòu)算法原理

2020-07-14 08:53:43

Redis數(shù)據(jù)存儲(chǔ)

2023-03-06 08:40:43

RedisListJava

2019-06-21 15:20:05

Redis數(shù)據(jù)結(jié)構(gòu)數(shù)據(jù)庫(kù)

2024-01-26 06:42:05

Redis數(shù)據(jù)結(jié)構(gòu)

2020-06-29 07:44:36

Redis

2019-06-12 22:51:57

Redis軟件開(kāi)發(fā)

2021-10-29 11:27:52

鏈表數(shù)據(jù)結(jié)構(gòu)算法

2018-06-06 08:54:23

數(shù)據(jù)結(jié)構(gòu)存儲(chǔ)

2021-12-27 11:30:51

數(shù)據(jù)結(jié)構(gòu)算法動(dòng)態(tài)規(guī)劃

2019-04-17 15:35:37

Redis數(shù)據(jù)庫(kù)數(shù)據(jù)結(jié)構(gòu)

2019-09-02 09:48:39

Redis數(shù)據(jù)結(jié)構(gòu)對(duì)象

2019-10-29 08:59:16

Redis底層數(shù)據(jù)

2022-03-11 07:37:39

hashCodeHashMap數(shù)據(jù)結(jié)構(gòu)

2020-03-06 15:20:34

Redis存儲(chǔ)String
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)