Redis的排序命令SORT: 將給定的列表 或集合或 有序集的鍵的元素對應到一個數組中去,然後按給定選項排序,然後返回排序後的元素值;或者將排序後的元素保存到一個給定的鍵,並存入數據庫字典中,返回所保存的元素個數。
具體排序命令用法可參考:http://www.cnblogs.com/linjiqin/archive/2013/06/14/3135921.html
其中數組的元素爲結構:
typedef struct _redisSortObject {
robj *obj;//被排序的鍵的值
//權重
union {
double score;//給數值排序時使用
robj *cmpobj;// 排序帶有 BY 選項的字符串值時使用(當 SORT 命令使用了 BY 選項時, 命令使用其他鍵的值作爲權重來進行排序操作)
} u;
} redisSortObject;//排序數值元素 結構
排序前,將給定鍵的元素的redisSortObject結構對應到數組中去,如圖一:
而排序後,如圖二:
排序命令的實現步驟:
*1* 創建一個和給定鍵值長度相同的數組,該數組的每個項都是redisSortObject結構
*2* 遍歷數組,將數組的每個項的obj指針分別指向給定鍵的各個值,使數組與鍵值列表(以列表爲例)構成一對一。
*3*遍歷數組,將各個obj指針所指的列表元素轉換成一個double類型(或者字符串,與給定選項相關),並將給浮點數保存到obj的u.score中。如上圖1所示。
*4*根據u.score(與給定選項相關:排序比較的權值不一樣)的值,對數值進行排序(按升序或降序改變obj指針指向的類表元素)。如上圖二所示。
*5*遍歷數組,將各個obj指向的列表項作爲排序的結果返回給客戶端。或者將排序結構存放到給定鍵的值中,並將該鍵值存入數據庫。
排序命令函數:
排序時採用快速排序算法。該函數是Redis中最複雜的函數,實現了上面的五個處理步驟。先了解排序命令的基本用法,有助於更好的理解該命令的實現過程。其中 比如在處理有序集排序時的優化處理等。
(其中快速排序基本實現參考:http://blog.csdn.net/yuyixinye/article/details/40589607)
<span style="font-size:18px;">void sortCommand(redisClient *c) {
list *operations;//操作鏈表
unsigned int outputlen = 0;//要回復的元素個數
int desc = 0, alpha = 0;
long limit_start = 0, limit_count = -1, start, end;
int j, dontsort = 0, vectorlen;
int getop = 0; /* GET操作計數 GET operation counter */
int int_convertion_error = 0;
int syntax_error = 0;
robj *sortval, *sortby = NULL, *storekey = NULL;
redisSortObject *vector; /* Resulting vector to sort */
/* Lookup the key to sort. It must be of the right types */
// 獲取要排序的鍵,並檢查他是否可以被排序的類型
sortval = lookupKeyRead(c->db,c->argv[1]);
if (sortval && sortval->type != REDIS_SET &&
sortval->type != REDIS_LIST &&
sortval->type != REDIS_ZSET)
{
addReply(c,shared.wrongtypeerr);
return;
}
/* Create a list of operations to perform for every sorted element.
* Operations can be GET/DEL/INCR/DECR */
// 創建一個鏈表,鏈表中保存了要對所有已排序元素執行的操作
// 操作可以是 GET 、 DEL 、 INCR 或者 DECR
operations = listCreate();//操作鏈表
listSetFreeMethod(operations,zfree);
// 指向選項參數位置
j = 2; /* options start at argv[2] */
/* Now we need to protect sortval incrementing its count, in the future
* SORT may have options able to overwrite/delete keys during the sorting
* and the sorted key itself may get destroyed */
// 爲 sortval 的引用計數增一
// 在將來, SORT 命令可以在排序某個鍵的過程中,覆蓋或者刪除那個鍵
if (sortval)
incrRefCount(sortval);
else
sortval = createListObject();
/* The SORT command has an SQL-alike syntax, parse it */
// 讀入並分析 SORT 命令的選項
while(j < c->argc) {
int leftargs = c->argc-j-1;
// ASC 選項 升序
if (!strcasecmp(c->argv[j]->ptr,"asc")) {
desc = 0;
// DESC 選項 降序對比來比較被排序的元素
} else if (!strcasecmp(c->argv[j]->ptr,"desc")) {
desc = 1;
// ALPHA 選項 假設被排序鍵包含的都是字符串值, 並且以字符串的方式來進行排序
} else if (!strcasecmp(c->argv[j]->ptr,"alpha")) {
alpha = 1;
// LIMIT 選項 命令只保留排序結果集中 LIMIT 選項指定的元素
} else if (!strcasecmp(c->argv[j]->ptr,"limit") && leftargs >= 2) {
// start 參數和 count 參數
if ((getLongFromObjectOrReply(c, c->argv[j+1], &limit_start, NULL)
!= REDIS_OK) ||
(getLongFromObjectOrReply(c, c->argv[j+2], &limit_count, NULL)
!= REDIS_OK))
{
syntax_error++;
break;
}
j+=2;
// STORE 選項 命令會將排序結果集保存在指定的鍵裏面
} else if (!strcasecmp(c->argv[j]->ptr,"store") && leftargs >= 1) {
// 目標鍵
storekey = c->argv[j+1];
j++;
// BY 選項 命令使用其他鍵的值作爲權重來進行排序操作
} else if (!strcasecmp(c->argv[j]->ptr,"by") && leftargs >= 1) {
// 排序的順序由這個模式決定
sortby = c->argv[j+1];
/* If the BY pattern does not contain '*', i.e. it is constant,
* we don't need to sort nor to lookup the weight keys. */
// 如果 sortby 模式裏面不包含 '*' 符號,
// 那麼無須執行排序操作
if (strchr(c->argv[j+1]->ptr,'*') == NULL) {
dontsort = 1;
} else {
/* If BY is specified with a real patter, we can't accept
* it in cluster mode. */
if (server.cluster_enabled) {
addReplyError(c,"BY option of SORT denied in Cluster mode.");
syntax_error++;
break;
}
}
j++;
// GET 選項 命令會根據排序結果集中的元素, 以及 GET 選項給定的模式,
//查找並返回其他鍵的值, 而不是返回被排序的元素。
} else if (!strcasecmp(c->argv[j]->ptr,"get") && leftargs >= 1) {
// 創建一個 GET 操作
// 不能在集羣模式下使用 GET 選項
if (server.cluster_enabled) {
addReplyError(c,"GET option of SORT denied in Cluster mode.");
syntax_error++;
break;
}
listAddNodeTail(operations,createSortOperation(
REDIS_SORT_GET,c->argv[j+1]));
getop++;
j++;
// 未知選項,語法出錯
} else {
addReply(c,shared.syntaxerr);
syntax_error++;
break;
}
j++;
}
/* Handle syntax errors set during options parsing. */
if (syntax_error) {
decrRefCount(sortval);
listRelease(operations);
return;
}
/* For the STORE option, or when SORT is called from a Lua script,
* we want to force a specific ordering even when no explicit ordering
* was asked (SORT BY nosort). This guarantees that replication / AOF
* is deterministic.
*
* 對於 STORE 選項,以及從 Lua 腳本中調用 SORT 命令的情況來看,
* 我們想即使在沒有指定排序方式的情況下,也強制指定一個排序方法。
* 這可以保證複製/AOF 是確定性的。
*
* However in the case 'dontsort' is true, but the type to sort is a
* sorted set, we don't need to do anything as ordering is guaranteed
* in this special case.
*
* 在 dontsort 爲真,並且被排序的鍵不是有序集合時,
* 我們才需要爲排序指定排序方式,
* 因爲有序集合的成員已經是有序的了。
*/
if ((storekey || c->flags & REDIS_LUA_CLIENT) &&
(dontsort && sortval->type != REDIS_ZSET))
{
/* Force ALPHA sorting */
// 強制 ALPHA 排序
dontsort = 0;
alpha = 1;
sortby = NULL;
}
/* Destructively convert encoded sorted sets for SORT. */
// 被排序的有序集合必須是 SKIPLIST 編碼的
// 如果不是的話,那麼將它轉換成 SKIPLIST 編碼
if (sortval->type == REDIS_ZSET)
zsetConvert(sortval, REDIS_ENCODING_SKIPLIST);
/* Objtain the length of the object to sort. */
// 獲取要排序對象的長度
switch(sortval->type) {
case REDIS_LIST: vectorlen = listTypeLength(sortval); break;
case REDIS_SET: vectorlen = setTypeSize(sortval); break;
case REDIS_ZSET: vectorlen = dictSize(((zset*)sortval->ptr)->dict); break;
default: vectorlen = 0; redisPanic("Bad SORT type"); /* Avoid GCC warning */
}
/* Perform LIMIT start,count sanity checking. */
// 對 LIMIT 選項的 start 和 count 參數進行檢查
start = (limit_start < 0) ? 0 : limit_start;
end = (limit_count < 0) ? vectorlen-1 : start+limit_count-1;
if (start >= vectorlen) {
start = vectorlen-1;
end = vectorlen-2;
}
if (end >= vectorlen) end = vectorlen-1;
/* Optimization:
* 優化
*
* 1) if the object to sort is a sorted set.
* 如果排序的對象是有序集合
* 2) There is nothing to sort as dontsort is true (BY <constant string>).
* dontsort 爲真,表示沒有什麼需要排序
* 3) We have a LIMIT option that actually reduces the number of elements
* to fetch.
* LIMIT 選項所設置的範圍比起有序集合的長度要小
*
* In this case to load all the objects in the vector is a huge waste of
* resources. We just allocate a vector that is big enough for the selected
* range length, and make sure to load just this part in the vector.
*
* 在這種情況下,不需要載入有序集合中的所有元素,只要載入給定範圍(range)內的元素就可以了。
*/
if (sortval->type == REDIS_ZSET &&
dontsort &&
(start != 0 || end != vectorlen-1))
{
vectorlen = end-start+1;
}
/* Load the sorting vector with all the objects to sort */
// 創建 redisSortObject 數組
vector = zmalloc(sizeof(redisSortObject)*vectorlen);//只需分配需要返回的元素的個數所需的空間
j = 0;
// 將列表項放入數組
if (sortval->type == REDIS_LIST) {
listTypeIterator *li = listTypeInitIterator(sortval,0,REDIS_TAIL);
listTypeEntry entry;
while(listTypeNext(li,&entry)) {
vector[j].obj = listTypeGet(&entry);
vector[j].u.score = 0;
vector[j].u.cmpobj = NULL;
j++;
}
listTypeReleaseIterator(li);
// 將集合元素放入數組
} else if (sortval->type == REDIS_SET) {
setTypeIterator *si = setTypeInitIterator(sortval);
robj *ele;
while((ele = setTypeNextObject(si)) != NULL) {
vector[j].obj = ele;
vector[j].u.score = 0;
vector[j].u.cmpobj = NULL;
j++;
}
setTypeReleaseIterator(si);
// 在 dontsort 爲真的情況下
// 將有序集合的部分成員放進數組
} else if (sortval->type == REDIS_ZSET && dontsort) {
/* Special handling for a sorted set, if 'dontsort' is true.
* This makes sure we return elements in the sorted set original
* ordering, accordingly to DESC / ASC options.
*
* Note that in this case we also handle LIMIT here in a direct
* way, just getting the required range, as an optimization. */
// 這是前面說過的,可以進行優化的 case
zset *zs = sortval->ptr;
zskiplist *zsl = zs->zsl;
zskiplistNode *ln;
robj *ele;
int rangelen = vectorlen;
/* Check if starting point is trivial, before doing log(N) lookup. */
// 根據 desc 或者 asc 排序,指向初始節點
if (desc) {
long zsetlen = dictSize(((zset*)sortval->ptr)->dict);
ln = zsl->tail;
if (start > 0)
ln = zslGetElementByRank(zsl,zsetlen-start);
} else {
ln = zsl->header->level[0].forward;
if (start > 0)
ln = zslGetElementByRank(zsl,start+1);
}
// 遍歷範圍中的所有節點,並放進數組
while(rangelen--) {
redisAssertWithInfo(c,sortval,ln != NULL);
ele = ln->obj;
vector[j].obj = ele;
vector[j].u.score = 0;
vector[j].u.cmpobj = NULL;
j++;
ln = desc ? ln->backward : ln->level[0].forward;
}
/* The code producing the output does not know that in the case of
* sorted set, 'dontsort', and LIMIT, we are able to get just the
* range, already sorted, so we need to adjust "start" and "end"
* to make sure start is set to 0. */
end -= start;
start = 0;
// 普通情況下的有序集合,將所有集合成員放進數組
} else if (sortval->type == REDIS_ZSET) {
dict *set = ((zset*)sortval->ptr)->dict;
dictIterator *di;
dictEntry *setele;
di = dictGetIterator(set);
while((setele = dictNext(di)) != NULL) {
vector[j].obj = dictGetKey(setele);
vector[j].u.score = 0;
vector[j].u.cmpobj = NULL;
j++;
}
dictReleaseIterator(di);
} else {
redisPanic("Unknown type");
}
redisAssertWithInfo(c,sortval,j == vectorlen);
/* Now it's time to load the right scores in the sorting vector */
// 載入權重值
if (dontsort == 0) {
for (j = 0; j < vectorlen; j++) {
robj *byval;
// 如果使用了 BY 選項,那麼就根據指定的對象作爲權重
if (sortby) {
/* lookup value to sort by */
byval = lookupKeyByPattern(c->db,sortby,vector[j].obj);
if (!byval) continue;
// 如果沒有使用 BY 選項,那麼使用對象本身作爲權重
} else {
/* use object itself to sort by */
byval = vector[j].obj;
}
// 如果是 ALPHA 排序,那麼將對比對象改爲解碼後的 byval
if (alpha) {
if (sortby) vector[j].u.cmpobj = getDecodedObject(byval);
// 否則,將字符串對象轉換成 double 類型
} else {
if (sdsEncodedObject(byval)) {
char *eptr;
// 將字符串轉換成 double 類型
vector[j].u.score = strtod(byval->ptr,&eptr);
if (eptr[0] != '\0' || errno == ERANGE ||
isnan(vector[j].u.score))
{
int_convertion_error = 1;
}
} else if (byval->encoding == REDIS_ENCODING_INT) {
/* Don't need to decode the object if it's
* integer-encoded (the only encoding supported) so
* far. We can just cast it */
// 直接將整數設置爲權重
vector[j].u.score = (long)byval->ptr;
} else {
redisAssertWithInfo(c,sortval,1 != 1);
}
}
/* when the object was retrieved using lookupKeyByPattern,
* its refcount needs to be decreased. */
if (sortby) {
decrRefCount(byval);
}
}
}
// 排序
if (dontsort == 0) {
server.sort_desc = desc;
server.sort_alpha = alpha;
server.sort_bypattern = sortby ? 1 : 0;
server.sort_store = storekey ? 1 : 0;
if (sortby && (start != 0 || end != vectorlen-1))
pqsort(vector,vectorlen,sizeof(redisSortObject),sortCompare, start,end);
else
qsort(vector,vectorlen,sizeof(redisSortObject),sortCompare);
}
/* Send command output to the output buffer, performing the specified
* GET/DEL/INCR/DECR operations if any. */
// 將命令的輸出放到輸出緩衝區
// 然後執行給定的 GET / DEL / INCR 或 DECR 操作
outputlen = getop ? getop*(end-start+1) : end-start+1;
if (int_convertion_error) {
addReplyError(c,"One or more scores can't be converted into double");
} else if (storekey == NULL) {
/* STORE option not specified, sent the sorting result to client */
// STORE 選項未使用,直接將排序結果發送給客戶端
addReplyMultiBulkLen(c,outputlen);
for (j = start; j <= end; j++) {
listNode *ln;
listIter li;
// 沒有設置 GET 選項,直接將結果添加到回覆
if (!getop) addReplyBulk(c,vector[j].obj);
// 有設置 GET 選項。。。
// 遍歷設置的操作
listRewind(operations,&li);
while((ln = listNext(&li))) {
redisSortOperation *sop = ln->value;
// 解釋並查找鍵
robj *val = lookupKeyByPattern(c->db,sop->pattern,
vector[j].obj);
// 執行 GET 操作,將指定鍵的值添加到回覆
if (sop->type == REDIS_SORT_GET) {
if (!val) {
addReply(c,shared.nullbulk);
} else {
addReplyBulk(c,val);
decrRefCount(val);
}
// DEL 、INCR 和 DECR 操作都尚未實現
} else {
/* Always fails */
redisAssertWithInfo(c,sortval,sop->type == REDIS_SORT_GET);
}
}
}
} else {
robj *sobj = createZiplistObject();
/* STORE option specified, set the sorting result as a List object */
// 已設置 STORE 選項,將排序結果保存到列表對象
for (j = start; j <= end; j++) {
listNode *ln;
listIter li;
// 沒有 GET ,直接返回排序元素
if (!getop) {
listTypePush(sobj,vector[j].obj,REDIS_TAIL);
// 有 GET ,獲取指定的鍵
} else {
listRewind(operations,&li);
while((ln = listNext(&li))) {
redisSortOperation *sop = ln->value;
robj *val = lookupKeyByPattern(c->db,sop->pattern,
vector[j].obj);
if (sop->type == REDIS_SORT_GET) {
if (!val) val = createStringObject("",0);
/* listTypePush does an incrRefCount, so we should take care
* care of the incremented refcount caused by either
* lookupKeyByPattern or createStringObject("",0) */
listTypePush(sobj,val,REDIS_TAIL);
decrRefCount(val);
} else {
/* Always fails */
redisAssertWithInfo(c,sortval,sop->type == REDIS_SORT_GET);
}
}
}
}
// 如果排序結果不爲空,那麼將結果列表關聯到數據庫鍵,併發送事件
if (outputlen) {
setKey(c->db,storekey,sobj);
notifyKeyspaceEvent(REDIS_NOTIFY_LIST,"sortstore",storekey,
c->db->id);
server.dirty += outputlen;
// 如果排序結果爲空,那麼只要刪除 storekey 就可以了,因爲沒有結果可以保存
} else if (dbDelete(c->db,storekey)) {
signalModifiedKey(c->db,storekey);
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",storekey,c->db->id);
server.dirty++;
}
decrRefCount(sobj);
addReplyLongLong(c,outputlen);
}
/* Cleanup */
if (sortval->type == REDIS_LIST || sortval->type == REDIS_SET)
for (j = 0; j < vectorlen; j++)
decrRefCount(vector[j].obj);
decrRefCount(sortval);
listRelease(operations);
for (j = 0; j < vectorlen; j++) {
if (alpha && vector[j].u.cmpobj)
decrRefCount(vector[j].u.cmpobj);
}
zfree(vector);
}</span>