- 1、问题:
- 2、Redis 介绍
- 2.1 redis 是什么?
- 2.2 那Redis不是什么?
- 2.3 Redis 性能:
- 2.4 Redis 的功能:
- 3、数据结构
- 3.1 Key
- 3.2 String
- 3.3 Hash
- 3.4 List
- 3.5 Set
- 3.6 Sorted Set
- 3.7 事务
- 3.8 过期数据清除
- 4、redis排序简介
- 4.1 以下是对 list 按 key 排序的示例:
- 4.2 一般 SORT 用法
- 4.3 使用 ALPHA 修饰符对字符串进行排序
- 4.4 使用 LIMIT 修饰符限制返回结果
- 4.5 使用外部 key 进行排序
- 4.6 BY 选项
- 4.7 GET 选项
- 4.8 组合使用 BY 和 GET
- 4.9 获取多个外部键
- 4.10 获取外部键,但不进行排序
- 4.11 将哈希表作为 GET 或 BY 的参数
- 4.12 保存排序结果
- 4.13 zset 中的排序
- 5、Refer:
1、问题:
有两列数据:UserId(用户ID) 和 Score(积分),现在想实时获取积分排名对应的 UserId,
简单说就是根据积分的 TopK,来获取对应的 userId 了,如果是你,你会怎么做?
这个问题从数据结构上来说,比较简单,如果你熟悉主流的 key-value 数据库,那么 redis 应该是不二之选,
它里面内置了一种数据结构:zset,它是 set 的升级版,可以根据给定的 score 作为权重来对 key 排序。
本篇就来简单介绍下 redis 的功能、常用的数据结构以及它的重要特性之一:排序
2、Redis 介绍
2.1 redis 是什么?
Redis 本质上一个 Key/Value 数据库,与 Memcached 类似的 NoSQL 型数据库,但是他的
数据可以持久化的保存在磁盘上,解决了服务重启后数据不丢失的问题,他的值可以是 string
(字符串)、list(列表)、sets(集合)或者是 ordered sets(被排序的集合),所有的数据
类型都具有 push/pop、add/remove、执行服务端的并集、交集、两个 sets 集中的差别等等
操作,这些操作都是具有原子性的,Redis 还支持各种不同的排序能力。
Redis 2.0 更是增加了很多新特性,如:提升了性能、增加了新的数据类型、更少的利用
内存(AOF 和 VM)。
Redis 支持绝大部分主流的开发语言,如:C、Java、C#、PHP、Perl、Python、Lua、Erlang、
Ruby 等等。
redis 非常非常的快,有测评说比Memcached还快(当大家都是单CPU的时候),而且是无短板的快,读写都一般的快,所有API都差不多快,也没有MySQL Cluster、MongoDB那样更新同一条记录如Counter时慢下去的毛病。
丰富的数据结构,超越了一般的Key-Value数据库而被认为是一个数据结构服务器。组合各种结构,限制Redis用途的是你自己的想象力,作者自己捉刀写的用途入门。
因为是个人作品,Redis目前只有2.3万行代码,Keep it simple的死硬做法,使得普通公司而不需淘宝那个级别的文艺公司也可以吃透它。Redis宣言就是作者的自白,我最喜欢其中的“代码像首诗”,”设计是一场与复杂性的战斗“,“Coding是一件艰苦的事情,唯一的办法是享受它。如果它已不能带来快乐就停止它。为了防止这一天的出现,我们要尽量避免把Redis往乏味的路上带。”
让人又爱又恨的单线程架构,使得代码不用处理平时最让人头痛的并发而大幅简化,也不用老是担心作者的并发有没有写对,但也带来CPU的瓶颈,而且单线程被慢操作所阻塞时,其他请求的延时变得不确定。
2.2 那Redis不是什么?
- Redis 不是Big Data,数据都在内存中,无法以T为单位。
- 在Redis-Cluster发布并被稳定使用之前,Redis没有真正的平滑水平扩展能力。
- Redis 不支持Ad-Hoc Query,提供的只是数据结构的API,没有SQL一样的查询能力。
2.3 Redis 性能:
根据 Redis 官方的测试结果:在 50 个并发的情况下请求 10w 次,写的速度是 110000
- 测试环境: RHEL 6.3 / HP Gen8 Server/ 2 * Intel Xeon 2.00GHz(6 core) / 64G DDR3 memory / 300G RAID-1 SATA / 1 master(writ AOF), 1 slave(write AOF & RDB)
- 数据准备: 预加载两千万条数据,占用10G内存。
- 测试工具:自带的redis-benchmark,默认只是基于一个很小的数据集进行测试,调整命令行参数如下,就可以开100条线程(默认50),SET 1千万次(key在0-1千万间随机),key长21字节,value长256字节的数据。
redis-benchmark -t SET -c 100 -n 10000000 -r 10000000 -d 256
- 测试结果(TPS): 1.SET:4.5万, 2.GET:6万 ,3.INCR:6万,4.真实混合场景: 2.5万SET & 3万GET
- 单条客户端线程时6千TPS,50与100条客户端线程差别不大,200条时会略多。
- Get/Set操作,经过了LAN,延时也只有1毫秒左右,可以反复放心调用,不用像调用REST接口和访问数据库那样,每多一次外部访问都心痛。
- 资源监控:
1.CPU: 占了一个处理器的100%,总CPU是4%(因为总共有2CPU*6核*超线程 = 24个处理器),可见单线程下单处理器的能力是瓶颈。 AOF rewrite时另一个处理器占用50-70%。
2.网卡:15-20 MB/s receive, 3Mb/s send(no slave) or 15-20 MB/s send (with slave) 。当把value长度加到4K时,receive 99MB/s,已经到达千兆网卡的瓶颈,TPS降到2万。
3.硬盘:15MB/s(AOF append), 100MB/s(AOF rewrite/AOF load,普通硬盘的瓶颈),
2.4 Redis 的功能:
- 所有数据都在内存中。
- 五种数据结构:String / Hash / List / Set / Ordered Set。
- 数据过期时间支持。
- 不完全的事务支持。
- 服务端脚本:使用Lua Script编写,类似存储过程的作用。
- PubSub:捞过界的消息一对多发布订阅功能,起码Redis-Sentinel使用了它。
- 持久化:支持定期导出内存的Snapshot 与 记录写操作日志的Append Only File两种模式。
- Replication:Master-Slave模式,Master可连接多个只读Slave,暂无专门的Geographic Replication支持。
- Fail-Over:Redis-Sentinel节点负责监控Master节点,在master失效时提升slave,独立的仲裁节点模式有效防止脑裂。
- Sharding:开发中的Redis-Cluser。
- 动态配置:所有参数可用命令行动态配置不需重启,并重新写回配置文件中,对云上的大规模部署非常合适。
- Virtual Memory:vm 是 Redis2.0 新增的一个非常稳定和可靠的功能,vm 的引入是为了提高 Redis 的性能,也就是把很少使用的 value 保存到 disk,而 key 保存在内存中。实际上就是如果你有 10w 的 keys 在内存中,而只有仅仅 10%左右的 key 经常使用,那么 Redis 可以通过开启 VM 尝试将不经常使用的 Value 转换到 disk 上保存。
3、数据结构
3.1 Key
- Key 不能太长,比如1024字节,但antirez也不喜欢太短如"u:1000:pwd",要表达清楚意思才好。他私人建议用":"分隔域,用"."作为单词间的连接,如"comment:1234:reply.to"。
- Keys,返回匹配的key,支持通配符如 "keys a*" 、 "keys a?c",但不建议在生产环境大数据量下使用。
- Sort,对集合按数字或字母顺序排序后返回或另存为list,还可以关联到外部key等。因为复杂度是最高的O(N+M*log(M))(N是集合大小,M 为返回元素的数量),有时会安排到slave上执行。
- Expire/ExpireAt/Persist/TTL,关于Key超时的操作。默认以秒为单位,也有p字头的以毫秒为单位的版本, Redis的内部实现见2.9 过期数据清除。
3.2 String
最普通的key-value类型,说是String,其实是任意的byte[],比如图片,最大512M。 所有常用命令的复杂度都是O(1),普通的Get/Set方法,可以用来做Cache,存Session,为了简化架构甚至可以替换掉Memcached。
Incr/IncrBy/IncrByFloat/Decr/DecrBy,可以用来做计数器,做自增序列。key不存在时会创建并贴心的设原值为0。IncrByFloat专门针对float,没有对应的decrByFloat版本?用负数啊。
SetNx, 仅当key不存在时才Set。可以用来选举Master或做分布式锁:所有Client不断尝试使用SetNx master myName抢注Master,成功的那位不断使用Expire刷新它的过期时间。如果Master倒掉了key就会失效,剩下的节点又会发生新一轮抢夺。
其他Set指令:
- SetEx, Set + Expire 的简便写法,p字头版本以毫秒为单位。
- GetSet, 设置新值,返回旧值。比如一个按小时计算的计数器,可以用GetSet获取计数并重置为0。这种指令在服务端做起来是举手之劳,客户端便方便很多。
- MGet/MSet/MSetNx, 一次get/set多个key。
- 2.6.12版开始,Set命令已融合了Set/SetNx/SetEx三者,SetNx与SetEx可能会被废弃,这对Master抢注非常有用,不用担心setNx成功后,来不及执行Expire就倒掉了。可惜有些懒惰的Client并没有快速支持这个新指令。
GetBit/SetBit/BitOp,与或非/BitCount, BitMap的玩法,比如统计今天的独立访问用户数时,每个注册用户都有一个offset,他今天进来的话就把他那个位设为1,用BitCount就可以得出今天的总人数。
Append/SetRange/GetRange/StrLen,对文本进行扩展、替换、截取和求长度,只对特定数据格式如字段定长的有用,json就没什么用。
3.3 Hash
Key-HashMap结构,相比String类型将这整个对象持久化成JSON格式,Hash将对象的各个属性存入Map里,可以只读取/更新对象的某些属性。这样有些属性超长就让它一边呆着不动,另外不同的模块可以只更新自己关心的属性而不会互相并发覆盖冲突。
另一个用法是土法建索引。比如User对象,除了id有时还要按name来查询。可以有如下的数据记录:
- (String) user:101 -> {"id":101,"name":"calvin"...}
- (String) user:102 -> {"id":102,"name":"kevin"...}
- (Hash) user:index-> "calvin"->101, "kevin" -> 102
底层实现是hash table,一般操作复杂度是O(1),要同时操作多个field时就是O(N),N是field的数量。
3.4 List
List是一个双向链表,支持双向的Pop/Push,江湖规矩一般从左端Push,右端Pop——LPush/RPop,而且还有Blocking的版本BLPop/BRPop,客户端可以阻塞在那直到有消息到来,所有操作都是O(1)的好孩子,可以当Message Queue来用。当多个Client并发阻塞等待,有消息入列时谁先被阻塞谁先被服务。任务队列系统Resque是其典型应用。
还有RPopLPush/ BRPopLPush,弹出来返回给client的同时,把自己又推入另一个list,LLen获取列表的长度。
还有按值进行的操作:LRem(按值删除元素)、LInsert(插在某个值的元素的前后),复杂度是O(N),N是List长度,因为List的值不唯一,所以要遍历全部元素,而Set只要O(log(N))。
在消息队列中,并没有JMS的ack机制,如果消费者把job给Pop走了又没处理完就死机了怎么办?
- 解决方法之一是加多一个sorted set,分发的时候同时发到list与sorted set,以分发时间为score,用户把job做完了之后要用ZREM消掉sorted set里的job,并且定时从sorted set中取出超时没有完成的任务,重新放回list。
- 另一个做法是为每个worker多加一个的list,弹出任务时改用RPopLPush,将job同时放到worker自己的list中,完成时用LREM消掉。如果集群管理(如zookeeper)发现worker已经挂掉,就将worker的list内容重新放回主list。
3.5 Set
Set就是Set,可以将重复的元素随便放入而Set会自动去重,底层实现也是hash table,值相当于True。
- SAdd/SRem/SIsMember/SCard/SMove/SMembers,各种标准操作。除了SMembers都是O(1)。
- SInter/SInterStore/SUnion/SUnionStore/SDiff/SDiffStore,各种集合操作。交集运算可以用来显示在线好友(在线用户 交集 好友列表),共同关注(两个用户的关注列表的交集)。O(N),并集和差集的N是集合大小之和,交集的N是小的那个集合的大小*2。
3.6 Sorted Set
有序集,元素放入集合时还要提供该元素的分数。
- ZRange/ZRevRange,按排名的上下限返回元素,正数与倒数。
- ZRangeByScore/ZRevRangeByScore,按分数的上下限返回元素,正数与倒数。
- ZRemRangeByRank/ZRemRangeByScore,按排名/按分数的上下限删除元素。
- ZCount,统计分数上下限之间的元素个数。
- ZRank/ZRevRank ,显示某个元素的正倒序的排名。
- ZScore/ZIncrby,显示元素的分数/增加元素的分数。
- ZAdd(Add)/ZRem(Remove)/ZCard(Count),ZInsertStore(交集)/ZUnionStore(并集),Set操作,与正牌Set相比,少了IsMember和差集运算。
Sorted Set的实现是hash table(element->score, 用于实现ZScore及判断element是否在集合内),和skip list(score->element,按score排序)的混合体。 skip list有点像平衡二叉树那样,不同范围的score被分成一层一层,每层是一个按score排序的链表。
ZAdd/ZRem是O(log(N)),ZRangeByScore/ZRemRangeByScore是O(log(N)+M),N是Set大小,M是结果/操作元素的个数。可见,原本可能很大的N被很关键的Log了一下,1000万大小的Set,复杂度也只是几十不到。当然,如果一次命中很多元素M很大那谁也没办法了。
3.7 事务
用Multi(Start Transaction)、Exec(Commit)、Discard(Rollback)实现。 在事务提交前,不会执行任何指令,只会把它们存到一个队列里,不影响其他客户端的操作。在事务提交时,批量执行所有指令。《Redis设计与实现》中的详述。
注意,Redis里的事务,与我们平时的事务概念很不一样:
- 它仅仅是保证事务里的操作会被连续独占的执行。因为是单线程架构,在执行完事务内所有指令前是不可能再去同时执行其他客户端的请求的。
- 它没有隔离级别的概念,因为事务提交前任何指令都不会被实际执行,也就不存在"事务内的查询要看到事务里的更新,在事务外查询不能看到"这个让人万分头痛的问题。
- 它不保证原子性——所有指令同时成功或同时失败,只有决定是否开始执行全部指令的能力,没有执行到一半进行回滚的能力。在redis里失败分两种,一种是明显的指令错误,比如指令名拼错,指令参数个数不对,在2.6版中全部指令都不会执行。另一种是隐含的,比如在事务里,第一句是SET foo bar, 第二句是LLEN foo,对第一句产生的String类型的key执行LLEN会失败,但这种错误只有在指令运行后才能发现,这时候第一句成功,第二句失败。还有,如果事务执行到一半redis被KILL,已经执行的指令同样也不会被回滚。
Watch指令,类似乐观锁,事务提交时,如果Key的值已被别的客户端改变,比如某个list已被别的客户端push/pop过了,整个事务队列都不会被执行。
3.8 过期数据清除
官方文档 与 《Redis设计与实现》中的详述,过期数据的清除从来不容易,为每一条key设置一个timer,到点立刻删除的消耗太大,每秒遍历所有数据消耗也大,Redis使用了一种相对务实的做法:
当client主动访问key会先对key进行超时判断,过时的key会立刻删除。
如果clien永远都不再get那条key呢? 它会在Master的后台,每秒10次的执行如下操作: 随机选取100个key校验是否过期,如果有25个以上的key过期了,立刻额外随机选取下100个key(不计算在10次之内)。可见,如果过期的key不多,它最多每秒回收200条左右,如果有超过25%的key过期了,它就会做得更多,但只要key不被主动get,它占用的内存什么时候最终被清理掉只有天知道。
4、redis排序简介
排序/TopK 是互联网业务场景中使用频率非常高的一个操作,比如根据积分/投票数/时间进行业务排序、根据PV、UV排名进行业务动态更新等。而 redis 作为一种 key-value 数据库,以其丰富的数据结构,出色的性能,非常适合这一业务场景。
4.1 以下是对 list 按 key 排序的示例:
SORT key [BY pattern] [LIMIT offset count] [GET pattern [GET pattern ...]] [ASC | DESC] [ALPHA] [STORE destination]
返回或保存给定列表、集合、有序集合 key 中经过排序的元素。
排序默认以数字作为对象,值被解释为双精度浮点数,然后进行比较。
4.2 一般 SORT 用法
最简单的 SORT 使用方法是 SORT key 和 SORT key DESC :
- SORT key 返回键值从小到大排序的结果。
- SORT key DESC 返回键值从大到小排序的结果。
假设 today_cost 列表保存了今日的开销金额, 那么可以用 SORT 命令对它进行排序:
# 开销金额列表 redis> LPUSH today_cost 30 1.5 10 8 (integer) 4 # 排序 redis> SORT today_cost 1) "1.5" 2) "8" 3) "10" 4) "30" # 逆序排序 redis 127.0.0.1:6379> SORT today_cost DESC 1) "30" 2) "10" 3) "8" 4) "1.5"
4.3 使用 ALPHA 修饰符对字符串进行排序
因为 SORT 命令默认排序对象为数字, 当需要对字符串进行排序时, 需要显式地在 SORT 命令之后添加 ALPHA 修饰符:
# 网址
redis> LPUSH website "www.reddit.com"
(integer) 1
redis> LPUSH website "www.slashdot.com"
(integer) 2
redis> LPUSH website "www.infoq.com"
(integer) 3
# 默认(按数字)排序,不是数值型会报错:(error) ERR One or more scores can't be converted into double
redis> SORT website
1) "www.infoq.com"
2) "www.slashdot.com"
3) "www.reddit.com"
# 按字符排序
redis> SORT website ALPHA
1) "www.infoq.com"
2) "www.reddit.com"
3) "www.slashdot.com"
如果系统正确地设置了 LC_COLLATE 环境变量的话,Redis能识别 UTF-8 编码。
4.4 使用 LIMIT 修饰符限制返回结果
排序之后返回元素的数量可以通过 LIMIT 修饰符进行限制, 修饰符接受 offset 和 count 两个参数:
- offset 指定要跳过的元素数量。
- count 指定跳过 offset 个指定的元素之后,要返回多少个对象。
以下例子返回排序结果的前 5 个对象( offset 为 0 表示没有元素被跳过)。
# 添加测试数据,列表值为 1 指 10
redis 127.0.0.1:6379> RPUSH rank 1 3 5 7 9
(integer) 5
redis 127.0.0.1:6379> RPUSH rank 2 4 6 8 10
(integer) 10
# 返回列表中最小的 5 个值
redis 127.0.0.1:6379> SORT rank LIMIT 0 5
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
可以组合使用多个修饰符。以下例子返回从大到小排序的前 5 个对象。
redis 127.0.0.1:6379> SORT rank LIMIT 0 5 DESC
1) "10"
2) "9"
3) "8"
4) "7"
5) "6"
4.5 使用外部 key 进行排序
可以使用外部 key 的数据作为权重,代替默认的直接对比键值的方式来进行排序。
假设现在有用户数据如下:
uid | user_name_{uid} | user_level_{uid} |
1 | admin | 9999 |
2 | jack | 10 |
3 | peter | 25 |
4 | mary | 70 |
以下代码将数据输入到 Redis 中:
# admin
redis 127.0.0.1:6379> LPUSH uid 1
(integer) 1
redis 127.0.0.1:6379> SET user_name_1 admin
OK
redis 127.0.0.1:6379> SET user_level_1 9999
OK
# jack
redis 127.0.0.1:6379> LPUSH uid 2
(integer) 2
redis 127.0.0.1:6379> SET user_name_2 jack
OK
redis 127.0.0.1:6379> SET user_level_2 10
OK
# peter
redis 127.0.0.1:6379> LPUSH uid 3
(integer) 3
redis 127.0.0.1:6379> SET user_name_3 peter
OK
redis 127.0.0.1:6379> SET user_level_3 25
OK
# mary
redis 127.0.0.1:6379> LPUSH uid 4
(integer) 4
redis 127.0.0.1:6379> SET user_name_4 mary
OK
redis 127.0.0.1:6379> SET user_level_4 70
OK
4.6 BY 选项
默认情况下, SORT uid 直接按 uid 中的值排序:
redis 127.0.0.1:6379> SORT uid
1) "1" # admin
2) "2" # jack
3) "3" # peter
4) "4" # mary
通过使用 BY 选项,可以让 uid 按其他键的元素来排序。
比如说, 以下代码让 uid 键按照 user_level_{uid} 的大小来排序:
redis 127.0.0.1:6379> SORT uid BY user_level_*
1) "2" # jack , level = 10
2) "3" # peter, level = 25
3) "4" # mary, level = 70
4) "1" # admin, level = 9999
user_level_* 是一个占位符, 它先取出 uid 中的值, 然后再用这个值来查找相应的键。
比如在对 uid 列表进行排序时, 程序就会先取出 uid 的值 1 、 2 、 3 、 4 , 然后使用 user_level_1 、 user_level_2 、 user_level_3 和user_level_4 的值作为排序 uid 的权重。
4.7 GET 选项
使用 GET 选项, 可以根据排序的结果来取出相应的键值。
比如说, 以下代码先排序 uid , 再取出键 user_name_{uid} 的值:
redis 127.0.0.1:6379> SORT uid GET user_name_*
1) "admin"
2) "jack"
3) "peter"
4) "mary"
4.8 组合使用 BY 和 GET
通过组合使用 BY 和 GET , 可以让排序结果以更直观的方式显示出来。
比如说, 以下代码先按 user_level_{uid} 来排序 uid 列表, 再取出相应的 user_name_{uid} 的值:
redis 127.0.0.1:6379> SORT uid BY user_level_* GET user_name_*
1) "jack" # level = 10
2) "peter" # level = 25
3) "mary" # level = 70
4) "admin" # level = 9999
现在的排序结果要比只使用 SORT uid BY user_level_* 要直观得多。
4.9 获取多个外部键
可以同时使用多个 GET 选项, 获取多个外部键的值。
以下代码就按 uid 分别获取 user_level_{uid} 和 user_name_{uid} :
redis 127.0.0.1:6379> SORT uid GET user_level_* GET user_name_*
1) "9999" # level
2) "admin" # name
3) "10"
4) "jack"
5) "25"
6) "peter"
7) "70"
8) "mary"
GET 有一个额外的参数规则,那就是 —— 可以用 # 获取被排序键的值。
以下代码就将 uid 的值、及其相应的 user_level_* 和 user_name_* 都返回为结果:
redis 127.0.0.1:6379> SORT uid GET # GET user_level_* GET user_name_*
1) "1" # uid
2) "9999" # level
3) "admin" # name
4) "2"
5) "10"
6) "jack"
7) "3"
8) "25"
9) "peter"
10) "4"
11) "70"
12) "mary"
4.10 获取外部键,但不进行排序
通过将一个不存在的键作为参数传给 BY 选项, 可以让 SORT 跳过排序操作, 直接返回结果:
redis 127.0.0.1:6379> SORT uid BY not-exists-key
1) "4"
2) "3"
3) "2"
4) "1"
这种用法在单独使用时,没什么实际用处。
不过,通过将这种用法和 GET 选项配合, 就可以在不排序的情况下, 获取多个外部键, 相当于执行一个整合的获取操作(类似于 SQL 数据库的 join 关键字)。
以下代码演示了,如何在不引起排序的情况下,使用 SORT 、 BY 和 GET 获取多个外部键:
redis 127.0.0.1:6379> SORT uid BY not-exists-key GET # GET user_level_* GET user_name_*
1) "4" # id
2) "70" # level
3) "mary" # name
4) "3"
5) "25"
6) "peter"
7) "2"
8) "10"
9) "jack"
10) "1"
11) "9999"
12) "admin"
4.11 将哈希表作为 GET 或 BY 的参数
除了可以将字符串键之外, 哈希表也可以作为 GET 或 BY 选项的参数来使用。
比如说,对于前面给出的用户信息表:
uid | user_name_{uid} | user_level_{uid} |
1 | admin | 9999 |
2 | jack | 10 |
3 | peter | 25 |
4 | mary | 70 |
我们可以不将用户的名字和级别保存在 user_name_{uid} 和 user_level_{uid} 两个字符串键中, 而是用一个带有 name 域和 level 域的哈希表user_info_{uid} 来保存用户的名字和级别信息:
redis 127.0.0.1:6379> HMSET user_info_1 name admin level 9999
OK
redis 127.0.0.1:6379> HMSET user_info_2 name jack level 10
OK
redis 127.0.0.1:6379> HMSET user_info_3 name peter level 25
OK
redis 127.0.0.1:6379> HMSET user_info_4 name mary level 70
OK
之后, BY 和 GET 选项都可以用 key->field 的格式来获取哈希表中的域的值, 其中 key 表示哈希表键, 而 field 则表示哈希表的域:
redis 127.0.0.1:6379> SORT uid BY user_info_*->level
1) "2"
2) "3"
3) "4"
4) "1"
redis 127.0.0.1:6379> SORT uid BY user_info_*->level GET user_info_*->name
1) "jack"
2) "peter"
3) "mary"
4) "admin"
4.12 保存排序结果
默认情况下, SORT 操作只是简单地返回排序结果,并不进行任何保存操作。
通过给 STORE 选项指定一个 key 参数,可以将排序结果保存到给定的键上。
如果被指定的 key 已存在,那么原有的值将被排序结果覆盖。
# 测试数据
redis 127.0.0.1:6379> RPUSH numbers 1 3 5 7 9
(integer) 5
redis 127.0.0.1:6379> RPUSH numbers 2 4 6 8 10
(integer) 10
redis 127.0.0.1:6379> LRANGE numbers 0 -1
1) "1"
2) "3"
3) "5"
4) "7"
5) "9"
6) "2"
7) "4"
8) "6"
9) "8"
10) "10"
redis 127.0.0.1:6379> SORT numbers STORE sorted-numbers
(integer) 10
# 排序后的结果
redis 127.0.0.1:6379> LRANGE sorted-numbers 0 -1
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
6) "6"
7) "7"
8) "8"
9) "9"
10) "10"
可以通过将 SORT 命令的执行结果保存,并用 EXPIRE 为结果设置生存时间,以此来产生一个 SORT 操作的结果缓存。
这样就可以避免对 SORT 操作的频繁调用:只有当结果集过期时,才需要再调用一次 SORT 操作。
另外,为了正确实现这一用法,你可能需要加锁以避免多个客户端同时进行缓存重建(也就是多个客户端,同一时间进行 SORT 操作,并保存为结果集),具体参见 SETNX 命令。
可用版本:
>= 1.0.0 时间复杂度:
O(N+M*log(M)), N 为要排序的列表或集合内的元素数量, M 为要返回的元素数量。
如果只是使用 SORT 命令的 GET 选项获取数据而没有进行排序,时间复杂度 O(N)。
返回值:
没有使用 STORE 参数,返回列表形式的排序结果。
使用 STORE 参数,返回排序结果的元素数量。
4.13 zset 中的排序
127.0.0.1:6379> ZADD page_rank 10 google.com
(integer) 1
127.0.0.1:6379> ZADD page_rank 9 baidu.com 8 bing.com
(integer) 2
127.0.0.1:6379> ZRANGE page_rank 0 -1 WITHSCORES
1) "bing.com"
2) "8"
3) "baidu.com"
4) "9"
5) "google.com"
6) "10"
127.0.0.1:6379> ZREVRANGE page_rank 0 -1 WITHSCORES
1) "google.com"
2) "10"
3) "baidu.com"
4) "9"
5) "bing.com"
6) "8"
5、Refer:
1、redis 中文/英文文档:
http://redis.cn/commands.html#sorted_set
http://redis.readthedocs.org/en/latest/key/sort.html
http://redis.readthedocs.org/en/latest/sorted_set/zrangebyscore.html
2、Redis几个认识误区
http://timyang.net/data/redis-misunderstanding/
3、十五分钟介绍 Redis 数据结构
4、Redis 学习笔记
http://docs.linuxtone.org/ebooks/NOSQL/Redis%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0.pdf
5、redis 在线测试环境
6、Redis能干啥?细看11种Web应用场景
http://os.51cto.com/art/201107/278292.htm
7、利用Redis的有序集合做购物车商品相关性分析
http://superlxw1234.iteye.com/blog/1868410
8、怎么样使用Redis来存储和查询ip数据