redis設計與實現-總結

鏈表

實現

總結

鏈表在redis應用很是普遍,好比當列表鍵包含了數量比較多的元素,又或者列表中包含的元素都是比較長的字符串,redis就會使用鏈表做爲列表鍵的底層實現html

鏈表的實現總結以下

字典

結構

dict結構

該結構中的type定義了特定類型鍵值對的函數,trehash定義了是否正在rehash,由於redis的rehash是漸進式的,漸進式的hash要操做2個dictht結構redis

dictEntry

分鍵值對和(next)指向下一個節點的指針(用於解決鍵的衝突)算法

dictht

又dictEntry的數組,還有哈希表的大小,用於hash的sizemask,和節點數量數據庫

hash算法

哈希表的擴展和收縮

或者負載因子小於0.1系統會自動收縮數組

總結

跳躍表

跳躍表按照分值來進行排序,因此基本redis只有有序列表和內部數據採用這個結構緩存

跳躍表節點

跳躍表

typedef struct zskiplist {
    //指向跳躍表的表頭和表尾
    structs zkiplistNode *header, *tail;
    //節點數量
    unsigned long length;
    //最大節點的基數
    int level;
}
複製代碼

總結

整數集合

其中encoding表示其contents數組中保存的數據大小,而不是根據int8_t來表明數據大小,由於c中的數組本質就是一個指針,而後整數集合會自動觸發升級,也就是將int8_t升級成int16_t,升級的好處就是能夠很靈活,當想要大的數據就進行擴容而後再存入,而後就是節約內存,由於當要大的數據時候纔會擴容,小數據的時候就使用小的數據結構

總結

壓縮列表

www.cnblogs.com/hunternet/p…bash

總結

壓縮列表就是相似數組,可是自己結構存儲了自身的長度和其餘信息,結尾有個0xff來表示結尾,而後中間的數據都有本身的長度標識,其中previous_entry_length用來標識前一個數據的長度,encoding用來標識自己數據的長度,其中以11開始標識是整數型,而01 00 10用來標識字節數組,後續的位用來表示長度,content用來表示內容服務器

  • 壓縮列表是Redis爲節約內存本身設計的一種順序型數據結構。
  • 壓縮列表被用做列表鍵和哈希鍵的底層實現之一。
  • 壓縮列表能夠包含多個節點,每一個節點能夠保存一個字節數組或者整數值。
  • 添加新節點到壓縮列表,或者從壓縮列表中刪除節點,可能會引起連鎖更新操做,但這種操做出現的概率並不高

對象

redis不是直接拿上面提到的數據結構來實現鍵值對數據庫,而是建立了一個對象系統,用上面的數據結構來組成 網絡

類型

可使用type命令查看其鍵的值的類型,好比type price數據結構

編碼

可使用object encoding命令查看鍵的值的編碼,好比object encoding price

字符串對象

  1. 字符串對象能夠用int, raw和embstr來編碼,
  2. 當設置的key是number的時候就會使用int編碼,可是當變爲字符串的時候就會選擇使用embstr或者raw來進行編碼,字符串的長度大於32字節的時候會選擇raw
  3. 而embstr和raw的區別是,raw是sds結構,內存分配的時候會分配2次,一次給redisObject另外一次給sdshdr結構,而embstr分配一次將2個結構放在同一塊內存中,embstr分配次數會少,釋放對應也少,而後兩個結構的內存有連續性能夠更好的利用緩存
  4. 能夠用long double類型標識浮點數做爲redis的字符串的值來保存,通常再進行浮點數運算的時候,將字符串類型的浮點數和浮點數進行計算會自動轉化位long duuble計算完了後又會存入字符串
  5. embstr通常都是隻讀的,在修改後會直接變成raw而後進行修改

列表對象

列表對象由ziplist或者linkedlist編碼

ziplist

linkedlist

linkedlist會嵌套redis的StringObject對象

編碼轉化

同時知足一下兩個條件的時候列表使用ziplist編碼

  1. 列表對象的全部字符串元素的長度都小於64字節
  2. 列表對象保存的元素數量小於512個 咱們知道ziplist會有連鎖更新問題,可是由於redis這裏限制了數量和大小因此觸發的連鎖更新並不會消耗不少性能

hash對象

哈希對象使用ziplist或者hashtable來編碼

ziplist

hashtable

編碼轉換

當知足兩個條件時,哈希對象使用ziplist編碼

  1. 哈希對象保存的全部鍵值對字符串長度都小於64字節
  2. 鍵值對數量小於512個

集合對象

集合對象的編碼能夠是intset或者hashtable

當集合對象都是整數值,並且數量小於512個就會使用intset編碼

有序集合對象

有序集合編碼能夠是ziplist或者skiplist

ziplist

skiplist

當使用skiplist編碼的時候是會使用zset結構,該結構包含了skiplist和字典來實現,由於字典能夠很快定位成員的分值,可是作區間計算的時候沒法進行只能進行排序後獲取,而skiplist做爲支持有序集合查找和範圍操做盡能夠快的執行,因此redis使用字典和跳躍表來實現有序集合

轉換

當元素數量小於128個,且成員的長度都小於64字節的時候使用ziplist

垃圾回收

由於c語言不具有內存回收功能,因此redis本身實現了一套引用計數的回收機制

對象共享

在回收機制以外,由於引入了對象引用計數的機制,而後對象能夠在2個鍵中進行共享,當他們的值相同的時候他們的值能夠指向同一塊內存

對象的空轉市場

redisObject中除了上面提到的對象還有lru這個屬性,該屬性記錄了最後一次命令訪問的時間,該空轉時長還有另一項做用,就是服務器設定了某種內存回收機制,就是當內存超過了maxmemory,空轉時長比較高的那部分會優先丟棄從而回收內存

數據庫

struct redisServer{
    // ...
    redis *db;
    int dbnum;
    // ...
}
typedef struct redisDb{
    // ...
    dict *dict
    dict *expries
    // ...
}
複製代碼

過時時間

redis有4個命令能夠設置過時時間, expire pexpire expireat pexpireat, 本質上最終都是使用pexpireat命令來設置過時時間,不論是相對時間仍是絕對時間,最終都是用絕對時間來進行設置,而後過時時間保存和key-value是不同的,用的是redisDb中的expreies

過時時間刪除策略

AOF和RDB複製功能對過時鍵的處理

  1. 對於生成rdb文件,當有過時鍵的時候調用rdb生成命令不會將過時鍵保存到
  2. 當載入rdb文件的時候,當是主服務載入rbd的時候會進行檢查,沒過時纔會載入,當是從服務器載入的時候,主服務器進行同步的時候會發送指令將過時的刪除
  3. aof持久化模式的時候當某個鍵過時的時候雖然他沒有刪除,可是當刪除後會給aof文件最佳一條del命令
  4. 在主從複製的狀況下,主服務刪除了過時鍵會給從發送del命令,而從雖然知道過時了可是要等待主服務器發送del命令不然仍是會給客戶端返回對應過時的值

RDB持久化

rdb有3種

  1. save命令會阻塞全部操做,知道生成完rdb文件後(備份完)
  2. bgsave會啓動一個子進程來進行生成備份,可是會阻塞其餘全部備份操做,正常客戶端操做能夠進行
  3. 根據配置文件定時和定量的進行自動備份,根據沒多少秒對數據庫多少次修改

dirty計數器和lastsave屬性saveparams

dirty,saveparams和lastsave都是redisServer的屬性,一個用來記錄在備份期間有多少數據更新了,而lastsave是最後更新時間,saveparams用來記錄定時定量備份用到的時間和更新次數從而知道何時能進行更新

rdb文件結構

  1. redis就是redis字符串的二進制數據可是除了'\0'結尾符,長度5字節
  2. db_version用來記錄rdb的版本長度4字節
  3. database包含了0個或多個數據庫的鍵值對數據
  4. EOF長度1字節來表明rdb文件正文內容的結束
  5. check_sum是一個8字節長的無符號整數,通常存入的值是將其餘4部分的數據進行校驗和

database部分

  1. selectdb長度位1個字節表明下面要讀入的是數據庫的號碼
  2. db_number保存的是數據庫的號碼,根據號碼的長度保存的字節長度也不一樣
  3. key_value_pairs保存了數據庫種的全部鍵值對

key_value_pairs

  1. EXPIRETIME_MS長度1字節表明下面的是過時時間
  2. type其實就是上面提到的對象的編碼(encoding)

AOF持久化

AOF的本質就是將每一條命令進行持久化,包括選擇數據庫的命令,而後有3種模式將AOF進行同步到磁盤的選項

  1. always 每次都將緩衝區的內容寫入aof文件種
  2. everysec 距離上次同步超過一秒就進行同步,有專門的線程負責執行
  3. no 就是磁盤何時決定將緩衝區存入磁盤

aof重寫

aof重寫就是假若有不少命令都是操做同一個key,那麼重寫的過程就會直接從數據庫中拿到那個key的value, 而後生成一條這個value的命令來代替原來的多條命令,而後在重寫期間,redis會建立一個子進程,由於子進程用的是進程的數據副本,不會有鎖的衝突,這就有個問題就是主進程還在進行接收客戶端命令,這邊就須要一個臨時的重寫緩衝區來存儲最新的命令,當子進程重寫完成後,將緩衝區的命令追加到aof文件結尾就ok了,這樣重寫後的數據纔不會不一致

客戶端

服務端有一個指針用鏈表的形式保存了全部客戶端的屬性(也就是客戶端的對象)

typeof struct redisClient {
    // ...
    int fd;
    robj *name;
    int flag;
    sds querybuf;
    robj **argv;
    int argc;
    struct redisCommand *cmd;
    char buf[REDIS_REPLY_CHUNK_BYTES];
    int bufpos;
    list *reply;
    int authenticated;
    time_t ctime;
    time_t lastinteraction;
    time_t dbuf_soft_limit_reached_time;
    // ...
}
複製代碼

  1. fd用來表示客戶端套接字,由於客戶端是網絡程序有文件套接字因此必大於-1,而後特殊的僞客戶端(lua腳本,aof載入)等fd都是-1
  2. name用來表示客戶端的名稱,默認客戶端沒有名稱
  3. flag用來表示客戶端的狀態,多個狀態經過二進制或的操做
  4. querybuf是輸入緩衝區,裏面存儲了用戶輸入的命令以sds的形式保存
  5. argv和argc就是對querybuf的解析,argv是數組每一個項都保存一個字符串,argc保存的是長度
  6. redisCommand就是對應argv的每項對應的實現函數
  7. buf是客戶端用來回復的緩衝區是固定大小通常默認是16kb,而後bufpos是多少個元素
  8. reply是個鏈表,當回覆的大小超過了固定值就會用reply來回復
  9. authenticated是對用戶的權限校驗0是校驗過的,1是沒有校驗
  10. ctime是客戶端建立事件
  11. lastinteraction最後一次互動事件
  12. dbuf_soft_limit_reached_time第一次到達軟限制的事件

複製

早期版本sync命令

如圖,直接進行同步rdb文件,而後有新的數據就存放在緩衝區,最後同步下緩衝區的內容就Ok了,初次同步是這樣,後續都在線的狀況下,每當不一樣步,主服務都會發送不一樣步的命令給從服務進行執行,而後有個問題就是斷線重連,每當重連後都會進行一次從新的rdb同步,雖然新增命令較少可是rdb太耗時耗力了,

新版psync

psync就增長2種模式

  1. 徹底重同步相似sync
  2. 部分重同步

對於部分重同步,psync是這樣設計的,每一個服務都有一個偏移量,而主服務上有個複製擠壓緩衝區,也就是下標是偏移量對應內容,而後各個服務之間會進行偏移量對比,發現少的時候從緩衝區裏拿,發現緩衝區都找不到就直接徹底重同步,而後還假如服務id這個字段,當從重連到主的時候發現id不同直接徹底重同步

事務

www.cnblogs.com/DeepInThoug…

每一個客戶端都有一個multistate msstats的屬性用來表示事務狀態

typedef struct multistate{
    // ...
    multiCmd *commands;
    int counts;
    // ...
}

typedef struct multiCmd {
    // ...
    robj **argv;
    int argc;
    struct redisCommond *cmd;
    // ...
}
複製代碼
  1. commands其實就是一個multiCmd數組,counts用來表示數組的長度
  2. multiCmd保存了事務中每行命令的argv和argc和rediCommond
  3. 事務的本質就是隊列,而後當redis在運行隊列中的命令的時候不會執行其餘的命令,只會等到執行完隊列中的命令

watch

watch本質其實就是在redisDb中有一個字典表,裏面對應的就是監聽的key, 在進行命令操做對應的key的時候會去監聽字典表中看是否有客戶端監聽,當有監聽就會去打開對應的客戶端的REDIS_DIRTY_CAS的flag, 當事務看到flag是打開的時候就會報錯這行命令

事務的ACID

redis的事務本質就是保證一致性和隔離性,而原子性由於沒有回滾一行命令出錯不會影響下面的命令執行,redis將這種錯誤稱之爲程序錯誤,不該該出如今生產環境,而持久性是根據rdb和aof的策略來斷定的,當每次事務完成都會進行落盤的操做就能夠擁有持久性,而隔離性由於是單線程因此天生具備隔離性,一致性是說數據沒有錯誤和非法數據,由於redis的單挑命令是原子的,當是命令錯誤的時候這條命令不會執行保證了一致性,當服務宕機的時候,無論有沒有被落盤數據都不會錯誤,要不就是丟失,而後就是入隊錯誤也就是非法命令,redis會在事務以前檢驗全部命令,因此不會有問題

相關文章
相關標籤/搜索