4.3 排序
4.3.1 有序集合的集合操作
集合类型提供了强大的集合操作命令,但是如果需要排序就要用到有序集合类型(但这只能对有序集合类型一种类型排序)。Redis的作者在设计Redis的命令时考虑到了不同数据类型的使用场景,对于不常用到的或者在不损失过多性能的前提下可以使用现有命令来实现的功能,Redis就不会单独提供命令来实现。这一原则使得Redis在拥有强大功能的同时保持着相对精简的命令。
有序集合常见的使用场景是
大数据排序,如游戏的玩家排行榜,所以
很少会需要获得键中的全部数据。同样Redis认为开发者在做完交集、并集运算后不需要直接获得全部结果,而是会希望将结果存入新的键中以便后续处理。这解释了为什么有序集合只有ZINTERSTORE和ZUNIONSTORE命令而没有ZINTER和ZUNION命令。
4.3.2 SORT命令
除了使用有序集合外,我们还可以借助Redis提供的
SORT命令来解决排序的问题。SORT命令可以对
列表类型、集合类型和有序集合类型键进行排序,并且可以完成与关系数据库中的连接查询相类似的任务。
比如博客中标有“ruby”标签的文章的ID分别是:“2”,“6”,“12”,“26”。由于在集合类型中所有元素是无序的,所以使用SMEMBERS命令并不能获得有序的结果(集合类型经常被用于存储对象的ID,很多情况下都是整数。所以Redis对这种情况进行了特殊的优化,元素的排列是有序的) 。为了能够让博客的标签页面下的文章也能按照发布的时间顺序排列(如果不考虑发布后再修改文章发布时间,就是按照文章ID的顺序排列),可以借助SORT命令实现,方法如下所示:
redis>SORT tag:ruby:posts
1) "2"
2) "6"
3) "12"
4) "26"
是不是十分简单?除了
集合类型,SORT命令还可以对
列表类型和
有序集合类型进行排序:
redis>LPUSH mylist 4 2 6 1 3 7
(integer)6
redis>SORT mylist
1) "1"
2) "2"
3) "3"
4) "4"
5) "6"
6) "7"
在对有序集合类型排序时会忽略元素的分数,只针对元素自身的值进行排序。例如:
redis>ZADD myzset 50 2 40 3 20 1 60 5
(integer) 4
redis>SORT myzset
1) "1"
2) "2"
3) "3"
4) "5"
除了可以排列数字外,SORT命令还可以
通过ALPHA参数实现按照
字典顺序排列非数字元素,就像这样:
redis>LPUSH mylistalpha a c e d B C A
(integer) 7
redis>SORT mylistalpha
(error) ERR One or more scores can't be converted into double
redis>SORT mylistalpha ALPHA
1) "A"
2) "B"
3) "C"
4) "a"
5) "c"
6) "d"
7) "e"
从这段示例中可以看到如果
没有加ALPHA参数的话,SORT命令会尝试将所有元素转换成双精度浮点数来比较,如果无法转换则会
提示错误。
SORT命令
默认是按照
从小到大的顺序排列,而一般博客中显示文章的顺序都是按照时间倒序的,即最新的文章显示在最前面。SORT命令的
DESC参数可以实现将元素按照从大到小的顺序排列:
redis>SORT tag:ruby:posts DESC
1) "26"
2) "12"
3) "6"
4) "2"
那么如果文章数量过多需要分页显示呢?SORT命令还支持
LIMIT参数来返回指定范围的结果。用法和SQL语句一样,LIMIT offset count,表示跳过前offset个元素并获取之后的count个元素。
SORT命令的参数可以组合使用,像这样:
redis>SORT tag:ruby:posts DESC LIMIT 1 2
1) "12"
2) "6"
4.3.3 BY参数
很多情况下列表(或集合、有序集合)中存储的元素值代表的是对象的ID(如标签集合中存储的是文章对象的ID),单纯对这些ID自身排序有时意义并不大。更多的时候我们希望
根据ID对应的对象的
某个属性进行排序。比如我们通过使用
有序集合键来存储文章ID列表,使得博客能够支持修改文章时间,所以文章ID的顺序和文章的发布时间的顺序并不完全一致。博客是使用
散列类型键存储文章对象的,其中
time字段存储的就是文章的发布时间。现在我们知道ID为“2”,“6”,“12”和“26”的四篇文章的time字段的值分别为“1352619200”,“1352619600”,“1352620100”和“1352620000”(Unix时间)。如果要按照文章的发布时间递减排列结果应为“12”,“26”,“6”,“2”。为了获得这样的结果,需要使用SORT命令的另一个强大的参数——
BY。
BY 参数的语法为“BY 参考键”。其中参考键可以是
字符串类型键或者是
散列类型键的某个字段(表示为键名->字段名)。如果提供了BY参数,SORT命令将不再依据元素自身的值进行排序,而是对每个元素使用元素的值替换参考键中的第一个“*”并获取其值,然后依据该值对元素排序。就像这样:
redis>SORT tag:ruby:posts BY post:*->time DESC
1) "12"
2) "26"
3) "6"
4) "2"
在上例中SORT命令会读取post:2、post:6、post:12、post:26几个散列键中的time字段的值并以此决定tag:ruby:posts键中各个文章ID的顺序。
相当于以id为关联字段,也就是post:*里的*的值与tag:ruby:posts里的值一一对应。
除了散列类型之外,参考键还可以是字符串类型,比如:
redis>LPUSH sortbylist 2 1 3
(integer) 3
redis>SET itemscore:1 50
OK
redis>SET itemscore:2 100
OK
redis>SET itemscore:3 -10
OK
redis>SORT sortbylist BY itemscore:* DESC
1) "2"
2) "1"
3) "3"
当参考键名
不包含“*”时(即
常量键名,与元素值无关),SORT命令将
不会执行排序操作,因为Redis认为这种情况是没有意义的(因为所有要比较的值都一样)。例如:
redis>SORT sortbylist BY anytext
1) "3"
2) "1"
3) "2"
例子中anytext是常量键名(甚至anytext键可以不存在),此时SORT的结果与LRANGE的结果相同,没有执行排序操作。在不需要排序但需要借助SORT命令获得与元素相关联的数据时,常量键名是很有用的(下面一节会介绍)。
如果几个元素的参考键值相同,则SORT命令会再比较元素本身的值来决定元素的顺序。像这样:
redis>LPUSH sortbylist 4
(integer) 4
redis>SET itemscore:4 50
OK
redis>SORT sortbylist BY itemscore:* DESC
1) "2"
2) "4"
3) "1"
4) "3"
示例中元素“4”的参考键itemscore:4的值和元素“1”的参考键itemscore:1的值都是50,所以SORT命令会再比较“4”和“1”元素本身的大小来决定两者的顺序。
当某个元素的参考键不存在时,会默认参考键的值为0:
redis>LPUSH sortbylist 5
(integer) 5
redis>SORT sortbylist BY itemscore:* DESC
1) "2"
2) "4"
3) "1"
4) "5"
5) "3"
上例中“5”排在了“3”的前面,是因为“5”的参考键不存在,所以默认为0,而“3”的参考键值为-10。
补充知识
参考键虽然支持散列类型,但是“*”只能在“->”符号前面(即键名部分)才有用,在“->”后(即字段名部分)会被当成
字段名本身而不会作为占位符被元素的值替換,即常量键名。但是实际运行时会发现一个有趣的结果:
redis>SORT sortbylist BY somekey->somefield:*
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
上面提到了当参考键名是常量键名时SORT命令将不会执行排序操作,然而上例中确进行了排序,而且只是对元素本身进行排序。这是因为Redis判断参考键名
是不是常量键名的方式是判断
参考键名中是否包含“*”,而somekey->somefield:*中包含“*”所以不是常量键名
(把->前后当做一个整体,但是只有->前面会替换掉)。所以在排序的时候Redis对每个元素都会读取键somekey中的somefield:*字段(“*”不会被替換),无论能否获得其值,每个元素的参考键值是相同的,所以Redis会按照元素本身的大小排列。
4.3.4 GET参数
现在博客已经可以按照文章的发布顺序获得一个标签下的文章ID列表了,接下来要做的事就是对每个ID都使用HGET命令获取文章的标题以显示在博客列表页中。有没有觉得很麻烦?不论你的答案如何,都有一种更简单的方式来完成这个操作,那就是借助SORT命令的GET参数。
GET参数不影响排序,它的作用是使SORT命令的返回结果不再是元素自身的值,而是GET参数中指定的键值。GET参数的规则和BY参数一样,
GET参数也支持字符串类型和散列类型的键,并使用“*”作为占位符。要实现在排序后直接返回ID对应的文章标题,可以这样写:
redis>SORT tag:ruby:posts BY post:*->time DESC GET post:*->title
1) "Windows 8 app designs"
2) "RethinkDB - An open-source distributed database built with love"
3) "Uses for cURL"
4) "The Nature of Ruby"
在一个SORT命令中可以有多个GET参数(而BY参数只能有一个),所以还可以这样用:
redis>SORT tag:ruby:posts BY post:*->time DESC GET post:*->title GET post:*-
>time
1) "Windows 8 app designs"
2) "1352620100"
3) "RethinkDB - An open-source distributed database built with love"
4) "1352620000"
5) "Uses for cURL"
6) "1352619600"
7) "The Nature of Ruby"
8) "1352619200"
可见有N个GET参数,每个元素返回的结果就有N行。这时有个问题:如果还需要返回文章ID该怎么办?答案是使用
GET #。就像下面这样:
redis>SORT tag:ruby:posts BY post:*->time DESC GET post:*->title GET post:*-
>time GET #
1) "Windows 8 app designs"
2) "1352620100"
3) "12"
4) "RethinkDB - An open-source distributed database built with love"
5) "1352620000"
6) "26"
7) "Uses for cURL"
8) "1352619600"
9) "6"
10) "The Nature of Ruby"
11) "1352619200"
12) "2"
也就是说,GET #会返回元素本身的值。
4.3.5 STORE参数
默认情况下SORT会直接返回排序结果,如果希望保存排序结果,可以使用
STORE参数。如希望把结果保存到sort.result键中:
redis>SORT tag:ruby:posts BY post:*->time DESC GET post:*->title GET post:*-
>time
GET # STORE sort.result
(integer) 12
redis>LRANGE sort.result 0 -1
1) "Windows 8 app designs"
2) "1352620100"
3) "12"
4) "RethinkDB - An open-source distributed database built with love"
5) "1352620000"
6) "26"
7) "Uses for cURL"
8) "1352619600"
9) "6"
10) "The Nature of Ruby"
11) "1352619200"
12) "2"
保存后的键的类型为
列表类型,如果键已经存在则会覆盖它。加上STORE参数后SORT命令的返回值为结果的个数。
STORE参数常用来结合
EXPIRE命令缓存排序结果,如下面的伪代码:
#判断是否存在之前排序结果的缓存
isCacheExists = EXISTS cache.sort
if isCacheExists is 1
#如果存在则直接返回
return LRANGE cache.sort, 0, -1
else
#如果不存在,则使用SORT命令排序并将结果存入cache.sort键中作为缓存
sortResult=SORT some.list STORE cache.sort
#设置缓存的生存时间为10分钟
EXPIRE cache.sort, 600
#返回排序结果
return sortResult
4.3.6 性能优化
SORT是Redis中最强大最复杂的命令之一,如果使用不好很容易成为性能瓶颈。SORT命令的时间复杂度是0(n+mlogm),其中n表示要排序的列表(集合或有序集合)中的元素个数,m表示要返回的元素个数。当n较大的时候SORT命令的性能相对较低,并且Redis在排序前会建立一个长度为n① 的容器来存储待排序的元素,虽然是一个临时的过程,但如果同时进行较多的大数据量排序操作则会严重影响性能。
注释:
①有一个例外是当键类型为
有序集合且参考键为
常量键名时容器大小为m而不是n。
所以开发中使用SORT命令时需要注意以下几点。
(1)尽可能减少待排序键中元素的数量(使n尽可能小)。
(2)使用LIMIT参数只获取需要的数据(使m尽可能小)。
(3)如果要排序的数据数量较大,尽可能使用STORE参数将结果缓存。
分享到:
相关推荐
<groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.6.2</version> </dependency> ``` - **Java 代码示例**:以下是一个简单的 Redis 操作示例。 ```java @Test public void test...
kill <PID> ``` ##### 4.5 设置密码 - 打开配置文件 `redis.conf`,找到 `requirepass` 行,取消注释并设置密码。 ```shell requirepass yourpassword ``` ##### 4.6 远程连接 - 一旦 Redis 服务配置允许远程...
Map<String, String> hash = new HashMap<>(); hash.put("field1", "value1"); hash.put("field2", "value2"); jedis.hMSet("myHash", hash); Map<String, String> result = jedis.hGetAll("myHash"); ``` #### 4.3...
- `redis-cli -h <host> -p <port>`: 连接到远程 Redis 服务器。 - `exit`: 退出 Redis 客户端。 - **键值操作** - `SET key value`: 设置键的值。 - `GET key`: 获取键的值。 - **字符串操作** - `INCR key`: ...
Redis是一款开源的高性能键值对存储数据库,由意大利软件开发者Salvatore Sanfilippo创造,后被VMware公司收购。它支持存储的值包括字符串(strings)、哈希(hashes)、列表(lists)、集合(sets)、有序集合...
- **4.3 Redis虚拟内存工作方式简介** - **4.3.1 当vm-max-threads设为0时(阻塞方式)** - **定义**: Redis会以单线程方式处理虚拟内存相关的读写操作,可能会导致性能下降。 - **4.3.2 当vm-max-threads大于0时...
RedisCommands<String, String> syncCommands = connection.sync(); syncCommands.set("key", "value"); String value = syncCommands.get("key"); System.out.println(value); ``` - **4.3 Redis连接池的配置...
### Redis必备书籍知识点详解 #### 一、内部数据结构 **1.1 简单动态字符串** **1.1.1 SDS (Simple Dynamic String) 的用途** SDS 在 Redis 中扮演着至关重要的角色,其主要用途有两个方面: 1. **实现字符串...
- **高性能**: Redis能够支持每秒超过10万次的读写操作。 - **丰富的数据类型**: 支持二进制安全的字符串(Strings),列表(Lists),哈希(Hashes),集合(Sets)以及有序集合(Ordered Sets)等多种数据结构。 - **原子性*...
### Tedu五阶段Redis分布式锁 #### 一、概述与背景 随着互联网技术的发展,越来越多的应用需要处理高并发场景下的业务需求。在这种情况下,如何确保数据的一致性和完整性成为了一个非常重要的问题。分布式锁作为...
对于耗时的计算任务,如排序或求交集,Redis提供了一些优化策略,如`KEYS`命令的慎用、`SCAN`命令的使用,以及Lua脚本的执行,以减少单线程的阻塞时间。 四、面试题解析 4.1 Redis为何选择单线程模型? 答:Redis...
### REDIS面试题详解 #### 一、Redis简介与特点 **1.1 什么是Redis?** Redis(Remote Dictionary Server)是一种开源的、基于内存的键值存储系统,它不仅可以用作数据库,还可以作为缓存和消息代理。Redis支持...
常见排序算法 3. 应用相关 3.1 爬虫应用 3.1.1 爬取百度企业信用信息 3.1.2 爬取空气质量信息 3.1.3 爬取免费快代理 4. 常见数据库ORM驱动 4.1 mysql-Gorm 4.2 mongodb-mgo 4.3 mongodb-官方驱动 4.4 redis 4.5 ...
- 选择合适的数据存储解决方案,如Redis、Kafka等。 - **数据计算** - 设计实时计算逻辑,如聚合统计、实时分析等。 - **展现** - 设计数据展示界面,实现动态更新。 **3.5 Storm日志监控告警系统** - **需求...
- 使用`mysql -h <hostname> -u root -p`登录。 #### 1.3 MySQL基础安全 ##### 1.3.1 启动程序设置700,属主和用户组为mysql 为了提高安全性,MySQL启动脚本的权限应设置为700,属主和用户组均为mysql用户。 ##...
4.3 文档存储内部机制 73 4.3.1 用内存映射文件存储数据 74 4.3.2 MongoDB集合和索引使用指南 75 4.3.3 MongoDB的可靠性和耐久性 75 4.3.4 水平扩展 76 4.4 键/值存储Memcached和Redis 78 4.4.1 Memcached的...