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。