Jade Dungeon

redis排序

排序命令

redis支持对listsetsorted set元素的排序。排序命令是sort完整的命令格式如下:

SORT [key] [BY pattern] [LIMIT start count] [GET pattern] [ASC|DESC] [ALPHA] [STORE dstkey] 

默认排序

这个是最简单的情况,没有任何选项就是简单的对集合自身元素排序并返回排序结果。下面给个例子:

lpush ml 12    -- (integer) 1
lpush ml 11    -- (integer) 2
lpush ml 23    -- (integer) 3
lpush ml 13    -- (integer) 4

sort  ml       -- 只指明对哪个[key]排序
               -- 1. "11"
               -- 2. "12"
               -- 3. "13"
               -- 4. "23"

升序与降序:[asc|desc] [alpha]

  • sort默认的排序方式asc是从小到大排的。
  • 逆序可以加上desc选项,
  • 想按字母顺序排可以加alpha选项
lpush mylist baidu        -- (integer) 1
lpush mylist hello        -- (integer) 2
lpush mylist xhan         -- (integer) 3
lpush mylist soso         -- (integer) 4
sort  mylist              -- 某些情况下禁用,非double必须用 alpha 选项
                          -- 1. "soso"
                          -- 2. "xhan"
                          -- 3. "hello"
                          -- 4. "baidu"
sort  mylist alpha
                          -- 1. "baidu"
                          -- 2. "hello"
                          -- 3. "soso"
                          -- 4. "xhan"
sort  mylist desc alpha
                          -- 1. "xhan"
                          -- 2. "soso"
                          -- 3. "hello"
                          -- 4. "baidu"

组合模式:[by pattern]

除了可以按集合元素自身值排序外,还可以将集合元素内容按照给定 [by pattern]组合成新的key,并按照新key中对应的内容进行排序。

LRANGE [keyname] 0 -1   
                            -- 1. "11"
                            -- 2. "12"
                            -- 3. "13"
                            -- 4. "23"
set name11 nihao
set name12 wo
set name13 shi
set name23 lala

sort ml by name* alpha
                            -- 1. "13"
                            -- 2. "23"
                            -- 3. "11"
                            -- 4. "12"

*代表了ml中的元素值,所以这个排序是按照name12 name13 name23 name23这四个 key对应值排序的,当然返回的还是排序后ml集合中的元素

sort ml by name* get name* get # 为什么会按照shi lala nihao wo的顺序排下来, 这个跟单纯的排序name*name * alpha的结果都不一样。 这个问题要从redis的实现逻辑上来分析了

  1. list在插入后默认是按照时间的先后反序排列的13 23 11 12
  2. sort m1 by name*确定是会按照name*的值进行排序的。 但当name*对应的值不是数值型并且没有设置alpha的时候会导致排序分值都是相同的。 因为程序将把name*对应的值尝试转换为数值型。
  3. 这就会导致sort ml by name*会按照ml的自然顺序进行排列了
if (alpha) {
	if (sortby) vector[j].u.cmpobj = getDecodedObject(byval);
} else {
	if (byval->encoding == REDIS_ENCODING_RAW) {
		vector[j].u.score = strtod(byval->ptr, NULL);
	} 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 {
		redisAssert(1 != 1); 
	}   
}   

取得KEY:[GET pattern]

上面的例子都是返回的ml集合中的元素。我们也可以通过get选项去获取指定pattern 作为新key对应的值。

sort ml by name* get name*  alpha
                                            -- 1. "lala"
                                            -- 2. "nihao"
                                            -- 3. "shi"
                                            -- 4. "wo"

这次返回的就不在是ml中的元素了,而是name12 name13 name23 name23对应的值。 当然排序是按照name12 name13 name23 name23值并根据字母顺序排的。

另外get选项可以有多个。看例子(#特殊符号引用的是原始集合也就是ml

sort ml by name* get name* get # alpha
                                                -- 1. "lala"
                                                -- 2. "23"
                                                -- 3. "nihao"
                                                -- 4. "11"
                                                -- 5. "shi"
                                                -- 6. "13"
                                                -- 7. "wo"
                                                -- 8. "12"

最后在还有一个引用hash类型字段的特殊字符->,下面是例子

hset user1  name hanjie         -- (integer) 1
hset user11 name hanjie         -- (integer) 1
hset user12 name 86             -- (integer) 1
hset user13 name lxl            -- (integer) 1

sort ml get user*->name
                                -- 1. "hanjie"
                                -- 2. "86"
                                -- 3. "lxl"
                                -- 4. (nil)

很容易理解,注意当对应的user23不存在时候返回的是nil

限制范围:[LIMIT start count]

上面例子返回结果都是全部。limit选项可以限定返回结果的数量。例子

sort ml get name* limit 1 2
                                      -- 1. "wo"
                                      -- 2. "shi"

start下标是从0开始的,这里的limit选项意思是从第二个元素开始获取2

保存排序:[STORE dstkey]

如果对集合经常按照固定的模式去排序,那么把排序结果缓存起来会减少不少cpu开销。 使用store选项可以将排序内容保存到指定key中。保存的类型是list

sort   ml get name* limit 1 2 store cl     -- (integer) 2
type   cl                                  -- list

lrange cl 0 -1
                                           -- 1. "wo"
                                           -- 2. "shi"

这个例子我们将排序结果保存到了cl中

多机集群排序

对分散在不同服务器的数据进行排序

如果我们有多个redis server的话,不同的key可能存在于不同的server上。 比如name12 name13 name23 name23,很有可能分别在四个不同的server上存贮着。 这种情况会对排序性能造成很大的影响。

redis作者在他的blog上提到了这个问题的解 决办法,就是通过key tag将需要排序的 key都放到同一个server上 。 由于具体决定哪个key存在哪个服务器上一般都是在client端hash的办法来做的。

我们可以通过只对key的部分进行hash。 举个例子假如我们 的client如果发现key中包含[]。 那么只对key中[]包含的内容进行hash。

我们将四个name相关的key,都这样命名[name]12 [name]13 [name]23 [name]23, 于是client 程序就会把他们都放到同一server上。不知道jredis实现了没。

排序耗时卡住单线程

如果要sort的集合非常大的话排序就会消耗很长时间。 由于redis单线程的,所以长时间的排序操作会阻塞其他client的 请求。 解决办法是通过主从复制机制将数据复制到多个slave上。 然后我们只在slave上做排序操作。并进可能的对排序结果缓存。

另外就是一个方案是就 是采用sorted set对需要按某个顺序访问的集合建立索引。

实例:

sadd tom:friend:list  123      -- tom的好友列表 里面是好友的uid 
sadd tom:friend:list  456 
sadd tom:friend:list  789 
sadd tom:friend:list  101 

set uid:sort:123 1000          -- uid对应的成绩 
set uid:sort:456 6000 
set uid:sort:789 100 
set uid:sort:101 5999 

set uid:123 "{'uid':123,'name':'lucy'}"       -- 增加uid对应好友信息 
set uid:456 "{'uid':456,'name':'jack'}" 
set uid:789 "{'uid':789,'name':'marry'}" 
set uid:101 "{'uid':101,'name':'icej'}"  

从好友列表中获得iduid:sort字段匹配后排序。并根据排序后的顺序, 用key在uid表获得信息

sort tom:friend:list by uid:sort:* get uid:* 
                                              -- 1. {'uid':789,'name':'marry'} 
                                              -- 2. {'uid':123,'name':'lucy'} 
                                              -- 3. {'uid':101,'name':'icej'} 
                                              -- 4. {'uid':456,'name':'jack'} 

sort tom:friend:list by uid:sort:* get uid:* get uid:sort:* 
                                              -- 1. {'uid':789,'name':'marry'} 
                                              -- 2. 100 
                                              -- 3. {'uid':123,'name':'lucy'} 
                                              -- 4. 1000 
                                              -- 5. {'uid':101,'name':'icej'} 
                                              -- 6. 5999 
                                              -- 7. {'uid':456,'name':'jack'} 
                                              -- 8. 6000

redis sort 对应的javaapi

最近需要用到redis的排序方法,在网上看到的基本上都是redis 客户端的命令, 没有对应成相应的Java api,今天写了两个单元测试, 关于redis的list结合hash的排序和set结合String的排序,希望家多多提意见, 有其他更好的redis 排序请指导。谢谢。

A:set结合String的排序

jedis.sadd("tom:friend:list", "123");  // tom的好友列表
jedis.sadd("tom:friend:list", "456");
jedis.sadd("tom:friend:list", "789");
jedis.sadd("tom:friend:list", "101");

jedis.set("uid:sort:123", "1000");     // 好友对应的成绩
jedis.set("uid:sort:456", "6000");
jedis.set("uid:sort:789", "100");
jedis.set("uid:sort:101", "5999");

jedis.set("uid:123", "{'uid':123,'name':'lucy'}");     // 好友的详细信息
jedis.set("uid:456", "{'uid':456,'name':'jack'}");
jedis.set("uid:789", "{'uid':789,'name':'marry'}");
jedis.set("uid:101", "{'uid':101,'name':'icej'}");

SortingParams sortingParameters = new SortingParams();
// sortingParameters.desc();
// sortingParameters.limit(0, 2);
sortingParameters.get("uid:*");
// sortingParameters.get("uid:sort:*");
sortingParameters.by("uid:sort:*");
// 对应的redis 命令是./redis-cli sort tom:friend:list by uid:sort:* get uid:*       
List<String> result=jedis.sort("tom:friend:list", sortingParameters);
for(String item:result){
	System.out.println("item..."+item);
}

打印输出为:

item...{'uid':789,'name':'marry'}
item...{'uid':123,'name':'lucy'}
item...{'uid':101,'name':'icej'}
item...{'uid':456,'name':'jack'}

2:

jedis.sadd("tom:friend:list", "123");  // tom的好友列表
jedis.sadd("tom:friend:list", "456");
jedis.sadd("tom:friend:list", "789");
jedis.sadd("tom:friend:list", "101");

jedis.set("uid:sort:123", "1000");     // 好友对应的成绩
jedis.set("uid:sort:456", "6000");
jedis.set("uid:sort:789", "100");
jedis.set("uid:sort:101", "5999");

jedis.set("uid:123", "{'uid':123,'name':'lucy'}");     // 好友的详细信息
jedis.set("uid:456", "{'uid':456,'name':'jack'}");
jedis.set("uid:789", "{'uid':789,'name':'marry'}");
jedis.set("uid:101", "{'uid':101,'name':'icej'}");

SortingParams sortingParameters = new SortingParams();
// sortingParameters.desc();
// sortingParameters.limit(0, 2);
sortingParameters.get("uid:*");
sortingParameters.get("uid:sort:*");
sortingParameters.by("uid:sort:*");

对应的redis 命令是 ./redis-cli sort tom:friend:list by uid:sort:* get uid:* get uid:sort:*

List<String> result=jedis.sort("tom:friend:list", sortingParameters);
for(String item:result){
	System.out.println("item..."+item);
}

打印输出为:

item...{'uid':789,'name':'marry'}
item...100
item...{'uid':123,'name':'lucy'}
item...1000
item...{'uid':101,'name':'icej'}
item...5999
item...{'uid':456,'name':'jack'}
item...6000

B:list结合hash的排序

jedis.hset("userrr1", "name", "hanjie");
jedis.hset("userrr11", "name", "hanjie");
jedis.hset("userrr12", "name", "86");
jedis.hset("userrr13", "name", "x86");
jedis.lpush("ml", "12");
jedis.lpush("ml", "11");
jedis.lpush("ml", "23");
jedis.lpush("ml", "13");

SortingParams sortingParameters = new SortingParams();
sortingParameters.get("userrr*->name");
List<String> result=jedis.sort("ml", sortingParameters);
for(String item:result){
	System.out.println("item...."+item);
}

对应的redis客户端命令是:sort ml get user*->name