redis
http://www.redis.cn/topics/data-types-intro.html#sorted-sets
文章目录
Redis keys
Redis key值是二进制安全的,这意味着可以用任何二进制序列作为key值,从形如”foo”的简单字符串到一个JPEG文件的内容都可以。空字符串也是有效key值。
关于key的几条规则:
- 太长的键值不是个好主意,例如1024字节的键值就不是个好主意,不仅因为消耗内存,而且在数据中查找这类键值的计算成本很高。
- 太短的键值通常也不是好主意,如果你要用”u:1000:pwd”来代替”user:1000:password”,这没有什么问题,但后者更易阅读,并且由此增加的空间消耗相对于key object和value object本身来说很小。当然,没人阻止您一定要用更短的键值节省一丁点儿空间。
- 最好坚持一种模式。例如:”object-type🆔field”就是个不错的注意,像这样”user:1000:password”。我喜欢对多单词的字段名中加上一个点,就像这样:”comment🔢reply.to”。
Redis Strings
这是最简单Redis类型。如果你只用这种类型,Redis就像一个可以持久化的memcached服务器(注:memcache的数据仅保存在内存中,服务器重启后,数据将丢失)。
我们用redis-cli来玩一下字符串类型:
> set mykey somevalue
OK
> get mykey
"somevalue"
正如你所见到的,通常用SET command 和 GET command来设置和获取字符串值。
值可以是任何种类的字符串(包括二进制数据),例如你可以在一个键下保存一副jpeg图片。值的长度不能超过512 MB。
SET
命令有些有趣的操作,例如,当key存在时SET
会失败,或相反的,当key不存在时它只会成功。
> set mykey newval nx
(nil)
> set mykey newval xx
OK
虽然字符串是Redis的基本值类型,但你仍然能通过它完成一些有趣的操作。例如:原子递增:
> set counter 100
OK
> incr counter
(integer) 101
> incr counter
(integer) 102
> incrby counter 50
(integer) 152
INCR 命令将字符串值解析成整型,将其加一,最后将结果保存为新的字符串值,类似的命令有INCRBY, DECR 和 DECRBY。实际上他们在内部就是同一个命令,只是看上去有点儿不同。
INCR是原子操作意味着什么呢?就是说即使多个客户端对同一个key发出INCR命令,也决不会导致竞争的情况。例如如下情况永远不可能发生:『客户端1和客户端2同时读出“10”,他们俩都对其加到11,然后将新值设置为11』。最终的值一定是12,read-increment-set操作完成时,其他客户端不会在同一时间执行任何命令。
对字符串,另一个的令人感兴趣的操作是GETSET命令,行如其名:他为key设置新值并且返回原值。这有什么用处呢?例如:你的系统每当有新用户访问时就用INCR命令操作一个Redis key。你希望每小时对这个信息收集一次。你就可以GETSET这个key并给其赋值0并读取原值。
为减少等待时间,也可以一次存储或获取多个key对应的值,使用MSET和MGET命令:
> mset a 10 b 20 c 30
OK
> mget a b c
1) "10"
2) "20"
3) "30"
MGET 命令返回由值组成的数组。
修改或查询键空间
有些指令不是针对任何具体的类型定义的,而是用于和整个键空间交互的。因此,它们可被用于任何类型的键。
使用EXISTS命令返回1或0标识给定key的值是否存在,使用DEL命令可以删除key对应的值,DEL命令返回1或0标识值是被删除(值存在)或者没被删除(key对应的值不存在)。
> set mykey hello
OK
> exists mykey
(integer) 1
> del mykey
(integer) 1
> exists mykey
(integer) 0
TYPE命令可以返回key对应的值的存储类型:
> set mykey x
OK
> type mykey
string
> del mykey
(integer) 1
> type mykey
none
Redis超时:数据在限定时间内存活
在介绍复杂类型前我们先介绍一个与值类型无关的Redis特性:超时。你可以对key设置一个超时时间,当这个时间到达后会被删除。精度可以使用毫秒或秒。
> set key some-value
OK
> expire key 5
(integer) 1
> get key (immediately)
"some-value"
> get key (after some time)
(nil)
上面的例子使用了EXPIRE来设置超时时间(也可以再次调用这个命令来改变超时时间,使用PERSIST命令去除超时时间 )。我们也可以在创建值的时候设置超时时间:
> set key 100 ex 10
OK
> ttl key
(integer) 9
TTL命令用来查看key对应的值剩余存活时间。
Redis lists基于Linked Lists实现。这意味着即使在一个list中有数百万个元素,在头部或尾部添加一个元素的操作,其时间复杂度也是常数级别的。用LPUSH 命令在十个元素的list头部添加新元素,和在千万元素list头部添加新元素的速度相同。
那么,坏消息是什么?在数组实现的list中利用索引访问元素的速度极快,而同样的操作在linked list实现的list上没有那么快。
Redis Lists用linked list实现的原因是:对于数据库系统来说,至关重要的特性是:能非常快的在很大的列表上添加元素。另一个重要因素是,正如你将要看到的:Redis lists能在常数时间取得常数长度。
如果快速访问集合元素很重要,建议使用可排序集合(sorted sets)。可排序集合我们会随后介绍。
Redis lists 入门
LPUSH 命令可向list的左边(头部)添加一个新元素,而RPUSH命令可向list的右边(尾部)添加一个新元素。最后LRANGE 命令可从list中取出一定范围的元素:
> rpush mylist A
(integer) 1
> rpush mylist B
(integer) 2
> lpush mylist first
(integer) 3
> lrange mylist 0 -1
1) "first"
2) "A"
3) "B"
注意:LRANGE 带有两个索引,一定范围的第一个和最后一个元素。这两个索引都可以为负来告知Redis从尾部开始计数,因此-1表示最后一个元素,-2表示list中的倒数第二个元素,以此类推。
上面的所有命令的参数都可变,方便你一次向list存入多个值。
> rpush mylist 1 2 3 4 5 "foo bar"
(integer) 9
> lrange mylist 0 -1
1) "first"
2) "A"
3) "B"
4) "1"
5) "2"
6) "3"
7) "4"
8) "5"
9) "foo bar"
还有一个重要的命令是pop,它从list中删除元素并同时返回删除的值。可以在左边或右边操作。
> rpush mylist a b c
(integer) 3
> rpop mylist
"c"
> rpop mylist
"b"
> rpop mylist
"a"
我们增加了三个元素,并弹出了三个元素,因此,在这最后 列表中的命令序列是空的,没有更多的元素可以被弹出。如果我们尝试弹出另一个元素,这是我们得到的结果:
> rpop mylist
(nil)
当list没有元素时,Redis 返回了一个NULL。
Capped lists
可以使用LTRIM把list从左边截取指定长度。
> rpush mylist 1 2 3 4 5
(integer) 5
> ltrim mylist 0 2
OK
> lrange mylist 0 -1
1) "1"
2) "2"
3) "3"
List上的阻塞操作
可以使用Redis来实现生产者和消费者模型,如使用LPUSH和RPOP来实现该功能。但会遇到这种情景:list是空,这时候消费者就需要轮询来获取数据,这样就会增加redis的访问压力、增加消费端的cpu时间,而很多访问都是无用的。为此redis提供了阻塞式访问 BRPOP 和 BLPOP 命令。 消费者可以在获取数据时指定如果数据不存在阻塞的时间,如果在时限内获得数据则立即返回,如果超时还没有数据则返回null, 0表示一直阻塞。
同时redis还会为所有阻塞的消费者以先后顺序排队。
如需了解详细信息请查看 RPOPLPUSH 和 BRPOPLPUSH。
Redis Hashes —
Redis hash 看起来就像一个 “hash” 的样子,由键值对组成:
> hmset user:1000 username antirez birthyear 1977 verified 1
OK
> hget user:1000 username
"antirez"
> hget user:1000 birthyear
"1977"
> hgetall user:1000
1) "username"
2) "antirez"
3) "birthyear"
4) "1977"
5) "verified"
6) "1"
Hash 便于表示 objects,实际上,你可以放入一个 hash 的域数量实际上没有限制(除了可用内存以外)。所以,你可以在你的应用中以不同的方式使用 hash。
HMSET
指令设置 hash 中的多个域,而 HGET
取回单个域。HMGET
和 HGET
类似,但返回一系列值:
> hmget user:1000 username birthyear no-such-field
1) "antirez"
2) "1977"
3) (nil)
也有一些指令能够对单独的域执行操作,比如 HINCRBY
:相当于+
> hincrby user:1000 birthyear 10
(integer) 1987
> hincrby user:1000 birthyear 10
(integer) 1997
你可以在文档中找到 hash 指令的完整列表。
值得注意的是,小的 hash 被用特殊方式编码,非常节约内存。
Redis Sets —
Redis Set 是 String 的无序排列。SADD
指令把新的元素添加到 set 中。对 set 也可做一些其他的操作,比如测试一个给定的元素是否存在,对不同 set 取交集,并集或差,等等。
> sadd myset 1 2 3
(integer) 3
> smembers myset
1. 3
2. 1
3. 2
现在我已经把三个元素加到我的 set 中,并告诉 Redis 返回所有的元素。可以看到,它们没有被排序 —— Redis 在每次调用时可能按照任意顺序返回元素,因为对于元素的顺序并没有规定。
Redis 有检测成员的指令。一个特定的元素是否存在?
> sismember myset 3
(integer) 1
> sismember myset 30
(integer) 0
“3” 是 set 的一个成员,而 “30” 不是。
Sets 适合用于表示对象间的关系。 例如,我们可以轻易使用 set 来表示标记。
一个简单的建模方式是,对每一个希望标记的对象使用 set。这个 set 包含和对象相关联的标签的 ID。
假设我们想要给新闻打上标签。 假设新闻 ID 1000 被打上了 1,2,5 和 77 四个标签,我们可以使用一个 set 把 tag ID 和新闻条目关联起来:
> sadd news:1000:tags 1 2 5 77
(integer) 4
但是,有时候我可能也会需要相反的关系:所有被打上相同标签的新闻列表:
> sadd tag:1:news 1000
(integer) 1
> sadd tag:2:news 1000
(integer) 1
> sadd tag:5:news 1000
(integer) 1
> sadd tag:77:news 1000
(integer) 1
获取一个对象的所有 tag 是很方便的:
> smembers news:1000:tags
1. 5
2. 1
3. 77
4. 2
注意:在这个例子中,我们假设你有另一个数据结构,比如一个 Redis hash,把标签 ID 对应到标签名称。
使用 Redis 命令行,我们可以轻易实现其它一些有用的操作。比如,我们可能需要一个含有 1, 2, 10, 和 27 标签的对象的列表。我们可以用 SINTER
命令来完成这件事。它获取不同 set 的交集。我们可以用:
> sinter tag:1:news tag:2:news tag:10:news tag:27:news
... results here ...
不光可以取交集,还可以取并集,差集,获取随机元素,等等。
获取一个元素的命令是 SPOP
,它很适合对特定问题建模。比如,要实现一个基于 web 的扑克游戏,你可能需要用 set 来表示一副牌。假设我们用一个字符的前缀来表示不同花色:
> 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
键中的内容,并放入 game:1:deck
键中。
这是通过 SUNIONSTORE
实现的,它通常用于对多个集合取并集,并把结果存入另一个 set 中。但是,因为一个 set 的并集就是它本身,我可以这样复制我的牌:
> sunionstore game:1:deck deck
(integer) 52
现在,我已经准备好给 1 号玩家发五张牌:
> spop game:1:deck
"C6"
> spop game:1:deck
"CQ"
> spop game:1:deck
"D1"
> spop game:1:deck
"CJ"
> spop game:1:deck
"SJ"
使用SCARD显示剩余牌
scard game:1:deck
(integer) 47
当您只需要获取随机元素而不将其从集合中删除时,可以使用适合该任务的SRANDMEMBER命令。 它还具有返回重复元素和非重复元素的功能。
Redis Sorted sets —
Redis 有序集合和集合一样也是string类型元素的集合,且不允许重复的成员。
不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。
有序集合的成员是唯一的,但分数(score)却可以重复。
如果A和B是两个分数不同的元素,则如果A.score是> B.score,则A>B。
如果A和B的分数完全相同,则A字符串在字典上大于B字符串,则A>B。 A和B字符串不能相等,因为排序后的集合只有唯一的元素。
让我们从一个简单的示例开始,添加一些选定的黑客名称作为排序的集合元素,并以其出生年份为“得分”。
> zadd hackers 1940 "Alan Kay"
(integer) 1
> zadd hackers 1957 "Sophie Wilson"
(integer 1)
> zadd hackers 1953 "Richard Stallman"
(integer) 1
> zadd hackers 1949 "Anita Borg"
(integer) 1
> zadd hackers 1965 "Yukihiro Matsumoto"
(integer) 1
> zadd hackers 1914 "Hedy Lamarr"
(integer) 1
> zadd hackers 1916 "Claude Shannon"
(integer) 1
> zadd hackers 1969 "Linus Torvalds"
(integer) 1
> zadd hackers 1912 "Alan Turing"
(integer) 1
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:
> 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"
也可以使用WITHSCORES参数返回分数:
> 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"
在范围内操作
排序集更强大的地方, 它们可以在范围内操作。 让我们获取所有在1950年(含)之前出生的人。 我们使用ZRANGEBYSCORE命令来做到这一点:
zrangebyscore hackers -inf 1950
1) "Alan Turing"
2) "Hedy Lamarr"
3) "Claude Shannon"
4) "Alan Kay"
5) "Anita Borg"
删除指定范围内的元素
ZREMRANGEBYSCORE可能不是最好的命令名称,但是它可能非常有用,并返回已删除元素的数量。
让我们从排序集中删除所有1940年至1960年之间出生的黑客:
> zremrangebyscore hackers 1940 1960
(integer) 4
为排序的集合元素定义的另一个极其有用的操作是get-rank操作。 可以问一个元素在有序元素集中的位置是什么。
zrank hackers "Anita Borg"
(integer) 4
ZREVRANK命令也可用于获得排名,考虑到元素按降序排序。
Lexicographical scores
使用词典范围进行操作的主要命令是ZRANGEBYLEX,ZREVRANGEBYLEX,ZREMRANGEBYLEX和ZLEXCOUNT。
例如,让我们再次添加著名黑客的列表,但是这次对所有元素使用零分:
> 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"
Because of the sorted sets ordering rules, they are already sorted lexicographically:
> 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我们可以求得词典范围:
> zrangebylex hackers [B [P
1) "Claude Shannon"
2) "Hedy Lamarr"
3) "Linus Torvalds"
范围可以是包含(inclusive)或排除(exclusive)(取决于第一个字符),字符串无限和负无限分别用+和-字符串指定。 有关更多信息,请参见文档。
Bitmaps —
Bitmaps 不是实际的数据类型,而是在String类型上定义的一组面向位的操作。 由于字符串是二进制安全Blob,并且最大长度为512 MB,因此它们适合设置多达2 ^ 32个不同的位。
位操作分为两类:固定时间的单个位操作(如将一个位设置为1或0或获取其值),以及对位组的操作,例如计算给定位范围内设置的位的数量 (例如,人口计数)。
位图的最大优点之一是,它们在存储信息时通常可以节省大量空间。 例如,在以增量用户ID表示不同用户的系统中,仅使用512 MB内存就可以记住40亿用户的一位信息(例如,知道用户是否要接收新闻通讯)。
> setbit key 10 1
(integer) 1
> getbit key 10
(integer) 1
> getbit key 11
(integer) 0
SETBIT命令将位号作为其第一个参数,将其设置为1或0的值作为其第二个参数。如果所寻址的位超出当前字符串长度,则该命令将自动放大字符串。
GETBIT只是返回指定索引处的位的值。 超出范围的位(寻址超出存储在目标键中的字符串长度的位)始终被视为零。
在位组上有三个命令:
BITOP在不同的字符串之间执行按位运算。 提供的运算为AND,OR,XOR和NOT。
BITCOUNT执行填充计数,报告设置为1的位数。
BITPOS查找指定值为0或1的第一位。
BITPOS和BITCOUNT都可以在字符串的字节范围内运行,而不是在字符串的整个长度上运行。 以下是BITCOUNT调用的一个简单示例:
> setbit key 0 1
(integer) 0
> setbit key 100 1
(integer) 0
> bitcount key
(integer) 2
HyperLogLogs
HyperLogLog是一种概率数据结构,用于对唯一事物进行计数(从技术上讲,这是指估计集合的基数)。通常,对唯一项目进行计数需要使用与要计数的项目数量成比例的内存量,因为您需要记住过去已经看到的元素,以避免多次对其进行计数。但是,有一组算法会以内存换取精度:在Redis实现的情况下,您得出的带有标准误差的估计度量最终会小于1%。该算法的魔力在于您不再需要使用与所计数项目数量成比例的内存量,而可以使用恒定数量的内存!在最坏的情况下为12k字节,如果您的HyperLogLog(从现在开始将它们称为HLL)看到的元素很少,则少得多。
从概念上讲,HLL API就像使用Set来执行相同的任务。您可以将每个观察到的元素添加到集合中,并使用SCARD检查集合中的元素数量,这是唯一的,因为SADD不会重新添加现有元素。
虽然你并未真正将项目添加到HLL中,但由于数据结构仅包含不包含实际元素的状态,因此API相同:
每次看到新元素时,都可以使用PFADD将其添加到计数中。
到目前为止,每次您要检索添加到PFADD中的唯一元素的当前近似值时,都使用PFCOUNT。
> pfadd hll a b c d
(integer) 1
> pfcount hll
(integer) 4
yperLogLogs
HyperLogLog是一种概率数据结构,用于对唯一事物进行计数(从技术上讲,这是指估计集合的基数)。通常,对唯一项目进行计数需要使用与要计数的项目数量成比例的内存量,因为您需要记住过去已经看到的元素,以避免多次对其进行计数。但是,有一组算法会以内存换取精度:在Redis实现的情况下,您得出的带有标准误差的估计度量最终会小于1%。该算法的魔力在于您不再需要使用与所计数项目数量成比例的内存量,而可以使用恒定数量的内存!在最坏的情况下为12k字节,如果您的HyperLogLog(从现在开始将它们称为HLL)看到的元素很少,则少得多。
从概念上讲,HLL API就像使用Set来执行相同的任务。您可以将每个观察到的元素添加到集合中,并使用SCARD检查集合中的元素数量,这是唯一的,**因为SADD不会重新添加现有元素。**
虽然你并未真正将项目添加到HLL中,但由于数据结构仅包含不包含实际元素的状态,因此API相同:
每次看到新元素时,都可以使用PFADD将其添加到计数中。
到目前为止,每次您要检索添加到PFADD中的唯一元素的当前近似值时,都使用PFCOUNT。
pfadd hll a b c d
(integer) 1
pfcount hll
(integer) 4