sorted set是set的一個升級版本,它在set的基礎上增加了一個順序屬性,這一屬性在添加
修改元素的時候可以指定,每次指定後,zset會自動重新按新的值調整順序。可以理解為有
兩列的mysql表,一列存value,一列存順序。操作中key理解為zset的名字。
和set一樣sorted set也是string類型元素的集合,不同的是每個元素都會關聯一個double
類型的score。sorted set的實現是skip list和hash table的混合體。
當元素被添加到集合中時,一個元素到score的映射被添加到hash table中,所以給定一個
元素獲取score的開銷是O(1),另一個score到元素的映射被添加到skip list,並按照score排
序,所以就可以有序的獲取集合中的元素。添加,刪除操作開銷都是O(log(N))和skip list的
開銷一致,redis 的skip list實現用的是雙向鏈表,這樣就可以逆序從尾部取元素。sorted set最
經常的使用方式應該是作為索引來使用.我們可以把要排序的字段作為score存儲,對象的id
當元素存儲.
下面講一個使用 Sorted Sets 的例子
mysql中有一張表,假設名字為 summary_data吧,記錄數為30M左右,
有一個字段first_path 為varchar(256),需要找到出現次數最多的10個first_path。
方法一 ) 直接sql語句
sql語句很好寫:
代碼如下 復制代碼 SELECT first_path, COUNT(*) AS c FROM summary_data GROUP BY first_path ORDER BY c DESC LIMIT 10;表上面是有索引的, 但是索引的長度為 KEY `first_path` (`first_path`(255)), 也許是這個原因導致了無法使用索引:
id: 1
select_type: SIMPLE
table: summary_data
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 28136948
Extra: Using temporary; Using filesort
這條sql運行了9分鐘。
把first_path都導出來,生成文件 input/first_path, 每行一條記錄,話說這個導出的過程真的很慢。
方法二 ) sort 與 uniq
sort input/first_path | uniq -c |sort -nr | head -n 10
排好序的狀態,就是分組的狀態了。
uniq -c 用來統計每組的數量。
sort -nr 再對統計結果進行降序
一分半鐘搞定。
方法三 ) redis的sorted set
用這種方法,只因為突然想到sorted set就是為此而生的嘛。
client libary 准備用 hiredis.
安裝很簡單: make && make install
可以看到庫文件安裝到了 /usr/local/lib/ 目錄
頭文件安裝到了 /usr/local/include/hiredis/ 目錄
需要把 /usr/local/lib/ 添加到 /etc/ld.so.conf
然後ldconfig
編譯一下:
代碼如下 復制代碼gcc -I /usr/local/include/hiredis -lhiredis ./example.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <hiredis.h>
int main(int argc, char **argv) {
unsigned int j;
redisContext *c;
redisReply *reply;
const char *hostname = (argc > 1) ? argv[1] : "127.0.0.1";
int port = (argc > 2) ? atoi(argv[2]) : 6379;
struct timeval timeout = { 1, 500000 }; // 1.5 seconds
c = redisConnectWithTimeout(hostname, port, timeout);
if (c == NULL || c->err) {
if (c) {
printf("Connection error: %sn", c->errstr);
redisFree(c);
} else {
printf("Connection error: can't allocate redis contextn");
}
exit(1);
}
FILE * fp;
fp = fopen(argv[3], "r");
if (!fp) exit(1);
char line[256];
while(fgets(line, sizeof(line)-1, fp ) != NULL) {
reply = redisCommand(c, "ZINCRBY myzset_firstpath 1 %s" , line);
freeReplyObject(reply);
}
fclose(fp);
reply = redisCommand(c, "ZREVRANGEBYSCORE myzset_firstpath +inf -inf WITHSCORES LIMIT 0 10");
if (reply->type == REDIS_REPLY_ARRAY) {
for (j = 0; j < reply->elements; j+=2) {
printf("%u) %s %sn", (unsigned int)(j/2), reply->element[j]->str, reply->element[j+1]->str);
}
}
freeReplyObject(reply);
/* Disconnects and frees the context */
redisFree(c);
return 0;
}
16分鐘出結果, not good enough。