Redis源碼學習

學習http://blog.csdn.net/men_wen/article/details/75668345node

簡單動態字符串:string

http://blog.csdn.net/men_wen/article/details/69396550redis

sds結構:算法

struct sdshdr {
    int len;        //buf中已佔用空間的長度
    int free;       //buf中剩餘可用空間的長度
    char buf[];     //初始化sds分配的數據空間,並且是柔性數組(Flexible array member)
};

優勢:數據庫

一、二進制安全:可以存儲含'\0'(C 內置字符串類型結束符)的字符串,好比二進制圖片、視頻等數組

二、緩存

鏈表結構:list 雙向鏈表

http://blog.csdn.net/men_wen/article/details/69215222安全

  • 鏈表節點
typedef struct listNode {
    struct listNode *prev; //前驅節點,若是是list的頭結點,則prev指向NULL
    struct listNode *next;//後繼節點,若是是list尾部結點,則next指向NULL
    void *value;            //萬能指針,可以存聽任何信息
}
  • 表頭
typedef struct list {
    listNode *head;     //鏈表頭結點指針
    listNode *tail;     //鏈表尾結點指針

    //下面的三個函數指針就像類中的成員函數同樣
    void *(*dup)(void *ptr);    //複製鏈表節點保存的值
    void (*free)(void *ptr);    //釋放鏈表節點保存的值
    int (*match)(void *ptr, void *key); //比較鏈表節點所保存的節點值和另外一個輸入的值是否相等
    unsigned long len;      //鏈表長度計數器
} list;

 

利用 list 表頭管理鏈表信息:服務器

一、head和tail指針:對於鏈表的頭結點和尾結點操做的複雜度爲O(1)。數據結構

二、len 鏈表長度計數器:獲取鏈表中節點數量的複雜度爲O(1)。socket

三、dup、free和match指針:實現多態,鏈表節點listNode使用萬能指針void *保存節點的值,而表頭list使用dup、free和match指針來針對鏈表中存放的不一樣對象從而實現不一樣的方法。

  • 鏈表迭代器

Redis 字典結構:hash、關聯數組、map

哈希表結構

typedef struct dictht { //哈希表
    dictEntry **table;      //存放一個數組的地址,數組存放着哈希表節點dictEntry的地址。
    unsigned long size;     //哈希表table的大小,初始化大小爲4
    unsigned long sizemask; //用於將哈希值映射到table的位置索引。它的值老是等於(size-1)。
    unsigned long used;     //記錄哈希表已有的節點(鍵值對)數量。
} dictht;

哈希表節點結構

typedef struct dictEntry {
    void *key;                  //key
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
        double d;
    } v;                        //value
    struct dictEntry *next;     //指向下一個hash節點,用來解決hash鍵衝突(collision)
} dictEntry;

字典結構

typedef struct dict {
    dictType *type;     //指向dictType結構,dictType結構中包含自定義的函數,這些函數使得key和value可以存儲任何類型的數據。
    void *privdata;     //私有數據,保存着dictType結構中函數的參數。
    dictht ht[2];       //兩張哈希表。
    long rehashidx;     //rehash的標記,rehashidx==-1,表示沒在進行rehash
    int iterators;      //正在迭代的迭代器數量
} dict;

dictType類型保存着 操做字典不一樣類型key和value的方法 的指針。

typedef struct dictType {
    unsigned int (*hashFunction)(const void *key);      //計算hash值的函數
    void *(*keyDup)(void *privdata, const void *key);   //複製key的函數
    void *(*valDup)(void *privdata, const void *obj);   //複製value的函數
    int (*keyCompare)(void *privdata, const void *key1, const void *key2);  //比較key的函數
    void (*keyDestructor)(void *privdata, void *key);   //銷燬key的析構函數
    void (*valDestructor)(void *privdata, void *obj);   //銷燬val的析構函數
} dictType;

哈希算法

int整型哈希值

unsigned int dictIntHashFunction(unsigned int key)      //用於計算int整型哈希值的哈希函數
{
    key += ~(key << 15);       //~按位取反 ,^按位異或
    key ^=  (key >> 10);
    key +=  (key << 3);
    key ^=  (key >> 6);
    key += ~(key << 11);
    key ^=  (key >> 16);
    return key;
}

字典用做數據庫底層實現或哈希鍵時,redis使用 MurmurHash2算法,能產生32-bit或64-bit 哈希值

衝突&rehash

Redis 跳躍表(skiplist):有序集合

跳躍表支持平均O(logN),最壞O(N)複雜度的節點查找,大部分狀況下,跳躍表的效率能夠和平衡樹相媲美。

Redis 整數集合(intset)

整數集合(intset)是集合鍵底層實現之一。集合鍵另外一實現是值爲空的散列表(hash table),雖然使用散列表對集合的加入刪除元素,判斷元素是否存在等等操做時間複雜度爲O(1),可是當存儲的元素是整型且元素數目較少時,若是使用散列表存儲,就會比較浪費內存,所以整數集合(intset)類型由於節約內存就存在。

Redis 壓縮列表(ziplist)

壓縮列表(ziplist)是哈希鍵的底層實現之一。它是通過特殊編碼的雙向鏈表,和整數集合(intset)同樣,是爲了提升內存的存儲效率而設計的。當保存的對象是小整數值,或者是長度較短的字符串,那麼redis就會使用壓縮列表來做爲哈希鍵的實現。

redis 3.2之後,quicklist做爲列表鍵的實現底層實現之一,代替了壓縮列表。

Redis 快速列表(quicklist)

127.0.0.1:6379> RPUSH list 1 2 5 1000
"redis" "quicklist"(integer) 
127.0.0.1:6379> OBJECT ENCODING list
"quicklist"

 

Redis 對象系統

對象結構robj功能:

  • 爲5種不一樣的對象類型提供同一的表示形式。
  • 爲不一樣的對象適用於不一樣的場景,支持同一種對象類型採用多種的數據結構方式。
  • 支持引用計數,實現對象共享機制。
  • 記錄對象的訪問時間,便於刪除對象。
#define LRU_BITS 24
#define LRU_CLOCK_MAX ((1<<LRU_BITS)-1) /* Max value of obj->lru */
#define LRU_CLOCK_RESOLUTION 1000 /* LRU clock resolution in ms */

typedef struct redisObject {
    //對象的數據類型,佔4bits,共5種類型
    unsigned type:4;        
    //對象的編碼類型,佔4bits,共10種類型
    unsigned encoding:4;

    //least recently used
    //實用LRU算法計算相對server.lruclock的LRU時間
    unsigned lru:LRU_BITS; /* lru time (relative to server.lruclock) */

    //引用計數
    int refcount;

    //指向底層數據實現的指針
    void *ptr;
} robj;

//type的佔5種類型:
/* Object types */
#define OBJ_STRING 0    //字符串對象
#define OBJ_LIST 1      //列表對象
#define OBJ_SET 2       //集合對象
#define OBJ_ZSET 3      //有序集合對象
#define OBJ_HASH 4      //哈希對象

/* Objects encoding. Some kind of objects like Strings and Hashes can be
 * internally represented in multiple ways. The 'encoding' field of the object
 * is set to one of this fields for this object. */
// encoding 的10種類型
#define OBJ_ENCODING_RAW 0     /* Raw representation */     //原始表示方式,字符串對象是簡單動態字符串
#define OBJ_ENCODING_INT 1     /* Encoded as integer */         //long類型的整數
#define OBJ_ENCODING_HT 2      /* Encoded as hash table */      //字典
#define OBJ_ENCODING_ZIPMAP 3  /* Encoded as zipmap */          //不在使用
#define OBJ_ENCODING_LINKEDLIST 4 /* Encoded as regular linked list */  //雙端鏈表,不在使用
#define OBJ_ENCODING_ZIPLIST 5 /* Encoded as ziplist */         //壓縮列表
#define OBJ_ENCODING_INTSET 6  /* Encoded as intset */          //整數集合
#define OBJ_ENCODING_SKIPLIST 7  /* Encoded as skiplist */      //跳躍表和字典
#define OBJ_ENCODING_EMBSTR 8  /* Embedded sds string encoding */   //embstr編碼的簡單動態字符串
#define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of ziplists */   //由壓縮列表組成的雙向列表-->快速列表

 

Redis 字符串鍵的實現(t_string)

Redis 列表類型命令實現(t_list)

1 BLPOP key1 [key2 ] timeout:移出並獲取列表的第一個元素, 若是列表沒有元素會阻塞列表直到等待超時或發現可彈出元素爲止。
2 BRPOP key1 [key2 ] timeout:移出並獲取列表的最後一個元素, 若是列表沒有元素會阻塞列表直到等待超時或發現可彈出元素爲止。

Redis 數據庫

Redis是一個key-value數據庫服務器,它將全部的鍵值對都保存在 redisDb 結構中的 dict 字典成員

  • 鍵值對字典的鍵,就是數據庫的key,每個key都是字符串的對象。

  • 鍵值對字典的值,就是數據庫的value,每個value能夠是字符串的對象,列表對象,哈希表對象,集合對象和有序集合對象中的任意一種。

typedef struct redisDb {
    // 鍵值對字典,保存數據庫中全部的鍵值對
    dict *dict;                 /* The keyspace for this DB */
    // 過時字典,保存着設置過時的鍵和鍵的過時時間
    dict *expires;              /* Timeout of keys with a timeout set */
    // 保存着 全部形成客戶端阻塞的鍵和被阻塞的客戶端
    dict *blocking_keys;        /*Keys with clients waiting for data (BLPOP) */
    // 保存着 處於阻塞狀態的鍵,value爲NULL
    dict *ready_keys;           /* Blocked keys that received a PUSH */
    // 事物模塊,用於保存被WATCH命令所監控的鍵
    dict *watched_keys;         /* WATCHED keys for MULTI/EXEC CAS */
    // 當內存不足時,Redis會根據LRU算法回收一部分鍵所佔的空間,而該eviction_pool是一個長爲16數組,保存可能被回收的鍵
    // eviction_pool中全部鍵按照idle空轉時間,從小到大排序,每次回收空轉時間最長的鍵
    struct evictionPoolEntry *eviction_pool;    /* Eviction pool of keys */
    // 數據庫ID
    int id;                     /* Database ID */
    // 鍵的平均過時時間
    long long avg_ttl;          /* Average TTL, just for stats */
} redisDb;

typedef struct client {
    redisDb *db;            /* Pointer to currently SELECTed DB. */
} client;

struct redisServer {
    redisDb *db;
    int dbnum;                      /* Total number of configured DBs */
};

數據庫每次根據鍵名找到值對象時,是分爲以讀操做 lookupKeyRead() 或寫操做 lookupKeyWrite() 的方式取出的,而這兩種有必定的區別。

Redis 通知功能實現與實戰

客戶端能夠經過 訂閱與發佈功能(pub/sub)功能,來接收那些以某種方式改動了Redis數據集的事件。

Redis的訂閱與發佈功能採用的是發送即忘(fire and forget)的策略,當訂閱事件的客戶端斷線時,它會丟失全部在斷線期間分發給它的事件

通知功能類型:

  • 鍵空間通知(key-space notification)
  • 鍵事件通知(key-event notification)

Redis輸入輸出的抽象(rio)

rio是Redis對IO操做的一個抽象,能夠面向不一樣的輸入輸出設備,例如一個緩衝區IO、文件IO和socket IO。

一個rio對象提供一下四個方法:

  • read:讀操做
  • write:寫操做
  • tell:讀寫的偏移量
  • flush:沖洗緩衝區操做

對rio抽象的結構體中,使用了一個共用體(union),它多是三種不一樣的對象,分別是:

  • 緩衝區 IO(Buffer I/O)
  • 標準輸入輸出 IO(Stdio file pointer)
  • 文件描述符集合(File descriptors set)

標準IO和文件IO的區別:

    標準IO:文件流fp,有緩存,庫函數

    文件IO:文件描述符fd(小的,非負的整型數),無緩存,系統調用

    標準IO是依賴於文件IO的

b1: 標準IO:

     stdin   鍵盤/stdout   屏幕/stderr   屏幕

     fgetc   每次讀入一個字符          fputc   每次寫一個字符

     fgets   每次讀入一行              fputs   每次寫一行

     getchar

    fprintf   格式化輸出到文件流

     sprintf   格式化輸出到緩存

     fopen   打開文件,創建一個文件流,與文件關聯

     fclose   關閉文件流

     fread   對文件流直接進行讀操做

     fwrite   對文件流直接進行寫操做

     errno   輸出錯誤的緣由(結果是個數)

     strerror   把結果數轉換爲字符串,而後輸出錯誤的緣由   

     perror   打印出錯的緣由(經常使用)

     fflush   強制刷新緩存

     stdout   行緩衝,只對/n進行刷新,對/r不進行刷新

     stderr   無緩衝

     fseek   移動當前的讀寫位置

     ftell   查看當前的讀寫位置

     freopen   重定向文件流

b2: 文件IO:

     文件描述符分配原則:當前未被使用的數值最小的int數,0(標準輸入)stdin,1(標準輸出)stdout,2(標準錯誤)stderr

     open   打開文件,將文件與文件描述符關聯

     close   關閉文件

     read   讀取文件,以字節爲單位

     write   寫入文件

     lseek   定位讀寫位置

b3: 文件描述符數值:有多個描述符指向file結構體

    file結構體裏面的成員變量指向文件的inode節點(結構體)

    file結構體有一成員變量「引用計數」refc記錄指向file結構體的文件描述符數量

b4: dup 文件描述符複製

    duplicate a file descriptor

    有多個文件描述符指向file結構體

D: 文件類型(7種):一切皆文件

     ls -l:查看文件類型

     bcd-lsp(b:塊文件   c:字符文件   d:目錄   -:常規文件   l:連接文件   s:套接字   p:管道文件)

Redis RDB持久化機制

由於Redis是內存數據庫,所以將數據存儲在內存中,若是一旦服務器進程退出,服務器中的數據庫狀態就會消失不見,爲了解決這個問題,Redis提供了兩種持久化的機制:RDBAOF。本篇主要剖析RDB持久化的過程。

RDB持久化是把當前進程數據生成時間點快照(point-in-time snapshot)保存到硬盤的過程,避免數據意外丟失。

AOF持久化:以獨立日誌的方式記錄每次寫命令,重啓時在從新執行AOF文件中的命令達到恢復數據的目的。

因爲Redis是單線程響應命令,因此每次寫AOF文件都直接追加到硬盤中,那麼寫入的性能徹底取決於硬盤的負載,因此Redis會將命令寫入到緩衝區中,而後執行文件同步操做,再將緩衝區內容同步到磁盤中,這樣就很好的保持了高性能。

Redis 事件處理實現

Redis包裝了常見的select epoll evport kqueue,他們在編譯階段,根據不一樣的系統選擇性能最高的一個多路複用庫做爲Redis的多路複用程序的實現,並且全部庫實現的接口名稱都是相同的,所以Redis多路複用程序底層實現是能夠互換的。

INFO server 

 

 

Redis 複製(replicate)實現

1. 複製的介紹

Redis爲了解決單點數據庫問題,會把數據複製多個副本部署到其餘節點上,經過複製,實現Redis高可用性,實現對數據的冗餘備份,保證數據和服務的高度可靠性

2.複製的實現

 

 

高可用性

Redis Sentinel實現

Raft 算法:

http://thesecretlivesofdata.com/raft/

Redis Cluster 集羣伸縮原理

Redis 故障轉移流程和原理

 

 

Redis 優化方法

Redis 利用Hash存儲節約內存

一、使用Redis的String結構來作一個key-value存儲,

SET media:1155315 939   
GET media:1155315   
> 939

將數據按上面的方法存儲,1,000,000數據會用掉70MB內存,300,000,000張照片就會用掉21GB的內存。

將key值存成純數字,通過實驗,內存佔用會降到50MB,總的內存佔用是15GB,

二、使用Hash結構。具體的作法就是將數據分段,每一段使用一個Hash結構存儲,因爲Hash結構會在單個Hash元素在不足必定數量時進行壓縮存儲,因此能夠大量節約內存,因此能夠大量節約內存。這一點在上面的String結構裏是不存在的。而這個必定數量是由配置文件中的hash-zipmap-max-entries參數來控制的。通過開發者們的實驗,將hash-zipmap-max-entries設置爲1000時,性能比較好,超過1000後HSET命令就會致使CPU消耗變得很是大。

 

 

 

 

#include <stdio.h>
struct str{
    int len;
    char s[0];
};
 
struct foo {
    struct str *a;
};
 
int main(int argc, char** argv) {
    struct foo f={0};
    if (f.a->s) {
        printf( f.a->s);
    }
    return 0;
}
相關文章
相關標籤/搜索