相关资料
Redis官网
An introduction to Redis data types and abstractions
在线Tutorial: Try Redis
下载与安装
Redis命令参考(中文)
官方手册command reference
前言
Redis: The name Redis means REmote DIctionary Server. Redis是一个Key-Value的存储系统、一个data structures server。与传统的存储字符串key和字符串value的存储系统相比,Redis的value提供了丰富多彩的数据结构。包括:
- Binary-safe strings
- Lists:基于链表的按照插入顺序排序的字符串集合
- Sets:无序、不重复的字符串集合
- Sorted Sets:与字符串相似,但每一个字符串元素都赋予了一个浮点数的值,称为score。所有的元素按照 score 值排序,因此可以使用 ZRANGE 命令以index获取元素
- Hashes:其 value 存储由键值对组成的映射。键值对都是字符串,类似于Ruby和Python中的hashes
- Bit arrays(or simply bitmaps):通过特殊的指令来把字符串值当作bits数组结果,可以设置和清楚单个bits,计算所有bits为1的个数,查找第一个置1或置0的bit等等
- HyperLogLogs:一个非常轻量级的计算不重复元素的个数的数据结构(笔者个人的看法)
- Streams:见Introduction to Redis Streams
本文只对常见的Redis数据类型和命令做总结,更详尽的命令可以查看官方手册command reference
Redis Keys
Redis存储系统的顶层Key,注意与Redis数据类型中的Hash部分的key做区分。结合Hash运算和散列表的知识不难得知,这个Key用于运算Redis的value的存储位置。 关于Redis Keys的一些特性和规则:
- 二进制安全,意味着你可以将二进制序列作为一个key,从字符串 “foo” 到JPEG文件都可以。空字符串也是一个有效的key
- 从内存、带宽和效率的角度考虑,过长的key不是个好主意
- 太短的key也不是个好主意。比如 “u1000flw” 的可读性明显低于 “user:1000:followers”。你应该在key的长度中寻找平衡
- 最好固定一个key的模版。多个字段时合理利用
: . - 等字符来提高key的可读性。比如 “object-type:id"这种模式对应"user:1000” 。多单词字段时可以写为 “comment:1000:reply.to” 或者 “comment:1000:reply-to” - key的最大允许大小为512MB
修改和查询key space
有几个与具体类型无关的对key进行操作的命令。
SET && EXISTS && DEL && TYPE
127.0.0.1:6379> SET mykey hello
OK
127.0.0.1:6379> EXISTS mykey
(integer) 1
127.0.0.1:6379> DEL mykey
(integer) 1
127.0.0.1:6379> EXISTS mykey
(integer) 0
127.0.0.1:6379> SET mykey x
OK
127.0.0.1:6379> TYPE mykey
string
127.0.0.1:6379> DEL mykey
(integer) 1
127.0.0.1:6379> TYPE mykey
none
更多与key space相关的指令参考command reference
Redis expires:keys with limited time to live
在介绍更复杂的数据结构之前,有必要介绍一下另一个与具体类型无关的指令,Redis expires。你可以通过expire指令来给key设定一个寿命,在寿命倒数完毕后相应的key将被销毁,就像执行了DEL指令一样。
- 寿命能以秒和毫秒做单位
- 到期时间分辨率总是1毫秒
- 有关过期的信息被复制并保留在磁盘上,当您的Redis服务器停止时,时间实际上已经过去了(这意味着Redis保存key到期的时间)[参考资料硬翻译,本句笔者并没有理解,有知道的欢迎评论区指出]
EXPIRE && TTL && PERSIST
127.0.0.1:6379> SET key somevalue
OK
127.0.0.1:6379> EXPIRE key 10
(integer) 1
127.0.0.1:6379> GET key
"somevalue"
127.0.0.1:6379> GET key
(nil)
127.0.0.1:6379> SET key somevalue ex 10
OK
127.0.0.1:6379> TTL key
(integer) 5
127.0.0.1:6379> TTL key
(integer) 1
127.0.0.1:6379> TTL key
(integer) -2
127.0.0.1:6379> SET key somevalue ex 20
OK
127.0.0.1:6379> TTL key
(integer) 18
127.0.0.1:6379> PERSIST key
(integer) 1
127.0.0.1:6379> TTL key
(integer) -1
Redis Strings
SET && GET
Redis Key所能对应的最简单的value。初学者最先接触的Redis数据类型。这种情况下,我们是把string类型的key映射到string类型的value。string类型有多种用法,比如可以用来缓存HTML网页。 说明:本文档所有redis命令通过redis-cli工具展示。127.0.0.1是本机ip,6379是redis-server的默认端口。markdown文档内代码块使用的js风格。所以部分代码高亮可能并不合适。
127.0.0.1:6379> SET mykey somevalue
OK
127.0.0.1:6379> GET mykey
"somevalue"
使用SET 和GET 命令可以存取string value。SET其实是赋值操作,意味着如果对应的key已经有了value,那么后续针对该key的SET操作将覆盖原先的值。
value可以是string(包括binary data)的各种形式,你甚至可以把jpeg image当作一个value,但是一个value的大小不能超过512MB。
SET命令可以添加附加参数,比如我可以让key只在没有值的时候才赋值成功,我可以使用以下命令:
127.0.0.1:6379> SET mykey somevalue
OK
127.0.0.1:6379> SET mykey newval NX
(nil)
127.0.0.1:6379> GET mykey
"somevalue"
127.0.0.1:6379> SET mykey newval XX
OK
127.0.0.1:6379> GET mykey
"newval"
number型表现形式的string在redis中可以直接进行原子性的增减操作,比如:
127.0.0.1:6379> INCR num
(integer) 101
127.0.0.1:6379> INCR num
(integer) 102
127.0.0.1:6379> INCRBY num 50
(integer) 152
INCR命令把string value转化成integer,加一,然后把新值赋值给value。与之类似的指令还有INCRBY、DECR和DECRBY。
原子性意味着什么呢?当多个客户端同时读取同一个string value时,进行加减操作,结果永远不会并发出错。比如客户端1读取“10”,客户端2读取“10”,客户端1+1,客户端2加1,结果永远是“12”。
Redis的所有操作都是原子性的,意思就是要么成功执行,要么失败完全不执行。单个操作是原子性的,多个操作也支持事务进行原子性包装,通过MULTI和EXEC指令包起来。
Redis支持单挑指令进行多个SET和GET操作,此种做法有助于减少延迟。
127.0.0.1:6379> MSET a 10 b 20 c 30
OK
127.0.0.1:6379> MGET a b c
1) "10"
2) "20"
3) "30"
127.0.0.1:6379> GET a
"10"
127.0.0.1:6379> GET b
"20"
127.0.0.1:6379> GET c
"30"
Redis Lists
List在各种编程语言和信息技术中都有,不同的编程语言语境表达的意思稍有差异,比如“Python Lists”指的是Arrays,但不管底层实现用的什么,其本质思想就是一个线性表。存储的元素按序排列,对于Arrays是使用数据的物理顺序表达逻辑顺序,而Linked List则是用一个指向下一个元素的指针表示逻辑顺序。
Redis Lists的底层实现是Linked Lists。这意味着即使有上百万的元素,在对Lists头部和尾部添加元素时耗费的都是常数时间。
缺点是通过indes访问元素时比用Array实现时要慢许多。
Redis使用Linked Lists实现的一个原因是对于数据库而言,插入元素是一个很常见和重要而且很在乎效率的操作,而且对于Redis而言,固定长度的index访问能在常量时间内完成。
当需要快速访问到集合中间位置时,Redis提供了另一个结构Sorted Sets供用户使用。
First steps with Redis Lists
RPUSH && LPUSH
127.0.0.1:6379> RPUSH mylist A
(integer) 1
127.0.0.1:6379> RPUSH mylist B
(integer) 2
127.0.0.1:6379> LPUSH mylist first
(integer) 3
127.0.0.1:6379> RPUSH mylist 1 2 3 4 5 "foo bar"
(integer) 9
127.0.0.1:6379> LRANGE mylist 0 -1
1) "first"
2) "A"
3) "B"
4) "1"
5) "2"
6) "3"
7) "4"
8) "5"
9) "foo bar"
维基百科: foo bar
LRANGE
127.0.0.1:6379> LRANGE mylist 0 -1
1) "first"
2) "A"
3) "B"
注意:虽然LRANGE从技术上讲是一个O(N)命令,但访问列表头部或尾部的小范围是一个恒定时间操作。
LPOP && RPOP
127.0.0.1:6379> RPUSH list a b c
(integer) 3
127.0.0.1:6379> RPOP list
"c"
127.0.0.1:6379> LPOP list
"a"
127.0.0.1:6379> RPOP list
"b"
127.0.0.1:6379> RPOP list
(nil)
Common use cases for lists
Lists可以用于许多任务
- 记住用户在社交网络上发布的最新更新
- 进程之间的通信,使用消费者-生产者模式,其中生产者将项目推送到列表中,消费者(通常是工人)消耗这些项目并执行操作。Redis有特殊的列表命令,使这个用例更可靠、更高效。
例如,流行的Ruby库reque和sidekiq都使用引擎盖下的Redis Lists来实现后台作业。
流行的Twitter社交网络将用户发布的最新推文纳入Redis Lists。
这里逐步描述一下更常见的情况,请想象您的主页显示照片共享社交网络中发布的最新照片,并希望加快访问速度。
每次用户发布新照片时,我们都会将其ID添加到LPUSH列表中。
当用户访问主页时,我们使用LRANGE 0 9来获取最新的10个发布项目。
Capped lists
LTRIM
在许多用例中,我们只想使用Lists来存储最新项目,无论它们是什么:社交网络更新、日志或其他任何内容。
Redis允许我们使用列表作为带上限的集合,仅记住最新的N个项目,并使用LTRIM命令丢弃所有最古老的项目。
LTRIM命令类似于LRANGE,但它没有显示指定的元素范围,而是将这个范围设置为新的列表值。删除给定范围以外的所有元素。
127.0.0.1:6379> RPUSH mylist 1 2 3 4 5
(integer) 5
127.0.0.1:6379> LTRIM mylist 0 2
OK
127.0.0.1:6379> LRANGE mylist 0 -1
1) "1"
2) "2"
3) "3"
这允许一个非常简单但有用的模式:一起做列表推送操作+列表修剪操作,以添加新元素并丢弃超过限制的元素:
LPUSH mylist <some element>
LTRIM mylist 0 999
上述组合添加了一个新元素,仅将1000个最新元素放入列表中。使用LRANGE,您可以访问顶级项目,而无需记住非常旧的数据。
Blocking operations on lists
列表具有特殊功能,使其适合实现队列,通常作为进程间通信系统的基石:阻塞操作。
想象一下,您想通过一个进程将项目推送到列表中,并使用不同的进程来实际使用这些项目进行某种工作。这是常见的生产者/消费者设置,可以通过以下简单方式实现:
- 要将项目推入列表中,生产商调用LPUSH。
- 要从列表中提取/处理项目,消费者调用RPOP。
然而,有时列表可能是空的,没有什么需要处理的,所以RPOP只是返回NULL。在这种情况下,消费者被迫等待一段时间,然后使用RPOP再次重试。这被称为轮询,在这种情况下不是一个好主意,因为它有几个缺点:
- 强迫Redis和客户端处理无用的命令(列表为空时的所有请求将无法完成实际工作,他们只会返回NULL)。
- 增加了项目处理的延迟,因为工人收到NULL后,它会等待一段时间。为了减少延迟,我们可以在调用RPOP之间减少等待,从而放大问题1,即对Redis的更多无用调用。
因此,Redis实现了名为BRPOP和BLPOP的命令,这些命令是RPOP和LPOP的版本,如果列表为空,则可以阻止:它们只有在向列表中添加新元素或达到用户指定的超时时,它们才会返回给调用方。
以下是我们可以在工人中使用的BRPOP调用的示例:
127.0.0.1:6379> RPUSH tasks "tasks" "do_something"
(integer) 2
127.0.0.1:6379> BRPOP tasks 5
1) "tasks"
2) "do_something"
127.0.0.1:6379> BRPOP tasks 5
1) "tasks"
2) "tasks"
127.0.0.1:6379> BRPOP tasks 5
(nil)
(5.04s)
这意味着:“等待列表任务中的元素,但如果5秒后没有可用的元素,则返回”。
请注意,您可以使用0作为超时器,以永久等待元素,您还可以指定多个列表,而不仅仅是一个列表,以便同时在多个列表中等待,并在第一个列表收到元素时收到通知。
关于BRPOP,需要注意的几点:
- 客户端以有序的方式提供服务:阻止等待列表的第一个客户端,当元素被其他客户端推送时,首先服务,依此类推。
- 与RPOP相比,返回值不同:它是一个双元素数组,因为它也包含密钥的名称,因为BRPOP和BLPOP能够阻止从多个列表中等待元素。
- 如果达到超时,将返回NULL。
关于列表和阻止操作,你应该知道更多事情。我们建议您查阅一下命令:
使用LMOVE可以构建更安全的队列或旋转队列。
该命令还有一个阻止变体,称为BLMOVE。
Automatic creation and removal of keys
到目前为止,在我们的示例中,我们从未在推送元素之前创建空Lists,或在其中不再有元素时删除空Lists。Redis有责任在LIsts为空时删除key,如果key不存在,我们正在尝试向其中添加元素,例如使用LPUSH,则创建空Lists。
这不特定于列表,它适用于由多个元素组成的所有Redis数据类型——Steams、Sets、Sorted Sets和Hashes。
基本上,我们可以用三条规则来总结:
- 当我们将元素添加到聚合数据类型时,如果目标key不存在,则在添加元素之前创建一个空聚合数据类型。
127.0.0.1:6379> DEL mylist
(integer) 1
127.0.0.1:6379> LPUSH mylist 1 2 3
(integer) 3
127.0.0.1:6379> SET foo bar
OK
127.0.0.1:6379> LPUSH foo 1 2 3
(error) WRONGTYPE Operation against a key holding the wrong kind of value
127.0.0.1:6379> TYPE foo
string
- 当我们从聚合数据类型中删除元素时,如果value保持空,key将自动销毁。Steam类型是此规则的唯一例外。
127.0.0.1:6379> LPUSH mylist 1 2 3
(integer) 3
127.0.0.1:6379> EXISTS mylist
(integer) 1
127.0.0.1:6379> LPOP mylist
"3"
127.0.0.1:6379> LPOP mylist
"2"
127.0.0.1:6379> LPOP mylist
"1"
127.0.0.1:6379> EXISTS mylist
(integer) 0
- 使用空key调用LLEN(返回列表长度)等只读命令或删除元素的写命令总是产生与键持有命令预期找到的类型的空聚合类型相同的结果。
127.0.0.1:6379> del mylist
(integer) 0
127.0.0.1:6379> llen mylist
(integer) 0
127.0.0.1:6379> lpop mylist
(nil)
Redis Hashes
Redis Hashes与人们期望的“hash”一致,属于键值对:
127.0.0.1:6379> HMSET user:1000 username holon birthyear 1997 verified 1
OK
127.0.0.1:6379> HGET user:1000 username
"holon"
127.0.0.1:6379> HGET user:1000 birthyear
"1997"
127.0.0.1:6379> HGETALL user:1000
1) "username"
2) "holon"
3) "birthyear"
4) "1997"
5) "verified"
6) "1"
虽然hashes可用于表示_objects_,但实际上,您可以放置在Hashes中的字段数量没有实际限制(可用内存除外),因此您可以在应用程序内以多种不同方式使用Hashes。 HMSET命令设置Hashes的多个字段,而HGET检索单个字段。HMGET与HGET相似,但返回一个数组:
127.0.0.1:6379> HMGET user:1000 username birthyear no-such-field
1) "holon"
2) "1997"
3) (nil)
有些命令也可以对单个字段执行操作,如HINCRBY:
127.0.0.1:6379> HINCRBY user:1000 birthyear 1
(integer) 1998
127.0.0.1:6379> HINCRBY user:1000 birthyear 10
(integer) 2008
您可以在文档中找到Hash命令的完整列表。 值得注意的是,小散列(即几个值较小的元素)在内存中以特殊方式编码,使其内存效率很高。
Redis Sets
Redis Sets是字符串的无序集合。SADD命令向Set添加新元素。也可以对Set进行许多其他操作,例如测试给定元素是否已经存在,在多个集合之间执行交集、联合或差分等。
127.0.0.1:6379> SADD myset 1 2 3
(integer) 3
127.0.0.1:6379> SMEMBERS myset
1) "1"
2) "2"
3) "3"
在这里,我在集合中添加了三个元素,并告诉Redis返回所有元素。如您所见,它们没有排序——Redis在每次调用时都可以自由返回元素,因为与用户没有关于元素顺序的约定。 Redis有检查元素存在与否的命令。例如,检查元素是否存在:
127.0.0.1:6379> SISMEMBER myset 3
(integer) 1
127.0.0.1:6379> SISMEMBER myset 30
(integer) 0
“3”是Sets的成员,而“30”不是。 Sets有利于表达对象之间的关系。例如,我们可以很容易地使用集合来实现标签。 建模这个问题的一个简单方法是为我们想要标记的每个对象设置一个set。该set包含与对象关联的标签的ID。 一个例子是给新闻文章贴标签。如果文章ID 1000用标签1、2、5和77标记,一组可以将这些标签ID与新闻项目关联:
127.0.0.1:6379> SADD news:1000:tags 1 2 5 77
(integer) 1
我们可能还希望有相反的关系:所有带有给定标签的新闻的列表:
127.0.0.1:6379> SADD tag:1:news 1000
(integer) 1
127.0.0.1:6379> SADD tag:2:news 1000
(integer) 1
127.0.0.1:6379> SADD tag:5:news 1000
(integer) 1
127.0.0.1:6379> SADD tag:77:news 1000
(integer) 1
获取给定对象的所有标签非常简单:
127.0.0.1:6379> SMEMBERS news:1000:tags
1) "1"
2) "2"
3) "5"
4) "7"
5) "77"
注意:在示例中,我们假设您有另一个数据结构,例如Redis散列,它将标签ID映射到标签名称。 使用正确的Redis命令,还有其他非琐碎的操作仍然很容易实现。例如,我们可能想要一个带有标签1、2、10和27的所有对象的列表。我们可以使用SINTER命令做到这一点,该命令执行不同集之间的交集。我们可以使用:
127.0.0.1:6379> SINTER tag:1:news tag:2:news tag:10:news tag:27:news
... results here ...
除了交叉外,您还可以执行联合、差异、提取随机元素等。 提取元素的命令称为SPOP,用于建模某些问题。例如,为了实现基于网络的扑克游戏,想象一下,我们对(C)lubs(??)、(D)iamonds(??)、(H)earts(??)、(S)pades(??)使用单字符前缀:
127.0.0.1:6379> SADD deck C1 C2 C3 C4 C5 C6 C7 C8 C9 C10 CJ CQ CK
D1 D2 D3 D4 D5 D6 D7 D8 D9 D10 DJ DQ DK
H1 H2 H3 H4 H5 H6 H7 H8 H9 H10 HJ HQ HK
S1 S2 S3 S4 S5 S6 S7 S8 S9 S10 SJ SQ SK
(integer) 52
现在,我们想为每个玩家提供5张牌。SPOP命令删除随机元素,将其返回给客户端,因此在这种情况下,这是完美的操作。 然而,如果我们直接把它放在deck(一副牌)上,在游戏的下一场比赛中,我们需要再次填充纸牌deck,这可能并不理想。因此,首先,我们可以将存储在deck密钥中的集复制到game:1:deck这个key中。 这是使用SUIONSTORE完成的,SUIONSTORE通常在多个集合之间执行联合,并将结果存储到另一个集合中。然而,由于单个集合的结合本身,我可以复制我的甲板:
127.0.0.1:6379> SETUNIONSTORE game:1:deck deck
(integer) 52
现在,我准备为第一个玩家提供五张牌:
127.0.0.1:6379> SPOP game:1:deck
"C6"
127.0.0.1:6379> SPOP game:1:deck
"CQ"
127.0.0.1:6379> SPOP game:1:deck
"D1"
127.0.0.1:6379> SPOP game:1:deck
"CJ"
127.0.0.1:6379> SPOP game:1:deck
"SJ"
一个对勾,还好。。。 这是引入set命令的好时机,该命令提供了集合中的元素数量。这通常被称为集合理论背景下的基数,因此Redis命令被称为SCARD。
127.0.0.1:6379> scard game:1:deck
(integer) 47
数学工作原理:52-5 = 47。 当您只需要获取随机元素而不从集合中删除它们时,有适合任务的SRANDMEMBER命令。它还具有返回重复和非重复元素的能力。
Redis Sorted Sets
一个冷知识:Why Redis ‘Zset’ means ‘Sorted Set’? A similar question is asked before on Redis’s github page and the creator of Redis answered it
Hello. Z is as in XYZ, so the idea is, sets with another dimension: the order. It’s a far association… I know 😃
大意是说Z是相对于X和Y的另一个纬度,在这里就是指顺序。 Set commands start with s Hash commands start with h List commands start with l Sorted set commands start with z Stream commands start with x Hyperloglog commands start with pf 而PF又指什么呢?这里的PF是一个人命的缩写,Philippe Flajolet,此人是HyperLogLog算法的发明人,关于HyperLogLog,可以参考这个维基百科: HyperLogLog
Sorted Sets是一种类似于Set和Hash之间的混合的数据类型。与Sets一样,Sorted Sets由唯一、非重复的字符串元素组成,因此从某种意义上说,Sorted Sets也是Sets。 然而,虽然Sets中的元素不是有序的,但Sorted Sets中的每个元素都与浮点值相关联,称为分数(这就是为什么类型也类似于散列,因为每个元素都映射到一个值)。 此外,排序集中的元素按顺序取(因此它们不是应要求排序的,顺序是用于表示排序集的数据结构的特点)。他们是根据以下规则排序的:
- 如果A和B是两个得分不同的元素,那么如果A.score是>B.score,则A>B。
- 如果A和B的分数完全相同,那么如果A字符串在词汇上大于B字符串,则A>B。A和B字符串不能相等,因为排序集只有唯一的元素。
让我们从一个简单的例子开始,添加一些选定的黑客名字作为排序集元素,他们的出生年份为“分数”。
127.0.0.1:6379> ZADD hackers 1940 "Alan Kay"
(integer) 1
127.0.0.1:6379> ZADD hackers 1957 "Sophie Wilson"
(integer) 1
127.0.0.1:6379> ZADD hackers 1953 "Richard Stallman"
(integer) 1
127.0.0.1:6379> ZADD hackers 1949 "Anita Borg"
(integer) 1
127.0.0.1:6379> ZADD hackers 1965 "Yukihiro Matsumoto"
(integer) 1
127.0.0.1:6379> ZADD hackers 1914 "Hedy Lamarr"
(integer) 1
127.0.0.1:6379> ZADD hackers 1916 "Claude Shannon"
(integer) 1
127.0.0.1:6379> ZADD hackers 1969 "Linus Torvalds"
(integer) 1
127.0.0.1:6379> ZADD hackers 1912 "Alan Turing"
(integer) 1
如您所见,ZADD与SADD相似,但需要另一个参数(放在要添加的元素之前),即score。ZADD也是变量,因此您可以自由指定多个score-value对,即使上面的示例中没有使用。 使用排序集,返回按出生年份排序的黑客列表是很简单的,因为他们实际上已经排序了。 实现说明:排序集是通过双端口数据结构实现的,该结构包含跳过列表和散列表,因此每次我们添加元素Redis都会执行O(log(N))操作。这很好,但当我们要求排序元素Redis根本不需要做任何工作时,它已经全部排序了:
127.0.0.1:6379> zrange hackers 0 -1
1) "Alan Turing"
2) "Hedy Lamarr"
3) "Claude Shannon"
4) "Alan Kay"
5) "Anita Borg"
6) "Richard Stallman"
7) "Sophie Wilson"
8) "Yukihiro Matsumoto"
9) "Linus Torvalds"
注意:0和-1表示从元素索引0到最后一个元素(-1在这里工作,就像LRANGE命令一样)。 如果我想按相反的方式订购它们,从小到大呢?使用ZREVRANGE而不是ZRANGE:
127.0.0.1:6379> zrevrange hackers 0 -1
1) "Linus Torvalds"
2) "Yukihiro Matsumoto"
3) "Sophie Wilson"
4) "Richard Stallman"
5) "Anita Borg"
6) "Alan Kay"
7) "Claude Shannon"
8) "Hedy Lamarr"
9) "Alan Turing"
It is possible to return scores as well, using the WITHSCORES argument:
127.0.0.1:6379> zrange hackers 0 -1 withscores
1) "Alan Turing"
2) "1912"
3) "Hedy Lamarr"
4) "1914"
5) "Claude Shannon"
6) "1916"
7) "Alan Kay"
8) "1940"
9) "Anita Borg"
10) "1949"
11) "Richard Stallman"
12) "1953"
13) "Sophie Wilson"
14) "1957"
15) "Yukihiro Matsumoto"
16) "1965"
17) "Linus Torvalds"
18) "1969"
Operating on ranges
排序集有很多强大的功能可以通过RANGES实现。让我们让所有出生到1950年的人都包括在内。我们使用ZRANGEBYSCORE命令来执行:
127.0.0.1:6379> zrangebyscore hackers -inf 1950
1) "Alan Turing"
2) "Hedy Lamarr"
3) "Claude Shannon"
4) "Alan Kay"
5) "Anita Borg"
我们要求Redis用负无穷大和1950(包括两个极端)之间的分数返回所有元素。 也可以删除元素的范围。让我们从分类集中删除所有1940年至1960年间出生的黑客:
127.0.0.1:6379> zremrangebyscore hackers 1940 1960
(integer) 4
ZREMRANGEBYSCORE可能不是最好的命令名称,但它可能非常有用,并返回删除元素的数量。 为排序集元素定义的另一个非常有用的操作是get-rank操作。可以询问元素在有序元素集中的位置是什么。
127.0.0.1:6379> zrank hackers "Anita Borg"
(integer) 4
考虑到元素的降序排序方式,ZREVRANK命令也可用以获取排名。
Lexicographical(按字典序) scores
在最新版本的Redis 2.8中,引入了一项新功能,允许通过词典获取范围,假设排序集中的元素都插入相同的分数相同(元素与C memcmp函数进行比较,因此保证没有排序,并且每个Redis实例都将以相同的输出进行响应)。 使用词典范围操作的主要命令是ZRANGEBYLEX、ZREVRANGEBYLEX、ZREMRANGEBYLEX和ZLEXCOUNT。 例如,让我们再次添加我们的著名黑客名单,但这次对所有元素使用零分:
127.0.0.1:6379> zadd hackers 0 "Alan Kay" 0 "Sophie Wilson" 0 "Richard Stallman" 0 "Anita Borg" 0 "Yukihiro Matsumoto" 0 "Hedy Lamarr" 0 "Claude Shannon" 0 "Linus Torvalds" 0 "Alan Turing"
由于排序集的排序规则,它们已经按词典排序:
127.0.0.1:6379> zrange hackers 0 -1
1) "Alan Kay"
2) "Alan Turing"
3) "Anita Borg"
4) "Claude Shannon"
5) "Hedy Lamarr"
6) "Linus Torvalds"
7) "Richard Stallman"
8) "Sophie Wilson"
9) "Yukihiro Matsumoto"
使用ZRANGEBYLEX,我们可以询问词典范围:
127.0.0.1:6379> zrangebylex hackers [B [P
1) "Claude Shannon"
2) "Hedy Lamarr"
3) "Linus Torvalds"
范围可以是包容性的或排他性的(取决于第一个字符),也可以用+和-字符串分别指定字符串无限和负无限。有关更多信息,请参阅文档。 此功能很重要,因为它允许我们使用排序集作为通用索引。例如,如果您想用128位无符号整数参数索引元素,您只需要将元素添加到具有相同分数(例如0)但具有16字节前缀的排序集中,该前缀由大端数中的128位数组成。由于大端数中的数字,当词典顺序(按原始字节顺序)实际上也是按数字顺序排列时,您可以询问128位空间中的范围,并获得元素丢弃前缀的元素值。 如果您想在更严肃的演示中查看该功能,请查看Redis自动完成演示。
Updating the score: leader boards
在切换到下一个主题之前,只需最后注释一下排序集。排序集的分数可以随时更新。仅针对已包含在排序集中的元素调用ZADD,就会用O(log(N))时间复杂性更新其分数(和位置)。因此,当有大量更新时,排序集是合适的。 由于这一特点,一个常见的用例是排行榜。典型的应用程序是一个Facebook游戏,您将根据高分排序的用户和排名操作相结合,以显示前N的用户和排行榜上的用户排名(例如,“您是这里的#4932最佳得分”)。
BitMaps
位图不是实际的数据类型,而是在字符串类型上定义的一组面向位的操作。由于字符串是二进制安全斑点,其最大长度为512 MB,因此它们适合设置多达232个不同的位。 位操作分为两组:恒定时单位操作,如将位设置为1或0,或获取其值,以及对位组的操作,例如计算给定位范围内的设置位数(例如,人口计数)。 位图的最大优势之一是,它们在存储信息时通常节省了极大的空间。例如,在一个不同用户由增量用户ID表示的系统中,只需使用512MB内存,就可以记住40亿用户的单个位信息(例如,知道用户是否想接收时事通讯)。 使用SETBIT和GETBIT命令设置和检索位:
127.0.0.1:6379> setbit key 10 1
(integer) 1
127.0.0.1:6379> getbit key 10
(integer) 1
127.0.0.1:6379> getbit key 11
(integer) 0
SETBIT命令将位数作为第一个参数,并将位设置为1或0的值作为第二个参数。如果地址位超出当前字符串长度,该命令会自动放大字符串。 GETBIT仅返回指定索引处的位值。超出范围的位(地址超出存储到目标键的字符串长度的位)始终被视为零。 有三个命令在位组上运行: BITOP在不同字符串之间执行位操作。提供的操作是AND、OR、XOR和NOT。 BITCOUNT执行人口计数,报告设置为1的位数。 BITPOS找到指定值为0或1的第一个位。 BITPOS和BITCOUNT都能够使用字符串的字节范围操作,而不是在整个字符串长度内运行。以下是BITCOUNT调用的微不足道的例子:
127.0.0.1:6379> setbit key 0 1
(integer) 0
127.0.0.1:6379> setbit key 100 1
(integer) 0
127.0.0.1:6379> bitcount key
(integer) 2
位图的常见用例是: 各种实时分析。 存储与对象ID关联的空间高效但高性能布尔信息。 例如,想象一下,您想知道网站用户每天访问时间最长的次数。您从零开始计算天数,即您公开网站的日期,每次用户访问网站时,都使用SETBIT设置一点。作为位索引,您只需取当前的unix时间,减去初始偏移量,然后除以一天中的秒数(通常为3600*24)。 这样,对于每个用户,您都有一个小字符串,其中包含每天的访问信息。使用BITCOUNT,可以轻松获得给定用户访问网站的天数,而只需拨打几个BITPOS,或简单地获取和分析位图客户端,即可轻松计算最长的连胜。 位图很难拆分为多个键,例如为了分割数据集,并且一般来说最好避免使用大键。要在不同键之间拆分位图,而不是将所有位设置为键,一个琐碎的策略只是存储每个键的M位,并获得bit-number/M的键名和第N位,以用bit-number MOD M在密钥内寻址。
HyperLogLogs
HyperLogLog是一种概率数据结构,用于计数唯一事物(从技术上讲,这是指估计集合的基数)。通常,计算唯一项目需要使用与要计数的项目数量成正比的内存量,因为您需要记住过去已经看到的元素,以避免多次计数它们。然而,有一套算法用内存来换取精度:你以标准误差的估计度量结束,就Redis实现而言,该误差小于1%。该算法的神奇之处在于,您不再需要使用与计数的项目数量成正比的内存量,而是可以使用恒定的内存量!在最坏的情况下,有12k字节,或者如果您的HyperLogLog(从现在起就叫它们HLL)看到很少的元素,则要少得多。 Redis中的HLL虽然在技术上是不同的数据结构,但编码为Redis字符串,因此您可以调用GET序列化HLL,并调用SET将其反序列化回服务器。 从概念上讲,HLL API就像使用Sets来执行相同的任务。您将将每个观察到的元素添加到集合中,并使用SCARD检查集合中的元素数量,这些元素是唯一的,因为SADD不会重新添加现有元素。 虽然您实际上没有将项目添加到HLL中,因为数据结构仅包含不包含实际元素的状态,API是一样的: 每次您看到新元素时,都会使用PFADD将其添加到计数中。 到目前为止,每次您想检索使用PFADD添加的唯一元素的当前近似值时,您都会使用PFCOUNT。
127.0.0.1:6379> pfadd hll a b c d
(integer) 1
127.0.0.1:6379> pfcount hll
(integer) 4
这种数据结构用例的一个例子是每天在搜索表单中计算用户执行的唯一查询。 Redis还能够执行HLL的联合,请查看完整文档以了解更多信息。
其他值得注意的功能
Redis API中还有其他重要内容无法在本文档中探索,但值得您关注: 可以逐步迭代大型集合的关键空间。 可以运行Lua脚本服务器端,以提高延迟和带宽。 Redis也是一个Pub-Sub服务器。
了解更多
本教程绝非完整,仅涵盖API的基础知识。阅读命令参考以发现更多。 感谢您的阅读,并与Redis一起玩得开心!
Redis是单线程的吗?
|