有序集sortedset算是redis中乙個很有特色的資料結構,通過這篇文章來總結一下這塊知識點。redis中的有序集,允許使用者使用指定值對放進去的元素進行排序,並且基於該已排序的集合提供了一系列豐富的操作集合的api。
舉例如下:
//新增元素,table1為有序集的名字,100為用於排序字段(redis把它叫做score),a為我們要儲存的元素
127.0.0.1:6379> zadd table1 100 a
(integer) 1
127.0.0.1:6379> zadd table1 200 b
(integer) 1
127.0.0.1:6379> zadd table1 300 c
(integer) 1
//按照元素索引返回有序集中的元素,索引從0開始
127.0.0.1:6379> zrange table1 0 1
1) "a"
2) "b"
//按照元素排序範圍返回有序集中的元素,這裡用於排序的字段在redis中叫做score
127.0.0.1:6379> zrangebyscore table1 150 400
1) "b"
2) "c"
//刪除元素
127.0.0.1:6379> zrem table1 b
(integer) 1
在有序集中,用於排序的值叫做score,實際儲存的值叫做member。由於有序集中提供的api較多,這裡只舉了幾個常見的,具體可以參考redis文件。
void zaddcommand(client *c)
這裡可以看到流程轉向了zaddgenericcommand,並且傳入了乙個模式標記。
關於sortedset的操作模式這裡簡單說明一下,先來看一條完整的zadd命令:
zadd key [nx|xx] [ch] [incr] score member [score member ...]
其中的可選項我們依次看下:
nx表示如果元素存在,則不執行替換操作直接返回。
xx表示只操作已存在的元素。
ch表示返回修改(包括新增,更新)元素的數量,只能被zadd命令使用。
incr表示在原來的score基礎上加上新的score,而不是替換。
上面**片段中的zadd_none表示普通操作。
接下來看下zaddgenericcommand函式的原始碼,很長,耐心一點點看:
void zaddgenericcommand(client *c, int flags)
//設定模式
int incr = (flags & zadd_incr) != 0;
int nx = (flags & zadd_nx) != 0;
int xx = (flags & zadd_xx) != 0;
int ch = (flags & zadd_ch) != 0;
//通過上面的解析,scoreidx為真實的初始score的索引位置
//這裡客戶端引數數量減去scoreidx就是剩餘所有元素的數量
elements = c->argc - scoreidx;
//由於有序集中score,member成對出現,所以加一層判斷
if (elements % 2 || !elements)
//這裡計算score,member有多少對
elements /= 2;
//引數合法性校驗
if (nx && xx)
//引數合法性校驗
if (incr && elements > 1)
//這裡開始解析score,先初始化scores陣列
scores = zmalloc(sizeof(double)*elements);
for (j = 0; j < elements; j++)
//這裡首先在client對應的db中查詢該key,即有序集
zobj = lookupkeywrite(c->db,key);
if (zobj == null) else
//加入db中
dbadd(c->db,key,zobj);
} else
}//這裡開始往有序集中新增元素
for (j = 0; j < elements; j++)
//記錄操作
if (retflags & zadd_added) added++;
if (retflags & zadd_updated) updated++;
if (!(retflags & zadd_nop)) processed++;
//設定新score值
score = newscore;
}//操作記錄
server.dirty += (added+updated);
//返回邏輯
reply_to_client:
if (incr) else
//清理邏輯
cleanup:
zfree(scores);
if (added || updated)
}
**有點長,來張圖看一下儲存結構:
注:每個entry都是由score+member組成
有了上面的結構圖以後,可以想到刪除操作應該就是根據不同的儲存結構進行,如果是ziplist就執行鍊錶刪除,如果是雜湊表+跳表結構,那就要把兩個集合都進行刪除。真實邏輯是什麼呢?
我們來看下刪除函式zremcommand的原始碼,相對短一點:
void zremcommand(client *c)
}//同步操作
if (deleted)
//返回
addreplylonglong(c,deleted);
}
看下具體的刪除操作原始碼:
//引數zobj為有序集,ele為要刪除的元素
int zsetdel(robj *zobj, sds ele)
} else if (zobj->encoding == obj_encoding_skiplist)
} else
//沒有找到指定元素返回0
return 0;
}
最後看乙個查詢函式zrangecommand原始碼,也是很長,汗~~~,不過放心,有了上面的基礎,大致也能猜到查詢邏輯應該是什麼樣子的:
void zrangecommand(client *c)
void zrangegenericcommand(client *c, int reverse) else if (c->argc >= 5)
//有序集校驗
if ((zobj = lookupkeyreadorreply(c,key,shared.emptymultibulk)) == null
|| checktype(c,zobj,obj_zset)) return;
//索引值重置
llen = zsetlength(zobj);
if (start < 0) start = llen+start;
if (end < 0) end = llen+end;
if (start < 0) start = 0;
//返回空集
if (start > end || start >= llen)
if (end >= llen) end = llen-1;
rangelen = (end-start)+1;
//返回給客戶端結果長度
addreplymultibulklen(c, withscores ? (rangelen*2) : rangelen);
//同樣是根據有序集的不同結構執行不同的查詢邏輯
if (zobj->encoding == obj_encoding_ziplist)
} else if (zobj->encoding == obj_encoding_skiplist) else
//遍歷並返回給客戶端
while(rangelen--)
} else
}
上面就是關於有序集sortedset的新增,刪除,查詢的原始碼。可以看出sortedset會根據存放元素的數量選擇ziplist或者雜湊表+跳表兩種資料結構進行實現,之所以原始碼看上去很長,主要原因也就是要根據不同的資料結構進行不同的**實現。只要掌握了這個核心思路,再看原始碼就不會太難。
有序集的邏輯不難,就是**有點長,涉及到ziplist,skiplist,dict三套資料結構,其中除了常規的dict之外,另外兩個資料結構內容都不少,準備專門寫文章進行總結,就不在這裡贅述了。本文主要目的是總結一下有序集sortedset的實現原理。
Redis原始碼分析 intset h c
intset.h c 是redis 的整數set實現,intset的結構體如下 基本結構 typedef struct intset intset intset的第乙個成員encoding,表明contents中的儲存資料的資料長度,可以是16bits,32bits,64bits。第二個成員leng...
Redis原始碼分析系列
redis目前熱門nosql記憶體資料庫,量不是很大,本系列是本人閱讀redis原始碼時記錄的筆記,由於時間倉促和水平有限,文中難免會有錯誤之處,歡迎讀者指出,共同學習進步,本文使用的redis版本是2.8.19。redis之hash資料結構 redis之intset資料結構 redis之skipl...
redis原始碼分析 adlist
typedef struct listnode listnode 首先定義了乙個節點,包含前驅和後繼以及對應的value typedef struct listiter listiter list的迭代器,next指標和迭代方向 typedef struct list list 鍊錶內容 head和...