REmote DIctionary Server(Redis) 是一個由SalvatoreSanfilippo寫的key-value存儲系統。node
Redis是一個開源的使用ANSI C語言編寫、遵照BSD協議、支持網絡、可基於內存亦可持久化的日誌型、Key-Value數據庫,並提供多種語言的API。git
它一般被稱爲數據結構服務器,由於值(value)能夠是字符串(String), 哈希(Map), 列表(list), 集合(sets) 和有序集合(sorted sets)等類型。github
Redis 是徹底開源免費的,遵照BSD協議,是一個高性能的key-value數據庫。redis
Redis 與其餘 key - value 緩存產品有如下三個特色:數據庫
性能極高 – Redis能讀的速度是110000次/s,寫的速度是81000次/s 。segmentfault
豐富的數據類型 – Redis支持 Strings, Lists, Hashes, Sets 及 Ordered Sets 數據類型操做。數組
原子 – Redis的全部操做都是原子性的,同時Redis還支持對幾個操做全並後的原子性執行。緩存
豐富的特性 – Redis 還支持 publish/subscribe, 隊列,key 過時等等特性。服務器
redis> SET message "hello redis"
其中的key是message,是一個包含了字符串"message"的對象。而value是一個包含了"hello redis"的對象。
Redis共有五種對象的類型,分別是:網絡
類型常量 | 對象的名稱 |
---|---|
REDIS_STRING | 字符串對象 |
REDIS_LIST | 列表對象 |
REDIS_HASH | 哈希對象 |
REDIS_SET | 集合對象 |
REDIS_ZSET | 有序集合對象 |
Redis中的一個對象的結構體表示以下:
typedef struct redisObject { // 類型 unsigned type:4; // 編碼方式 unsigned encoding: 4; // 引用計數 int refcount; // 指向對象的值 void *ptr; } robj;
type表示了該對象的對象類型,即上面五個中的一個。但爲了提升存儲效率與程序執行效率,每種對象的底層數據結構實現均可能不止一種。encoding就表示了對象底層所使用的編碼。
編碼常量 | 編碼所對應的底層數據結構 |
---|---|
REDIS_ENCODING_INT | long 類型的整數 |
REDIS_ENCODING_EMBSTR | embstr 編碼的簡單動態字符串 |
REDIS_ENCODING_RAW | 簡單動態字符串 |
REDIS_ENCODING_HT | 字典 |
REDIS_ENCODING_LINKEDLIST | 雙端鏈表 |
REDIS_ENCODING_ZIPLIST | 壓縮列表 |
REDIS_ENCODING_INTSET | 整數集合 |
REDIS_ENCODING_SKIPLIST | 跳躍表和字典 |
字符串對象的編碼能夠是int、raw或者embstr
若是一個字符串的內容能夠轉換爲long,那麼該字符串就會被轉換成爲long類型,對象的ptr就會指向該long,而且對象類型也用int類型表示。
普通的字符串有兩種,embstr和raw。embstr應該是Redis 3.0新增的數據結構,在2.8中是沒有的。若是字符串對象的長度小於39字節,就用embstr對象。不然用傳統的raw對象。
#define REDIS_ENCODING_EMBSTR_SIZE_LIMIT 44 robj *createStringObject(char *ptr, size_t len) { if (len <= REDIS_ENCODING_EMBSTR_SIZE_LIMIT) return createEmbeddedStringObject(ptr,len); else return createRawStringObject(ptr,len); }
embstr的好處有以下幾點:
sds
分配對象,另外一次爲objet分配對象,embstr省去了第一次)。raw和embstr的區別能夠用下面兩幅圖所示:
列表對象的編碼能夠是ziplist或者linkedlist
哈希對象的底層實現能夠是ziplist或者hashtable。
ziplist中的哈希對象是按照key1,value1,key2,value2這樣的順序存放來存儲的。當對象數目很少且內容不大時,這種方式效率是很高的。
hashtable的是由dict這個結構來實現的, dict是一個字典,其中的指針dicht ht[2] 指向了兩個哈希表
typedef struct dict { dictType *type; void *privdata; dictht ht[2]; long rehashidx; /* rehashing not in progress if rehashidx == -1 */ int iterators; /* number of iterators currently running */ } dict; typedef struct dictht { dictEntry **table; unsigned long size; unsigned long sizemask; unsigned long used; } dictht;
dicht[0] 是用於真正存放數據,dicht[1]通常在哈希表元素過多進行rehash的時候用於中轉數據。
dictht中的table用語真正存放元素了,每一個key/value對用一個dictEntry表示,放在dictEntry數組中。
集合對象的編碼能夠是intset或者hashtable
intset是一個整數集合,裏面存的爲某種同一類型的整數,支持以下三種長度的整數:
#define INTSET_ENC_INT16 (sizeof(int16_t)) #define INTSET_ENC_INT32 (sizeof(int32_t)) #define INTSET_ENC_INT64 (sizeof(int64_t))
intset是一個有序集合,查找元素的複雜度爲O(logN),但插入時不必定爲O(logN),由於有可能涉及到升級操做。好比當集合裏全是int16_t型的整數,這時要插入一個int32_t,那麼爲了維持集合中數據類型的一致,那麼全部的數據都會被轉換成int32_t類型,涉及到內存的從新分配,這時插入的複雜度就爲O(N)了。
intset不支持降級操做。
有序集合的編碼可能兩種,一種是ziplist,另外一種是skiplist與dict的結合。
ziplist做爲集合和做爲哈希對象是同樣的,member和score順序存放。按照score從小到大順序排列
skiplist是一種跳躍表,它實現了有序集合中的快速查找,在大多數狀況下它的速度均可以和平衡樹差很少。但它的實現比較簡單,能夠做爲平衡樹的替代品。它的結構比較特殊。下面分別是跳躍表skiplist和它內部的節點skiplistNode的結構體:
/* * 跳躍表 */ typedef struct zskiplist { // 頭節點,尾節點 struct zskiplistNode *header, *tail; // 節點數量 unsigned long length; // 目前表內節點的最大層數 int level; } zskiplist; /* ZSETs use a specialized version of Skiplists */ /* * 跳躍表節點 */ typedef struct zskiplistNode { // member 對象 robj *obj; // 分值 double score; // 後退指針 struct zskiplistNode *backward; // 層 struct zskiplistLevel { // 前進指針 struct zskiplistNode *forward; // 這個層跨越的節點數量 unsigned int span; } level[]; } zskiplistNode;
head和tail分別指向頭節點和尾節點,而後每一個skiplistNode裏面的結構又是分層的(即level數組)
用圖表示,大概是下面這個樣子:
以上簡單介紹了Redis的簡介,特性以及五種對象類型和五種對象類型的底層實現。事實上,Redis的高效性和靈活性正是得益於同一個對象類型採用不一樣的底層結構,而且在必要的時候對兩者進行轉換,還有就是各類底層結構對內存的合理利用。
本文做者:Worktile高級工程師 龔林傑
文章來源:Worktile技術博客
歡迎訪問交流更多關於技術及協做的問題。
文章轉載請註明出處。