Redis設(shè)計(jì)與實(shí)現(xiàn)讀書(shū)筆記

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

字符串(SDS)

數(shù)據(jù)結(jié)構(gòu)為如下:

struct sdshdr{
//記錄bug中已經(jīng)使用了的長(zhǎng)度
  int len;
//記錄buf中沒(méi)有使用的長(zhǎng)度
  int free;
//字節(jié)數(shù)組,用于保存字符串 
  char buf[];
}

優(yōu)點(diǎn):

  • 可以以常數(shù)復(fù)雜度獲取字符串的長(zhǎng)度,因?yàn)橛涗浟俗址拈L(zhǎng)度。
  • 通過(guò)free空間可以減少字符串修改時(shí)帶來(lái)的內(nèi)存重新分配次數(shù)。
    ??Redis里修改sdshdr的時(shí)候會(huì)對(duì)buf進(jìn)行擴(kuò)容,擴(kuò)容的方式當(dāng)buf小于1MB的時(shí)候是翻倍擴(kuò)容,當(dāng)大于1MB的時(shí)候是以1MB大小進(jìn)行擴(kuò)容。當(dāng)需要對(duì)buf進(jìn)行減字符操作時(shí),不會(huì)對(duì)buf數(shù)組進(jìn)行縮減,而是通過(guò)len與free的改值來(lái)實(shí)現(xiàn)。稱為惰性空間釋放,這樣可以避免內(nèi)存重新分配。
  • 內(nèi)部API杜絕了緩沖區(qū)溢出。
  • 二進(jìn)制安全的,因?yàn)閘en記錄了buf的長(zhǎng)度,而不是像C語(yǔ)言一樣通過(guò)一個(gè)空字符來(lái)判斷是否到字符的未位。
  • 在buf里存字符的時(shí)候還是加上了空字符串的,這樣可以兼容部分C字符串的函數(shù)。

鏈表

數(shù)據(jù)結(jié)構(gòu)由兩部分組成listNode與list,分別如下:

typedef struct listNode{
//前置節(jié)點(diǎn)
  struct listNode *prev;
//后置節(jié)點(diǎn)
  struct listNode *next;
// 節(jié)點(diǎn)的值
  void *value;
}listNode;
typedef struct list{
//鏈表頭節(jié)點(diǎn)
  listNode *head;
//鏈表尾節(jié)點(diǎn)
  listNode *tail;
//鏈表長(zhǎng)度
  unsigned long len;
}list;

特點(diǎn):

  • 節(jié)點(diǎn)通過(guò)prev和next指針實(shí)現(xiàn)雙端鏈表
  • 表頭節(jié)點(diǎn)的pre與表屬節(jié)點(diǎn)的next都指向NULL,不是個(gè)循環(huán)鏈表,對(duì)鏈表的訪問(wèn)會(huì)以NULL結(jié)束
  • list帶表頭與表尾指針,程序可以快速的獲取表頭與表尾。
    +通過(guò)len屬性可以快帶的獲取鏈表的長(zhǎng)度。

字典(HashMap)

??Redis底層的數(shù)據(jù)庫(kù)采用的就是這種結(jié)構(gòu),還有哈希鍵的底層實(shí)現(xiàn)之一也是采用HashMap這種結(jié)構(gòu)。 哈希表的節(jié)點(diǎn)結(jié)構(gòu)如下:

typedef struct dictEntry{
//鍵
  void *key;
//值 
union{
  void *val;
  uint64_t u64;
  int64_t s64;
}v;
//指向一個(gè)哈希表的節(jié)點(diǎn)
struct dictEnty *next;
}dictEnty 

哈希表的結(jié)構(gòu)定義如下:

typedef struct dictht{
//哈希表的數(shù)組
  dictEntry **table;
//哈希表的大小
  unsigned long size;
//哈希表的掩碼,用于計(jì)算索引值
  unsigned long sizemask;
//已有的節(jié)點(diǎn)數(shù)
  unsigned long used;
}dictht

哈希表通過(guò)數(shù)組來(lái)存儲(chǔ)數(shù)據(jù),通過(guò)sizemask與key的hashcode來(lái)計(jì)算數(shù)據(jù)存儲(chǔ)的位置。當(dāng)hashcode值相同時(shí),采用的是鏈表的方式進(jìn)行存儲(chǔ)。
??Redis的字典設(shè)計(jì)成有兩個(gè)dictht的數(shù)組結(jié)構(gòu),這樣設(shè)計(jì)的好處是可以采用漸進(jìn)的方式對(duì)字典數(shù)據(jù)進(jìn)行擴(kuò)容。所謂漸進(jìn)式的進(jìn)行rehash指的是在rehash的過(guò)程中并不是一步完成的,在rehash的時(shí)候同時(shí)也能對(duì)外提供添加,查找,更新與刪除的功能。只是在這些功能完成的時(shí)候會(huì)將相應(yīng)的數(shù)據(jù)從dictht的一個(gè)哈希表移動(dòng)到新的dictht表中。rehash過(guò)程中增,刪,改,查這四個(gè)操作,只有增加數(shù)據(jù)是在新的dictht中,另外三個(gè)操作都要同時(shí)操作兩個(gè)dictht。
優(yōu)點(diǎn):

  • Redis的字典是數(shù)據(jù)庫(kù)的底層實(shí)現(xiàn),采用雙哈希表的設(shè)計(jì)能在擴(kuò)容時(shí)還能對(duì)象提供相應(yīng)的數(shù)據(jù)查找,修改,增加與刪除的功能。這是Redis哈希結(jié)構(gòu)設(shè)計(jì)的亮點(diǎn)之一。
  • Redis采用鏈表的方式來(lái)解決hashcode沖突的問(wèn)題。

跳躍表

??跳躍表是一種有序數(shù)據(jù)結(jié)構(gòu),通過(guò)在每個(gè)節(jié)點(diǎn)維持多個(gè)指向其它節(jié)點(diǎn)的指針來(lái)達(dá)到快速訪問(wèn)其它節(jié)點(diǎn)的目地。在Redis里有序節(jié)點(diǎn)采用的是這種數(shù)據(jù)結(jié)構(gòu)來(lái)進(jìn)行實(shí)現(xiàn)。跳躍表節(jié)點(diǎn)的數(shù)據(jù)結(jié)構(gòu)如下:

typedef struct zskiplistNode{
//后退指針
  struct zskiplistNode *backward;
//分值,節(jié)點(diǎn)以這個(gè)值從小到大進(jìn)行排序
  double score;
//成員對(duì)象
  robj *obj;
//層
struct zskiplistLevel{
     //前進(jìn)指針
    struct zskiplistNode *forward;
    //跨度
    unsigned int span;
  } level[];
}zskiplistNode;

這里需要重點(diǎn)關(guān)注的是 level[]數(shù)組,他用于表示層信息,層里包括兩部分信息,指向的節(jié)點(diǎn)指針以及當(dāng)前節(jié)點(diǎn)與指向節(jié)點(diǎn)的跨度信息。有了節(jié)點(diǎn)就可以組成一張?zhí)S表了,跟鏈表一樣,Redis通過(guò)提供zskiplist結(jié)構(gòu)來(lái)操作跳躍表的信息,數(shù)據(jù)結(jié)構(gòu)如下:

typedef struct zskiplist{
  //表頭節(jié)點(diǎn)和表尾節(jié)點(diǎn)
  struct zskiplistNode *header, *tail;
//表中的節(jié)點(diǎn)數(shù)量
  unsigned long length;
//表中層數(shù)最大的節(jié)點(diǎn)的層數(shù)
int level;
}zskiplist;

整數(shù)集合

??整數(shù)集合是集合鍵的底層實(shí)現(xiàn)之一,條件為:1:集合中只包含整數(shù),2:集合的元素?cái)?shù)量不多。數(shù)據(jù)結(jié)構(gòu)的定義如下:

typedef struct intset{
//編碼方式
  uint32_t encoding;
//集合包含的元素?cái)?shù)量
uint32_t length;
//保存元素的數(shù)組
int8_t contents[];
}

上面的定義contents數(shù)組聲明的是int8_t類型,實(shí)際上contents保存的類型取決于encoding屬性。當(dāng)向整數(shù)集合里增加一個(gè)超過(guò)當(dāng)前編碼的值的時(shí)候,會(huì)引發(fā)升級(jí)操作,所謂的升級(jí)就是對(duì)當(dāng)前的整數(shù)進(jìn)行擴(kuò)容。這樣做的好處主要是為了節(jié)約內(nèi)存。整數(shù)集合的特點(diǎn)有下面幾個(gè):

  • 整數(shù)集合是有序無(wú)重復(fù)的特點(diǎn)。
  • 在有需要的時(shí)候,會(huì)根據(jù)增加數(shù)據(jù)的類型對(duì)數(shù)據(jù)進(jìn)行升級(jí)操作。
  • 整數(shù)集合數(shù)據(jù)量不大,所以升級(jí)操作耗時(shí)不會(huì)太多
  • 整數(shù)集合不支持降級(jí)操作

壓縮列表

??壓縮列表是列表鍵和哈希鍵的底層實(shí)現(xiàn)之一。壓縮列表是Redis為了節(jié)約內(nèi)存而開(kāi)發(fā)的,是由一系列特殊編碼的連續(xù)內(nèi)存塊組成的順序型 數(shù)據(jù)結(jié)構(gòu)。壓縮列表的組成如下圖所示:

上圖中括號(hào)里記錄的是對(duì)應(yīng)位置所占用的字節(jié)數(shù)。里面的entry的值結(jié)構(gòu)如下圖所示:
因?yàn)閜revious_entry_length的長(zhǎng)度不固定,會(huì)有連鎖更新的情況發(fā)生。

對(duì)象

??Redis數(shù)據(jù)庫(kù)基于上面的數(shù)據(jù)結(jié)構(gòu)創(chuàng)建了一個(gè)對(duì)象系統(tǒng),這個(gè)系統(tǒng)包括:字符串對(duì)象,列表對(duì)象,哈希對(duì)象,集合對(duì)象,有序集合對(duì)象。在 Redis里每新建一個(gè)鍵值對(duì)會(huì)創(chuàng)建兩個(gè)對(duì)象,分別為鍵對(duì)象與值對(duì)象。每個(gè)對(duì)象都由redisObject結(jié)構(gòu)表示:

typedef struct redisObject{
//類型
  unsigned type;
//編碼,底層數(shù)據(jù)結(jié)構(gòu)
  unsigned  encoding;
//指向底層實(shí)現(xiàn)數(shù)據(jù)結(jié)構(gòu)的指針
  void *ptr;
//引用計(jì)數(shù)器 通過(guò)OBJECT REFCOUNT 可以查看
  int refcount;
//空轉(zhuǎn)時(shí)長(zhǎng) 通過(guò)OBJECT IDLETIME 可以查看
unsigned lru; 
}robj;

其中type值只有五種類型,分別為:string,list,hash,set,zset,可以通過(guò)TYPE key得到對(duì)象的類型。而encoding用于標(biāo)識(shí)底層的數(shù)據(jù)結(jié)構(gòu),可能通過(guò)OBJCET ENCODING key來(lái)查看某個(gè)值對(duì)象的底層結(jié)構(gòu)。encoding可能的輸出為:int,embstr, raw,hashtable,linkedlist,ziplist,intset,skiplist。

字符串對(duì)象

??字符串的編碼可能是int, embstr, raw這三種之中的一種。為int的情況是存的這個(gè)整數(shù)值可以用long類型(浮點(diǎn)數(shù)不在這個(gè)范圍內(nèi))來(lái)表示。當(dāng)存的值長(zhǎng)度小于39字節(jié)的時(shí)候,采用的是embstr結(jié)構(gòu)來(lái)存儲(chǔ),其它情況采用的是raw方式存儲(chǔ)。embstr與raw的區(qū)別為:embstr專門用于存儲(chǔ)短字符串,主要是為了在創(chuàng)建對(duì)象的時(shí)候只需要調(diào)用一次內(nèi)存分配函數(shù)。embstr的結(jié)構(gòu)如下圖所示:

int類型的數(shù)據(jù)在操作的時(shí)候比如變成了浮點(diǎn)數(shù)會(huì)轉(zhuǎn)成raw類型的編碼。embstr只要有修改的操作,無(wú)論長(zhǎng)度多少都會(huì)變成raw類型的編碼

列表對(duì)象(里面的元素允許重復(fù))

??列表對(duì)象的編碼可能是雙端鏈表或者壓縮列表。當(dāng)列表對(duì)象同時(shí)滿足所有字符串的長(zhǎng)度都小于64字節(jié)且元素?cái)?shù)量小于512個(gè)時(shí),采用的是壓縮列表的方式。其它情況采用雙端列表來(lái)進(jìn)行存儲(chǔ)。

哈希對(duì)象

??哈希對(duì)象的編碼可以是壓縮列表或者h(yuǎn)ashtable 。只有當(dāng)哈希對(duì)象保存的鍵值對(duì)的鍵和值的字符串都小于64字節(jié)且對(duì)象 保存的鍵數(shù)量小于512個(gè),才使用壓縮列表的方式進(jìn)行存儲(chǔ),其它情況采用的是hashtable。當(dāng)采用壓縮列表來(lái)存儲(chǔ)時(shí)有如下特點(diǎn):

  • 保存同一個(gè)鍵值對(duì)的兩個(gè)節(jié)點(diǎn)總是緊挨在一起,鍵的節(jié)點(diǎn)在前,值的節(jié)點(diǎn)在后。
  • 增加鍵值對(duì)放到壓縮列表表尾。

集合對(duì)象

??集合對(duì)象的編碼可以是intset或者h(yuǎn)ashtable來(lái)實(shí)現(xiàn)。使用intset的條件為集合中的所有元素全為整數(shù)值且對(duì)象保存的元素?cái)?shù)量不超過(guò)512 個(gè)。采用hashtable的方式來(lái)保存的數(shù)據(jù)值為NULL,

有序集合對(duì)象

??有序集合對(duì)象可以采用壓縮列表或者跳躍表加字典的方式來(lái)實(shí)現(xiàn)。
當(dāng)保存的元素小于128個(gè)且元素長(zhǎng)度都小于64個(gè)字節(jié)采壓縮列表的方式來(lái)保存,壓縮列表內(nèi)在元素按分值大小進(jìn)行排序,分值小的靠近表頭,每個(gè)元素占用兩個(gè)節(jié)點(diǎn),第一個(gè)節(jié)點(diǎn)保存值,第二個(gè)節(jié)點(diǎn)保存分值。其它情況用用的是zset的結(jié)構(gòu)來(lái)保存數(shù)據(jù)。zset結(jié)構(gòu)如下:

typedef struct zset{
  //跳躍表指針
  zskiplist *zsl;
//字典
dict *dict;
}zset;

通過(guò)跳躍表可以對(duì)有序集合進(jìn)行范圍操作,而通過(guò)字典建立了元素到分值的映射,通過(guò)字典Redis可以快速的查找某個(gè)元素的分值。有序集合中每個(gè)元素都是字符串對(duì)象,每個(gè)元素的分值都是double類型的浮點(diǎn)數(shù)。雖然zset同時(shí)采用跳躍表與字典來(lái)保存有序集合,但他們會(huì)通過(guò)指針來(lái)共享相同元素的成員與分值

鍵的生存時(shí)間

??Redis里可能通過(guò) EXPIRE,PEXPIRE,EXPIREAT,PEXPIREAT四個(gè)命令來(lái)設(shè)置鍵的過(guò)期時(shí)間,其中以P開(kāi)頭的命令設(shè)置的時(shí)間單位是毫秒。EXPIRE,PEXPIRE,EXPIREAT內(nèi)部最終會(huì)轉(zhuǎn)成PEXPIREAT命令。在Redis內(nèi)部通過(guò)字典(expires變量)保存設(shè)置了過(guò)期時(shí)間的對(duì)象,其中字典中的鍵是一個(gè)指針,指向鍵空間中的某個(gè)鍵對(duì)象,字典的值為一個(gè)long類型的整數(shù),保存了過(guò)期時(shí)間。PERSIST命令用移除鍵的過(guò)期時(shí)間,TTL與PTTL命令用于查看鍵還有多長(zhǎng)時(shí)間過(guò)期。上面幾個(gè)命令都是圍繞著redisDB里的expire變量來(lái)進(jìn)行操作的。
??Redis對(duì)于過(guò)期鍵的處理采用的是惰性刪除與定期刪除兩種方式。惰性刪除通過(guò)攔截所有讀寫(xiě)請(qǐng)求,判斷鍵是否有過(guò)期,如果過(guò)期則執(zhí)行刪除邏輯。定期刪除是通過(guò)Redis的定時(shí)任務(wù)來(lái)完成的。

持久化

RDB持久化

??Redis的是內(nèi)存數(shù)據(jù)庫(kù),一旦進(jìn)程退出,數(shù)據(jù)狀態(tài)便沒(méi)了。Redis為了解決這個(gè)問(wèn)題,提供了RDB持久化方案??梢酝ㄟ^(guò)SAVE或者BGSAVE兩個(gè)命令來(lái)手動(dòng)生成RDB文件。兩個(gè)命令的區(qū)別為SAVE命令是阻塞的,BGSAVE是通過(guò)子進(jìn)程來(lái)生成RDB。Redis沒(méi)有提供任何載入RDB文件的命令,而是在啟動(dòng)的時(shí)候會(huì)直接載入RDB文件,載入的順序?yàn)锳OF文件優(yōu)先,如果沒(méi)開(kāi)啟AOF功能,則使用RDB文件的方式??梢酝ㄟ^(guò)提供配置讓Redis在滿足一定的條件時(shí)自動(dòng)執(zhí)行BGSAVE命令,配置方式如下:

save 900 1
save 300 10

上面的配置含義為當(dāng)900秒內(nèi)對(duì)數(shù)據(jù)庫(kù)執(zhí)行了一次修改或者300秒內(nèi)對(duì)數(shù)據(jù)庫(kù)執(zhí)行了10次修改便會(huì)執(zhí)行BGSAVE命令。
??Redis通過(guò)在redisServer對(duì)象上保存saveparams數(shù)組對(duì)象,修改記數(shù)器和上次執(zhí)行保存的時(shí)間三個(gè)參數(shù)來(lái)判斷是否執(zhí)行BGSAVE命令。相關(guān)數(shù)據(jù)結(jié)構(gòu)如下:

struct redisServer{
   //記錄了保存條件的數(shù)組
  struct saveparam *saveparams;
//修改記數(shù)器
  long dirty;
//上一次執(zhí)行保存的時(shí)間
time_t lastsave;
//AOF緩沖區(qū)
sds aof_buf;
};

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

AOF持久化

??AOF持久化通過(guò)保存Redis服務(wù)器所執(zhí)行的寫(xiě)命令來(lái)記錄數(shù)據(jù)庫(kù)的狀態(tài)。AOF的持久化實(shí)現(xiàn)可以分為命令追加,文件寫(xiě)入,文件同步三個(gè)步驟。

  • 命令追加:當(dāng)redis執(zhí)行了一個(gè)寫(xiě)命令以后,會(huì)將命令寫(xiě)入到redisServer里的 aof_buf緩沖區(qū)里。這時(shí)候命令并沒(méi)有寫(xiě)入到AOF文件中。
  • 文件寫(xiě)入與同步:Redis服務(wù)器每次接收服務(wù)器的請(qǐng)求后都會(huì)執(zhí)行flushAppendOnlyFile函數(shù),在這個(gè)函數(shù)里會(huì)根據(jù)appendfsync配置的值不同采用不同的處理方式將aop_buf里的內(nèi)容寫(xiě)入到內(nèi)存緩存并同步。appendfsync可以取always,everysec,no。
    1)always表示將緩沖區(qū)的內(nèi)容寫(xiě)入并同步到AOF文件。(理論上可能丟失一次事件循環(huán)命令行的數(shù)據(jù))。
    2)everysec表示每秒鐘同步一次(這樣會(huì)丟失一秒鐘的數(shù)據(jù))。
    3)no表示將數(shù)據(jù)寫(xiě)入到AOF內(nèi)存緩存,但是何時(shí)同步由操作系統(tǒng)決定。

AOF重寫(xiě)

??AOF重寫(xiě)主要是為了解決AOF文件過(guò)大的問(wèn)題。重寫(xiě)的步驟大概如下:
1:創(chuàng)建新的AOF文件
2:遍歷數(shù)據(jù)庫(kù),并逐個(gè)選擇庫(kù)來(lái)進(jìn)行重寫(xiě)
3:忽略過(guò)期的鍵。
4:根據(jù)不同鍵的類型對(duì)鍵進(jìn)行重寫(xiě)
5:如果鍵有過(guò)期時(shí)間,將過(guò)期時(shí)間進(jìn)行重寫(xiě)。
??整個(gè)重寫(xiě)過(guò)程可能需要花費(fèi)大量時(shí)間,而重寫(xiě)的過(guò)程中Redis是能接收客戶端的請(qǐng)求的,而后臺(tái)重寫(xiě)用于解決這個(gè)問(wèn)題。后臺(tái)重寫(xiě)需要解決客戶端在重寫(xiě)期間執(zhí)行的寫(xiě)命令需要寫(xiě)入到新的AOF文件里。
Redis重寫(xiě)是通過(guò)子進(jìn)程來(lái)實(shí)現(xiàn)的,而服務(wù)進(jìn)程需要執(zhí)行以下三個(gè)工作:
1:執(zhí)行客戶端發(fā)送來(lái)的命令。
2:將執(zhí)行后的寫(xiě)命令追加到aof緩沖區(qū)。
3:將執(zhí)行后的寫(xiě)命令追加到AOF重寫(xiě)緩沖區(qū)。
當(dāng)子進(jìn)程完成AOF文件重寫(xiě)后,會(huì)給服務(wù)進(jìn)程發(fā)送信號(hào),服務(wù)進(jìn)程需要將AOF重寫(xiě)緩沖區(qū)的內(nèi)容寫(xiě)入到新的AOF文件里,并用新的AOF文件替換掉舊的AOF文件。這就完成了后臺(tái)AOF文件重寫(xiě),在服務(wù)進(jìn)程接到信息之后的過(guò)程是阻塞的,這大大降低了服務(wù)阻塞的時(shí)間。

多機(jī)數(shù)據(jù)庫(kù)的實(shí)現(xiàn)

復(fù)制

??在Redis中通過(guò)SLAVEOF IP PORT命令,可以讓一臺(tái)服務(wù)器設(shè)為另一臺(tái)服務(wù)器(主服務(wù))的slave。slave服務(wù)器能提供除寫(xiě)入功能外的其它功能,slave服務(wù)器相當(dāng)于主服務(wù)器的一個(gè)鏡像。這一切都是通過(guò)Redis的復(fù)制功能實(shí)現(xiàn)的,復(fù)制功能分為兩步:

  • 同步:將從服務(wù)器的狀態(tài)更新到主服務(wù)器當(dāng)前所處的狀態(tài)。
  • 命令傳播:主服務(wù)器有更新命令時(shí)會(huì)將命令傳給從服務(wù)器,使主從保持?jǐn)?shù)據(jù)一致。

??Redis2.8以后為了提升斷線后重復(fù)制的效率,提供了PSYNC命令來(lái)替換原有的SYNC命令。PSYNC能夠?qū)崿F(xiàn)部分?jǐn)?shù)據(jù)重同步,實(shí)現(xiàn)的原理是主服務(wù)器記錄了1:復(fù)制的偏移量;2:復(fù)制積壓緩沖區(qū)(可以通過(guò)repl-backlog-size設(shè)置大小)3:服務(wù)器運(yùn)行的ID。從服務(wù)器記錄了主服務(wù)器的ID與復(fù)制的偏移量。主服務(wù)器根據(jù)服務(wù)器的ID與偏移量來(lái)決定是否可以進(jìn)行部分重同步。流程如下:

PSYNC執(zhí)行時(shí)可能遇到的情況

在復(fù)制操作剛開(kāi)始的時(shí)候,從服務(wù)器會(huì)成為主服務(wù)器的客戶端,并通過(guò)向主服務(wù)器發(fā)送命令請(qǐng)求執(zhí)行復(fù)制步驟,而在復(fù)制操作的后期,主從服務(wù)器會(huì)互相成為對(duì)方的客戶端 。
主服務(wù)器通過(guò)向從服務(wù)器傳播命令來(lái)更新從服務(wù)器的狀態(tài),保持主從服務(wù)器一致。從服務(wù)器通過(guò)向主服務(wù)器發(fā)送命令進(jìn)行心跳栓測(cè)(每秒發(fā)送一次,會(huì)將當(dāng)前服務(wù)器的復(fù)制偏移量傳入),通過(guò)這種方式檢測(cè)命令丟失情況。

Sentinel

??Sentinel是Redis的高可用解決方案。他會(huì)監(jiān)視Redis主從服務(wù)器,并且在主服務(wù)器下線時(shí),自動(dòng)將主服務(wù)進(jìn)行故障轉(zhuǎn)移,選出新的主服務(wù)器。當(dāng)原來(lái)的服務(wù)器重新啟動(dòng)后,將為成為新主服務(wù)的從服務(wù)器。Sentinel本質(zhì)上是一個(gè)運(yùn)行在特殊模式下的Redis服務(wù)器。Sentinel作為Redis的高可用解決方案會(huì)經(jīng)過(guò)如下幾步:
1:?jiǎn)?dòng)并初使化Sentinel服務(wù)器,在啟動(dòng)后Sentinel服務(wù)保存了Redis的master機(jī)器信息,并做為master機(jī)器的客戶端與master機(jī)器建立了一個(gè)命令連接與訂閱連接,訂閱的是master服務(wù)器的sentinel:hello頻道。
2:Sentinel默認(rèn)會(huì)以每十秒一次的頻率,通過(guò)命令連接向被監(jiān)視的主服務(wù)器發(fā)送info命令。通過(guò)info命令得到的信息可以得到從服務(wù)器的信息,如果在這期間主服務(wù)器的信息有更新,則需要更新主服務(wù)器的信息。
3:根據(jù)上面得到的從服務(wù)器的信息,創(chuàng)建命令連接與訂閱連接到從服務(wù)器。并以每十秒一次的頻率發(fā)送info命令給從服務(wù)器,以便更新Sentinel服務(wù)器保存的從服務(wù)器的信息。
4:在默認(rèn)情況下,Sentinel會(huì)以每?jī)擅胍淮蔚念l率,通過(guò)命令連接向所有被監(jiān)視的主服務(wù)器和從服務(wù)器發(fā)送以下格式的命令:
PUBLISTH sentinel:hello "<s_ip>,<s_port>,<s_runid>,<s_epoch>,<m_ip>,<m_port>,<m_runid>,<m_epoch>,"
其中以s開(kāi)頭的信息表求的是Sentinel服務(wù)器的信息,分別為ip, port, runid,配置紀(jì)元。以m開(kāi)頭的則是主服務(wù)器的信息。
5:Sentinel訂閱了相應(yīng)主服務(wù)器與從服務(wù)器的sentinel:hello頻道,Sentinel發(fā)現(xiàn)這個(gè)信息是自已發(fā)送的將不處理這個(gè)信息。通過(guò)向sentinel:hello發(fā)送信息主要用于發(fā)現(xiàn)其它Sentinel服務(wù)器的存在。
6:當(dāng)發(fā)現(xiàn)有新的Sentinel監(jiān)視相同的主服務(wù)器,Sentinel之間會(huì)互相建立命令連接,但是相互之間不會(huì)建訂閱連接
7:檢測(cè)主觀下線狀態(tài) ,Sentinel會(huì)以每秒一次的頻率向所有與它創(chuàng)建了命令連接的實(shí)例發(fā)送PING命令,并通過(guò)實(shí)例返回的回復(fù)判斷實(shí)例是否在線。當(dāng)實(shí)例(主服務(wù)器,從服務(wù)器,同樣監(jiān)視主服務(wù)器的Sentinel)在Sentinel配置的down-after-milliseconds毫秒內(nèi)連續(xù)返回?zé)o效的回復(fù),則Sentinel主觀判斷服務(wù)器下線。
8:檢查客觀下線狀態(tài):當(dāng)Sentinel主觀判斷服務(wù)器下線后,會(huì)向同樣監(jiān)視這一主服務(wù)器的其它Sentinel進(jìn)行詢問(wèn),看他們是否認(rèn)同主服務(wù)器已經(jīng)進(jìn)入了下線狀態(tài) 。Sentinel會(huì)向其它Sentinel發(fā)送如下命令:SENTINEL is-master-down-by-addr <ip> <port> <current_epoch> <runid>,其中<current_epoch> <runid>用于選舉領(lǐng)頭Sentinel。Sentinel將統(tǒng)計(jì)其它Sentinel同意主服務(wù)器已下線的數(shù)量,當(dāng)這一數(shù)量達(dá)到客觀下線所需的數(shù)量時(shí),Sentinel會(huì)將主服務(wù)器判斷為客觀下線狀態(tài)。
9:當(dāng)主服務(wù)器被判斷為客觀下線狀態(tài)后,監(jiān)視的各Sentinel會(huì)選舉領(lǐng)頭的Sentinel,并由領(lǐng)頭的Sentinel對(duì)主服務(wù)器執(zhí)行故障轉(zhuǎn)移操作。
10:執(zhí)行故障轉(zhuǎn)移,首先領(lǐng)頭Sentinel會(huì)在從服務(wù)器列表里找出一個(gè)健康的并且具有最新數(shù)據(jù)的從服務(wù)器,對(duì)其執(zhí)行slaveof no one命令,將其變?yōu)閙aster。其次會(huì)將其它從服務(wù)器成為新的主服務(wù)器的slave。如果舊的主服務(wù)器從新上線的話,會(huì)成為新主服務(wù)器的從服務(wù)器
11:sentinel的配置文件如下

port 36379 ##端口
daemonize yes ##是否后臺(tái)運(yùn)行
logfile "/var/log/sentinel_36379_log.log" ##日志文件
dir "/tmp"
sentinel monitor mymaster 10.200.10.175 6379 2 ##sentinel監(jiān)視的主Redis地址,后面的2表示當(dāng)有兩臺(tái)sentinel認(rèn)為當(dāng)前Redis下線后才進(jìn)行客觀服務(wù)器下線操作。
sentinel down-after-milliseconds mymaster 6000 ##連續(xù)6秒沒(méi)接收到mymaster服務(wù)器的ping消息,將mymaster設(shè)為主觀下線狀態(tài)。
sentinel failover-timeout mymaster 180000 ##故障轉(zhuǎn)移的時(shí)長(zhǎng)
sentinel parallel-syncs mymaster 1 ##指定了在執(zhí)行故障轉(zhuǎn)移時(shí),最多可以有多少個(gè)從Redis實(shí)例在同步新的主實(shí)例。
bind 0.0.0.0 ##這個(gè)必須設(shè)置,否則沒(méi)法進(jìn)行故障轉(zhuǎn)移。

監(jiān)控指標(biāo)

  • 通過(guò)info clients 查看redis 的連接數(shù)與阻寨數(shù)。connected_clients這個(gè)值不能太大,建議不要超過(guò)5000,跟應(yīng)用使用連接池相關(guān)。blocked_clients就是阻塞的客戶端,這個(gè)數(shù)要為零。
  • 內(nèi)存使用率與碎片率,通過(guò)info memory可以看到內(nèi)存使用的情況,其中:
    1:used_memory是Redis使用的內(nèi)存總量,它包含了實(shí)際緩存占用的內(nèi)存和Redis自身運(yùn)行所占用的內(nèi)存(如元數(shù)據(jù)、lua)
    2:used_memory_rss:從操作系統(tǒng)上顯示已經(jīng)分配的內(nèi)存總量。
    3:mem_fragmentation_ratio: 內(nèi)存碎片率由:used_memory_rss/used_memory
    4:used_memory_lua: Lua腳本引擎所使用的內(nèi)存大小。
    5:mem_allocator: 在編譯時(shí)指定的Redis使用的內(nèi)存分配器,可以是libc、jemalloc、tcmalloc
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • 參考文檔:redis設(shè)計(jì)與實(shí)現(xiàn)讀書(shū)筆記 第二版 一、數(shù)據(jù)結(jié)構(gòu)和對(duì)象 1.關(guān)于字符串 redis底層存儲(chǔ)字符串結(jié)構(gòu)為...
    lionel880閱讀 1,063評(píng)論 0 2
  • 關(guān)于Mongodb的全面總結(jié) MongoDB的內(nèi)部構(gòu)造《MongoDB The Definitive Guide》...
    中v中閱讀 32,289評(píng)論 2 89
  • 閱讀奧術(shù)神座起源于閱讀武道宗師,從武道宗師可以看出愛(ài)潛水的烏賊是一名三觀很正,非常專一的作家,寫(xiě)文的水平在現(xiàn)在參差...
    Fawn_1996閱讀 2,379評(píng)論 0 0
  • 大家好,我是來(lái)自山東濟(jì)南丁科新,今天給大家分享一下的心得是:有輸出就會(huì)有發(fā)生。就拿閱讀晨間日記奇跡一書(shū)來(lái)說(shuō),當(dāng)我第...
    丁科新閱讀 158評(píng)論 0 0
  • 這是一個(gè)認(rèn)識(shí)兩年了的異性朋友。 我身邊從不缺乏異性朋友。至少在別人看來(lái)是這樣。實(shí)際上,我一直自認(rèn)為是一個(gè)不善維持友...
    小黃鴨子嘎嘎嘎閱讀 616評(píng)論 3 6

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