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);
- } //Vevb.com
- }
- freeReplyObject(reply);
- /* Disconnects and frees the context */
- redisFree(c);
- return 0;
- }
16分鐘出結果,not good enough.
新聞熱點
疑難解答