0 概述
1 通用命令
2 數(shù)據(jù)結(jié)構(gòu)和內(nèi)部編碼
2.1 Redis 沒(méi)有傳統(tǒng)關(guān)系型數(shù)據(jù)庫(kù)的Table 模型
schema 所對(duì)應(yīng)的db僅以編號(hào)區(qū)分。同一個(gè)db 內(nèi),key 作為頂層模型,它的值是扁平化的。也就是說(shuō)db 就是key的命名空間
key的定義通常以“:” 分隔,如:Article:Count:1
我們常用的Redis數(shù)據(jù)類(lèi)型有:string、list、set、map、sorted-set
2.2 redisObject通用結(jié)構(gòu)
Redis中的所有value 都是以object 的形式存在的,其通用結(jié)構(gòu)如下
typedef struct redisObject {
unsigned [type] 4;
unsigned [encoding] 4;
unsigned [lru] REDIS_LRU_BITS;
int refcount;
void *ptr;
} robj;
- type 指的是前面提到的 string、list 等類(lèi)型
- encoding 指的是這些結(jié)構(gòu)化類(lèi)型具體的實(shí)現(xiàn)方式,同一個(gè)類(lèi)型可以有多種實(shí)現(xiàn)
e.g. string 可以用int 來(lái)實(shí)現(xiàn),也可以使用char[] 來(lái)實(shí)現(xiàn);list 可以用ziplist 或者鏈表來(lái)實(shí)現(xiàn) - lru 表示本對(duì)象的空轉(zhuǎn)時(shí)長(zhǎng),用于有限內(nèi)存下長(zhǎng)時(shí)間不訪問(wèn)的對(duì)象清理
- refcount 對(duì)象引用計(jì)數(shù),用于GC
- ptr 指向以encoding 方式實(shí)現(xiàn)這個(gè)對(duì)象實(shí)際實(shí)現(xiàn)者的地址
如:string 對(duì)象對(duì)應(yīng)的SDS地址(string的數(shù)據(jù)結(jié)構(gòu)/簡(jiǎn)單動(dòng)態(tài)字符串)
3 單線(xiàn)程
4 string-字符串
Redis中的string 可以表示很多語(yǔ)義
- 字節(jié)串(bits)
- 整數(shù)
- 浮點(diǎn)數(shù)
這三種類(lèi)型,redis會(huì)根據(jù)具體的場(chǎng)景完成自動(dòng)轉(zhuǎn)換,并且根據(jù)需要選取底層的承載方式
例如整數(shù)可以由32-bit/64-bit、有符號(hào)/無(wú)符號(hào)承載,以適應(yīng)不同場(chǎng)景對(duì)值域的要求
4.1 內(nèi)存結(jié)構(gòu)
在Redis內(nèi)部,string的內(nèi)部以 int、SDS(簡(jiǎn)單動(dòng)態(tài)字符串 simple dynamic string)作為存儲(chǔ)結(jié)構(gòu)
- int 用來(lái)存放整型
- SDS 用來(lái)存放字節(jié)/字符和浮點(diǎn)型SDS結(jié)構(gòu)
(1)SDS
typedef struct sdshdr {
// buf中已經(jīng)占用的字符長(zhǎng)度
unsigned int len;
// buf中剩余可用的字符長(zhǎng)度
unsigned int free;
// 數(shù)據(jù)空間
char buf[];
}
上面存儲(chǔ)的內(nèi)容為“Redis”,Redis采用類(lèi)似C語(yǔ)言的存儲(chǔ)方法,使用'\0'結(jié)尾(僅僅是定界符)
上面SDS的free 空間大小為0,當(dāng)free > 0時(shí),buf中的free 區(qū)域的引入提升了SDS對(duì)字符串的處理性能,可以減少處理過(guò)程中的內(nèi)存申請(qǐng)和釋放次數(shù)
(2) buf 的擴(kuò)容與縮容
當(dāng)對(duì)SDS 進(jìn)行操作時(shí),如果超出了容量。SDS會(huì)對(duì)其進(jìn)行擴(kuò)容,觸發(fā)條件如下:
- 字節(jié)串初始化時(shí),buf的大小 = len + 1,即加上定界符'\0'剛好用完所有空間
- 當(dāng)對(duì)串的操作后小于1M時(shí),擴(kuò)容后的buf 大小 = 業(yè)務(wù)串預(yù)期長(zhǎng)度 * 2 + 1,也就是擴(kuò)大2倍。
- 對(duì)于大小 > 1M的長(zhǎng)串,buf總是留出 1M的 free空間,即2倍擴(kuò)容,但是free最大為 1M。
(3)字節(jié)串與字符串
SDS中存儲(chǔ)的內(nèi)容可以是ASCII 字符串,也可以是字節(jié)串
由于SDS通過(guò)len 字段來(lái)確定業(yè)務(wù)串的長(zhǎng)度,因此業(yè)務(wù)串可以存儲(chǔ)非文本內(nèi)容
對(duì)于字符串的場(chǎng)景,buf[len] 作為業(yè)務(wù)串結(jié)尾的'\0' 又可以復(fù)用C的已有字符串函數(shù)
(4)SDS編碼的優(yōu)化
value 在內(nèi)存中有2個(gè)部分:redisObject和ptr 指向的字節(jié)串部分。在創(chuàng)建時(shí),通常要分別為2個(gè)部分申請(qǐng)內(nèi)存,但是對(duì)于小字節(jié)串,可以一次性申請(qǐng)。
除此之外,string 類(lèi)型的value還有一些CAS的原子操作,如:get、set、set value nx(如果不存在就設(shè)置)、set value xx(如果存在就設(shè)置)。
String 類(lèi)型是二進(jìn)制安全的,也就是說(shuō)在Redis中String類(lèi)型可以包含各種數(shù)據(jù),比如一張JPEG圖片或者是一個(gè)序列化的Ruby對(duì)象。一個(gè)String類(lèi)型的值最大長(zhǎng)度可以是512M。
在Redis中String有很多有趣的用法
- 把String當(dāng)做原子計(jì)數(shù)器,這可以使用INCR家族中的命令來(lái)實(shí)現(xiàn):INCR, DECR, INCRBY。
- 使用APPEND命令來(lái)給一個(gè)String追加內(nèi)容。
- 把String當(dāng)做一個(gè)隨機(jī)訪問(wèn)的向量(Vector),這可以使用GETRANGE和 SETRANGE命令來(lái)實(shí)現(xiàn)
- 使用GETBIT 和SETBIT方法,在一個(gè)很小的空間中編碼大量的數(shù)據(jù),或者創(chuàng)建一個(gè)基于Redis的Bloom Filter 算法。
5 List
Redis的列表類(lèi)型中存儲(chǔ)一系列String值,這些String按照插入的順序排序
Redis的List可以從頭部(左側(cè))加入元素,也可以從尾部(右側(cè))加入元素
5.1 內(nèi)存數(shù)據(jù)結(jié)構(gòu)
List 類(lèi)型的 value對(duì)象,由 linkedlist 或者 ziplist 實(shí)現(xiàn)
當(dāng) List 元素個(gè)數(shù)少并且元素內(nèi)容長(zhǎng)度不大
采用ziplist 實(shí)現(xiàn),否則使用linkedlist
5.1.1 linkedlist實(shí)現(xiàn)
鏈表的代碼結(jié)構(gòu)
typedef struct list {
// 頭結(jié)點(diǎn)
listNode *head;
// 尾節(jié)點(diǎn)
listNode *tail;
// 節(jié)點(diǎn)值復(fù)制函數(shù)
void *(*dup)(void * ptr);
// 節(jié)點(diǎn)值釋放函數(shù)
void *(*free)(void *ptr);
// 節(jié)點(diǎn)值對(duì)比函數(shù)
int (*match)(void *ptr, void *key);
// 鏈表長(zhǎng)度
unsigned long len;
} list;
// Node節(jié)點(diǎn)結(jié)構(gòu)
typedef struct listNode {
struct listNode *prev;
struct listNode *next;
void *value;
} listNode;
5.1.2 ziplist實(shí)現(xiàn)
ziplist 存儲(chǔ)在連續(xù)內(nèi)存
- zlbytes:表示ziplist 的總長(zhǎng)度
- zltail:指向最末元素。
- zllen:表示元素的個(gè)數(shù)。
- entry:為元素內(nèi)容。
- zlend:恒為0xFF,作為ziplist的定界符
從上面的結(jié)構(gòu)可以看出,對(duì)于linkedlist和 ziplist,它們的rpush、rpop、llen的時(shí)間復(fù)雜度都是O(1)
但是對(duì)于ziplist,lpush、lpop都會(huì)牽扯到所有數(shù)據(jù)的移動(dòng),時(shí)間復(fù)雜度為O(N)
但是由于List的元素少,體積小,這種情況還是可控的
對(duì)于ziplist 的每個(gè)Entry 其結(jié)構(gòu)如下
記錄前一個(gè)相鄰的Entry的長(zhǎng)度,作用是方便進(jìn)行雙向遍歷,類(lèi)似于linkedlist 的prev 指針
ziplist是連續(xù)存儲(chǔ),指針由偏移量來(lái)承載
Redis中實(shí)現(xiàn)了2種方式的實(shí)現(xiàn)
- 當(dāng)前鄰 Entry的長(zhǎng)度小于254 時(shí),使用1字節(jié)來(lái)實(shí)現(xiàn)
- 否則使用5個(gè)字節(jié)
這里面會(huì)有個(gè)問(wèn)題,就是當(dāng)前一個(gè)Entry的長(zhǎng)度變化時(shí),這時(shí)候有可能會(huì)造成后續(xù)的所有空間移動(dòng)。當(dāng)然這種情況發(fā)生的可能性比較小。
Entry內(nèi)容本身是自描述的,意味著第二部分(Entry內(nèi)容)包含了幾個(gè)信息:Entry內(nèi)容類(lèi)型、長(zhǎng)度和內(nèi)容本身。而內(nèi)容本身包含:類(lèi)型長(zhǎng)度部分和內(nèi)容本身部分。類(lèi)型和長(zhǎng)度同樣采用變長(zhǎng)編碼:
- 00xxxxxx :string類(lèi)型;長(zhǎng)度小于64,0~63可由6位bit 表示,即xxxxxx表示長(zhǎng)度
- 01xxxxxx|yyyyyyyy : string類(lèi)型;長(zhǎng)度范圍是[64, 16383],可由14位 bit 表示,即xxxxxxyyyyyyyy這14位表示長(zhǎng)度。
- 10xxxxxx|yy..y(32個(gè)y) : string類(lèi)型,長(zhǎng)度大于16383.
- 1111xxxx :integer類(lèi)型,integer本身內(nèi)容存儲(chǔ)在xxxx 中,只能是1~13之間取值。也就是說(shuō)內(nèi)容類(lèi)型已經(jīng)包含了內(nèi)容本身。
- 11xxxxxx :其余的情況,Redis用1個(gè)字節(jié)的類(lèi)型長(zhǎng)度表示了integer的其他幾種情況,如:int_32、int_24等。
由此可見(jiàn),ziplist 的元素結(jié)構(gòu)采用的是可變長(zhǎng)的壓縮方法,針對(duì)于較小的整數(shù)/字符串的壓縮效果較好
LPUSH 命令是在頭部加入一個(gè)新元素,RPUSH 命令是在尾部加入一個(gè)新元素。當(dāng)在一個(gè)空的鍵值(key)上執(zhí)行這些操作時(shí)會(huì)創(chuàng)建一個(gè)新的列表。類(lèi)似的,當(dāng)一個(gè)操作清空了一個(gè)list時(shí),這個(gè)list對(duì)應(yīng)的key會(huì)被刪除。這非常好理解,因?yàn)閺拿畹拿志涂梢钥闯鲞@個(gè)命令是做什么操作的。如果使用一個(gè)不存在的key調(diào)用的話(huà)就會(huì)使用一個(gè)空的list。
一些例子:
LPUSH mylist a # 現(xiàn)在list是 "a"
LPUSH mylist b # 現(xiàn)在list是"b","a"
RPUSH mylist c # 現(xiàn)在list是 "b","a","c" (注意這次使用的是 RPUSH)
list的最大長(zhǎng)度是2^32 – 1個(gè)元素(4294967295,一個(gè)list中可以有多達(dá)40多億個(gè)元素)
從時(shí)間復(fù)雜度的角度來(lái)看,Redis list類(lèi)型的最大特性是:即使是在list的頭端或者尾端做百萬(wàn)次的插入和刪除操作,也能保持穩(wěn)定的很少的時(shí)間消耗。在list的兩端訪問(wèn)元素是非常快的,但是如果要訪問(wèn)一個(gè)很大的list中的中間部分的元素就會(huì)比較慢了,時(shí)間復(fù)雜度是O(N)
Redis的List類(lèi)型有很多有趣的用法
- 在社交網(wǎng)絡(luò)中使用List進(jìn)行時(shí)間表建模,使用LPUSH命令在用戶(hù)時(shí)間線(xiàn)中加入新的元素,然后使用LRANGE 命令來(lái)獲得最近加入的元素。
- 可以把LPUSH 和LTRIM 命令結(jié)合使用來(lái)實(shí)現(xiàn)定長(zhǎng)的列表,列表中只保存最近的N個(gè)元素
- 在創(chuàng)建后臺(tái)運(yùn)行的工作時(shí),Lists可以作為消息傳遞原語(yǔ),例如著名的Ruby庫(kù) Resque
- 還有很多可以使用lists來(lái)做的事,這種數(shù)據(jù)類(lèi)型支持很多命令,包括像BLPOP這樣的阻塞命令
6 Sets
Set類(lèi)似List,但是它是一個(gè)無(wú)序集合,包含的元素不重復(fù)
向集合中添加多次相同的元素,集合中只存在一個(gè)該元素。在實(shí)際應(yīng)用中,這意味著在添加一個(gè)元素前不需要先檢查元素是否存在。
支持多個(gè)服務(wù)器端命令來(lái)從現(xiàn)有集合開(kāi)始計(jì)算集合,所以執(zhí)行集合的交集,并集,差集都可以很快
set的最大長(zhǎng)度是2^32 – 1個(gè)元素(4294967295,一個(gè)set中可以有多達(dá)40多億個(gè)元素)
6.1 內(nèi)存數(shù)據(jù)結(jié)構(gòu)
Set在Redis中以intset 或 hashtable來(lái)存儲(chǔ)。Hashtable前面已經(jīng)介紹過(guò)了,對(duì)于Set,HashTable的value永遠(yuǎn)為NULL
當(dāng)Set中只包含整型數(shù)據(jù)時(shí),采用intset作為實(shí)現(xiàn)
6.1 .1 intset
intset的核心元素是一個(gè)字節(jié)數(shù)組,其中從小到大有序的存放著set的元素
typedef struct intset {
// 編碼方式
uint32_t enconding;
// 集合包含的元素?cái)?shù)量
uint32_t length;
// 保存元素的數(shù)組
int8_t contents[];
} intset;
因?yàn)樵赜行蚺帕校許ET的獲取操作采用二分查找的方式,復(fù)雜度為O(log(N))
進(jìn)行插入操作時(shí),首先通過(guò)二分查找到要插入的位置,再對(duì)元素進(jìn)行擴(kuò)容
然后將插入位置之后的所有元素向后移動(dòng)一個(gè)位置,最后插入元素。時(shí)間復(fù)雜度為O(N)。
為了使二分查找的速度足夠快,存儲(chǔ)在content 中的元素是定長(zhǎng)的。
當(dāng)插入2018 時(shí),所有的元素向后移動(dòng),并且不會(huì)發(fā)生覆蓋的情況
并且當(dāng)Set 中存放的整型元素集中在小整數(shù)范圍[-128, 127]內(nèi)時(shí),可以大大的節(jié)省內(nèi)存空間。這里面需要注意的是:IntSet支持升級(jí),但是不支持降級(jí)。
記錄唯一的事物
比如,你想知道訪問(wèn)某個(gè)博客的IP地址,不要重復(fù)的IP,這種情況只需要在每次處理一個(gè)請(qǐng)求時(shí)簡(jiǎn)單的使用SADD命令就可以了,可以確信不會(huì)插入重復(fù)的IP表示關(guān)系
你可以使用Redis創(chuàng)建一個(gè)標(biāo)簽系統(tǒng),每個(gè)標(biāo)簽使用一個(gè)Set來(lái)表示。然后你可以使用SADD命令把具有特定標(biāo)簽的所有對(duì)象的所有ID放在表示這個(gè)標(biāo)簽的Set中
如果你想要知道同時(shí)擁有三個(gè)不同標(biāo)簽的對(duì)象,那么使用SINTER命令就好了你可以使用SPOP 或者 SRANDMEMBER 命令從集合中隨機(jī)的提取元素。
7 Hashes/ Maps
因?yàn)镽edis本身是一個(gè)key - valueObject的結(jié)構(gòu),Hash類(lèi)型的結(jié)構(gòu)可以理解為subkey - subvalue
這里面的subkey - subvalue只能是
- 整型
- 浮點(diǎn)型
- 字符串
因?yàn)镸ap的value 可以表示整型和浮點(diǎn)型,因此Map也可以使用hincrby
對(duì)某個(gè)field的value值做自增操作
7.1 內(nèi)存數(shù)據(jù)結(jié)構(gòu)
Map可以由HashTable 和 ziplist 兩種方式來(lái)承載。對(duì)于數(shù)據(jù)量較小的Map,使用ziplist 實(shí)現(xiàn)
7.1.1 HashTable 實(shí)現(xiàn)
HashTable在Redis 中分為3 層,自底向上分別是:
- dictEntry:管理一個(gè)field - value 對(duì),保留同一桶中相鄰元素的指針,以此維護(hù)Hash 桶中的內(nèi)部鏈
- dictht:維護(hù)Hash表的所有桶鏈
- dict:當(dāng)dictht需要擴(kuò)容/縮容時(shí),用戶(hù)管理dictht的遷移
dict是Hash表存儲(chǔ)的頂層結(jié)構(gòu)
// 哈希表(字典)數(shù)據(jù)結(jié)構(gòu),Redis 的所有鍵值對(duì)都會(huì)存儲(chǔ)在這里。其中包含兩個(gè)哈希表。
typedef struct dict {
// 哈希表的類(lèi)型,包括哈希函數(shù),比較函數(shù),鍵值的內(nèi)存釋放函數(shù)
dictType *type;
// 存儲(chǔ)一些額外的數(shù)據(jù)
void *privdata;
// 兩個(gè)哈希表
dictht ht[2];
// 哈希表重置下標(biāo),指定的是哈希數(shù)組的數(shù)組下標(biāo)
int rehashidx; /* rehashing not in progress if rehashidx == -1 */
// 綁定到哈希表的迭代器個(gè)數(shù)
int iterators; /* number of iterators currently running */
} dict;
Hash表的核心結(jié)構(gòu)是dictht,它的table 字段維護(hù)著 Hash 桶,桶(bucket)是一個(gè)數(shù)組,數(shù)組的元素指向桶中的第一個(gè)元素(dictEntry)。
typedef struct dictht {
//槽位數(shù)組
dictEntry **table;
//槽位數(shù)組長(zhǎng)度
unsigned long size;
//用于計(jì)算索引的掩碼
unsigned long sizemask;
//真正存儲(chǔ)的鍵值對(duì)數(shù)量
unsigned long used;
} dictht;
從上圖可以看出,Hash表使用的是 鏈地址法 解決Hash沖突
當(dāng)一個(gè)bucket 中的Entry 很多時(shí),Hash表的插入性能會(huì)下降,此時(shí)就需要增加bucket的個(gè)數(shù)來(lái)減少Hash 沖突
7.1.1.1 Hash表擴(kuò)容
和大多數(shù)Hash表實(shí)現(xiàn)一樣,Redis引入負(fù)載因子判定是否需要增加bucket個(gè)數(shù)
負(fù)載因子 = Hash表中已有元素 / bucket數(shù)量
擴(kuò)容之后bucket的數(shù)量是原先的2倍
目前有2 個(gè)閥值:
小于1 時(shí)一定不擴(kuò)容
大于5 時(shí)一定擴(kuò)容
在1 ~ 5 之間時(shí),Redis 如果沒(méi)有進(jìn)行
bgsave/bdrewrite
操作時(shí)則會(huì)擴(kuò)容當(dāng)key - value 對(duì)減少時(shí),低于0.1時(shí)會(huì)進(jìn)行縮容。縮容之后,bucket的個(gè)數(shù)是原先的0.5倍
7.1.2 ziplist 實(shí)現(xiàn)
這里面的ziplist 和List的ziplist實(shí)現(xiàn)類(lèi)似,都是通過(guò)Entry 存放element
和List不同的是,Map對(duì)應(yīng)的ziplist 的Entry 個(gè)數(shù)總是2的整數(shù)倍,第奇數(shù)個(gè)Entry 存放key,下一個(gè)相鄰的Entry存放value
ziplist承載時(shí),Map的大多數(shù)操作不再是O(1)了,而是由Hash表遍歷,變成了鏈表的遍歷,復(fù)雜度變?yōu)镺(N)
由于Map相對(duì)較小時(shí)采用ziplist,采用Hash表時(shí)計(jì)算hash值的開(kāi)銷(xiāo)較大,因此綜合起來(lái)ziplist的性能相對(duì)好一些
Redis Hashes 保存String域和String值之間的映射,所以它們是用來(lái)表示對(duì)象的絕佳數(shù)據(jù)類(lèi)型(比如一個(gè)有著用戶(hù)名,密碼等屬性的User對(duì)象)
| `1` | `@cli` |
| `2` | `HMSET user:1000 username antirez password P1pp0 age 34` |
| `3` | `HGETALL user:1000` |
| `4` | `HSET user:1000 password 12345` |
| `5` | `HGETALL user:1000` |
一個(gè)有著少量數(shù)據(jù)域(這里的少量大概100上下)的hash,其存儲(chǔ)方式占用很小的空間,所以在一個(gè)小的Redis實(shí)例中就可以存儲(chǔ)上百萬(wàn)的這種對(duì)象
Hash的最大長(zhǎng)度是2^32 – 1個(gè)域值對(duì)(4294967295,一個(gè)Hash中可以有多達(dá)40多億個(gè)域值對(duì))
8 Sorted sets 類(lèi)型(有序集合類(lèi)型)
類(lèi)似于Map的key-value對(duì),但有序
- key :key-value對(duì)中的鍵,在一個(gè)Sorted-Set中不重復(fù)
- value : 浮點(diǎn)數(shù),稱(chēng)為 score
- 有序 :內(nèi)部按照score 從小到大的順序排列
8.1 基本操作
由于Sorted-Set 本身包含排序信息,在普通Set 的基礎(chǔ)上,Sorted-Set 新增了一系列和排序相關(guān)的操作:
8.2 內(nèi)部數(shù)據(jù)結(jié)構(gòu)
Sorted-Set類(lèi)型的valueObject 內(nèi)部由
- ziplist
作為ziplist的實(shí)現(xiàn)方式和Map類(lèi)似,由于Sorted-Set包含了Score的排序信息,ziplist內(nèi)部的key-value元素對(duì)的排序方式也是按照Score遞增排序的,意味著每次插入數(shù)據(jù)都要移動(dòng)之后的數(shù)據(jù)
因此ziplist適用于元素個(gè)數(shù)不多,元素內(nèi)容不大的場(chǎng)景。 - skiplist+hashtable
對(duì)于更通用的場(chǎng)景,Sorted-Set使用sliplist來(lái)實(shí)現(xiàn)。
8.2.1 skiplist
和通用的跳表不同的是,Redis為每個(gè)level 對(duì)象增加了span 字段,表示該level 指向的forward節(jié)點(diǎn)和當(dāng)前節(jié)點(diǎn)的距離,使得getByRank類(lèi)的操作效率提升
typedef struct zskiplist {
//表頭節(jié)點(diǎn)和表尾節(jié)點(diǎn)
structz skiplistNode *header,*tail;
//表中節(jié)點(diǎn)數(shù)量
unsigned long length;
//表中層數(shù)最大的節(jié)點(diǎn)的層數(shù)
int level;
} zskiplist;
從上圖可以看出,每次向skiplist 中新增或者刪除一個(gè)節(jié)點(diǎn)時(shí),需要同時(shí)修改圖標(biāo)中紅色的箭頭,修改其forward和span的值
需要修改的箭頭和對(duì)skip進(jìn)行查找操作遍歷并廢棄過(guò)的路徑是吻合的
對(duì)于span的修改僅僅是+1或者-1 。skiplist 的查找復(fù)雜度平均是 O(Log(N)),因此add / remove的復(fù)雜度也是O(Log(N))。因此Redis中新增的span 提升了獲取rank(排序)操作的性能,僅需對(duì)遍歷路徑相加即可(矢量相加)
還有一點(diǎn)需要注意的是,每個(gè)skiplist的節(jié)點(diǎn)level 大小都是隨機(jī)生成的(1-32之間)。
skiplistNode的代碼結(jié)構(gòu)
typedef struct zskiplistNode {
// 層
struct zskiplistLevel{
struct zskiplistNode *forward; // 前進(jìn)指針
unsigned int span; // 跨度
} level[];
// 后退指針
struct zskiplistNode *backward;
// 分值
double score;
// 成員對(duì)象
robj *obj;
}
8.2.2 hashtable
skiplist 是zset 實(shí)現(xiàn)順序相關(guān)操作比較高效的數(shù)據(jù)結(jié)構(gòu),但是對(duì)于簡(jiǎn)單的zscore操作效率并不高。Redis在實(shí)現(xiàn)時(shí),同時(shí)使用了Hashtable和skiplist,代碼結(jié)構(gòu)如下:
typedef struct zset {
dict *dict;
zskiplist *zsl;
} zset;
Hash表的存在使得Sorted-Set中的Map相關(guān)操作復(fù)雜度由O(N)變?yōu)镺(1)。
Redis有序集合類(lèi)型與Redis的集合類(lèi)型類(lèi)似,是非重復(fù)的String元素的集合。不同之處在于,有序集合中的每個(gè)成員都關(guān)聯(lián)一個(gè)Score,Score是在排序時(shí)候使用的,按照Score的值從小到大進(jìn)行排序。集合中每個(gè)元素是唯一的,但Score有可能重復(fù)。
使用有序集合可以很高效的進(jìn)行,添加,移除,更新元素的操作(時(shí)間消耗與元素個(gè)數(shù)的對(duì)數(shù)成比例)。由于元素在集合中的位置是有序的,使用get ranges by score或者by rank(位置)來(lái)順序獲取或者隨機(jī)讀取效率都很高。(本句不確定,未完全理解原文意思,是根據(jù)自己對(duì)Redis的淺顯理解進(jìn)行的翻譯)訪問(wèn)有序集合中間部分的元素也非常快,所以可以把有序集合當(dāng)做一個(gè)不允許重復(fù)元素的智能列表,你可以快速訪問(wèn)需要的一切:獲取有序元素,快速存在測(cè)試,快速訪問(wèn)中間的元素等等。
簡(jiǎn)短來(lái)說(shuō),使用有序集合可以實(shí)現(xiàn)很多高性能的工作,這一點(diǎn)在其他數(shù)據(jù)庫(kù)是很難實(shí)現(xiàn)的。
使用有序集合你可以:
在大型在線(xiàn)游戲中創(chuàng)建一個(gè)排行榜,每次有新的成績(jī)提交,使用ZADD命令加入到有序集合中。可以使用ZRANGE命令輕松獲得成績(jī)名列前茅的玩家,你也可以使用ZRANK根據(jù)一個(gè)用戶(hù)名獲得該用戶(hù)的分?jǐn)?shù)排名。把ZRANK 和 ZRANGE結(jié)合使用你可以獲得與某個(gè)指定用戶(hù)分?jǐn)?shù)接近的其他用戶(hù)。這些操作都很高效。
有序集合經(jīng)常被用來(lái)索引存儲(chǔ)在Redis中的數(shù)據(jù)。比如,如果你有很多用戶(hù),用Hash來(lái)表示,可以使用有序集合來(lái)為這些用戶(hù)創(chuàng)建索引,使用年齡作為Score,使用用戶(hù)的ID作為Value,這樣的話(huà)使用ZRANGEBYSCORE 命令可以輕松和快速的獲得某一年齡段的用戶(hù)。
有序集合可能是Redis中最高級(jí)的數(shù)據(jù)類(lèi)型了,所以請(qǐng)花一些時(shí)間查看一下 有序集合命令列表 來(lái)獲得更多信息,同時(shí)你可能也想閱讀Redis數(shù)據(jù)類(lèi)型介紹
Bitmaps and HyperLogLogs類(lèi)型(位圖類(lèi)型和HyperLogLogs類(lèi)型)
Redis 也支持位圖類(lèi)型和HyperLogLogs 類(lèi)型,他們是在String基本類(lèi)型基礎(chǔ)上建立的類(lèi)型,但有自己的語(yǔ)義。