主要参考资料:http://wiki.jikexueyuan.com/project/redis-guide/data-type.html
一、redis 安装
1、在官网下载安装包
2、解压安装包
tar -zvxf redis-3.2.8.tar.gz
3、进行编译
进入目录
cd redis-3.2.8;
进行编译
make
4、安装redis
进入src目录
cd src/
编译安装
make install
5、后台运行运、
./redis-server ../redis.conf
数据类型
1、字符创
Redis 字符串是二进制安全的,也就是说,一个 Redis 字符串可以包含任意类型的数据,一个字符创最大512M长度。
Redis 的字符串类型做很事情:
使用 INCR 命令族 (INCR,DECR,INCRBY),将字符串作为原子计数器。
使用 APPEND 命令追加字符串。
使用 GETRANGE 和 SETRANGE 命令,使字符串作为随机访问向量 (vectors)。
编码大量数据到很小的空间,或者使用 GETBIT 和 SETBIT 命令,创建一个基于 Redis 的布隆 (Bloom) 过滤器。
2、列表 (Lists)
Redis 列表仅仅是按照插入顺序排序的字符串列表。可以添加一个元素到 Redis 列表的头部 (左边) 或者尾部 (右边)。
LPUSH 命令用于插入一个元素到列表的头部,RPUSH 命令用于插入一个元素到列表的尾部
当这两个命令操作在一个不存在的键时,将会创建一个新的列表
例子:
lpush mylist test
lpush mylist test2
rpush mylist test3
列表的最大长度是 223-1 个元素 (4294967295,超过 40 亿个元素)。
列表用处
为社交网络时间轴 (timeline) 建模,使用 LPUSH 命令往用户时间轴插入元素,使用 LRANGE 命令获得最近事项。
使用 LPUSH 和 LTRIM 命令创建一个不会超出给定数量元素的列表,只存储最近的 N 个元素。
列表可以用作消息传递原语,例如,众所周知的用于创建后台任务的 Ruby 库 Resque。
你可以用列表做更多的事情,这种数据类型支持很多的命令,包括阻塞命令,如 BLPOP。
3、集合 (Sets)
Redis 集合是没有顺序的字符串集合 (collection)。可以在 O(1) 的时间复杂度添加、删除和测试元素存在与否 。
Redis 集合具有你需要的不允许重复成员的性质。多次加入同一个元素到集合也只会有一个拷贝在其中。实际上,这意味着加入一个元素到集合中并不需要检查元素是否已存在
集合可以在很短的时间内和已经存在的集合一起计算并集,交集和差集。
用处:
你可以使用 Redis 集合追踪唯一性的事情。你想知道访问某篇博客文章的所有唯一 IP 吗?只要 每次页面访问时使用 SADD 命令就可以了。你可以放心,重复的 IP 是不会被插入进来的。
Redis 集合可以表示关系。你可以通过使用集合来表示每个标签,来创建一个标签系统。然后你可以把所有拥有此标签的对象的 ID 通过 SADD 命令,加入到表示这个标签的集合中。你想获得同时拥有三个不同标签的对象的全部 ID 吗?用 SINTER 就可以了。
你可以使用 SPOP 或 SRANDMEMBER 命令来从集合中随机抽取元素
4、哈希 / 散列 (Hashes)
Redis 哈希是字符串字段 (field) 与字符串值之间的映射,所以是表示对象的理想数据类型 (例如:一个用户对象有多个字段,像用户名,姓氏,年龄等等
例子:
HMSET user:1000 username antirez password P1pp0 age 34
HGETALL user:1000
HSET user:1000 password 12345
HGETALL user:1000
拥有少量字段 (少量指的是大约 100) 的哈希会以占用很少存储空间的方式存储,所以你可以在一个很小的 Redis 实例里存储数百万的对象。
由于哈希主要用来表示对象,对象能存储很多元素,所以你可以用哈希来做很多其他的事情。
每个哈希可以存储多达 223-1 个字段值对 (field-value pair)(多于 40 亿个)
5、有序集合 (Sorted sets)
Redis 有序集合和 Redis 集合类似,是非重复字符串集合 (collection)。不同的是,每一个有序集合的成员都有一个关联的分数 (score),用于按照分数高低排序。尽管成员是唯一的,但是分数是可以重复的
对有序集合我们可以通过很快速的方式添加,删除和更新元素 (在和元素数量的对数成正比的时间内)。由于元素是有序的而无需事后排序,你可以通过分数或者排名 (位置) 很快地来获取一个范围内的元素。访问有序集合的中间元素也是很快的,所以你可以使用有序集合作为一个无重复元素,快速访问你想要的一切的聪明列表:有序的元素,快速的存在性测试,快速的访问中间元素!
总之,有序集合可以在很好的性能下,做很多别的数据库无法模拟的事情。
使用有序集合你可以:
例如多人在线游戏排行榜,每次提交一个新的分数,你就使用 ZADD 命令更新。你可以很容易地使用 ZRANGE 命令获取前几名用户,你也可以用 ZRANK 命令,通过给定用户名返回其排行。同时使用 ZRANK 和 ZRANGE 命令可以展示与给定用户相似的用户及其分数。以上这些操作都非常的快。
有序集合常用来索引存储在 Redis 内的数据。例如,假设你有很多表示用户的哈希,你可以使用有序集合,用年龄作为元素的分数,用用户 ID 作为元素值,于是你就可以使用 ZRANGEBYSCORE 命令很快且轻而易举地检索出给定年龄区间的所有用户了。
有序集合或许是最高级的 Redis 数据类型,后续我们会详细介绍可用的有序集合命令,也会详细介绍 Redis 数据类型的更多高级信息。
6、位图 (Bitmaps) 和超重对数 (HyperLogLogs)
Redis 还支持位图和超重对数这两种基于字符串基本类型,但有自己语义的数据类型。
三、详细讲解(上)
Redis 不是一个无格式 (plain) 的键值存储,而是一个支持各种不同类型值的数据结构服务器。这就是说,传统键值存储是关联字符串值到字符串键,但是 Redis 的值不仅仅局限于简单字符串,还可以持有更复杂的数据结构。下面列的是 Redis 支持的所有数据结构
二进制安全 (binary-safe) 的字符串。
列表:按照插入顺序排序的字符串元素 (element) 的集合 (collection)。通常是链表。
集合:唯一的,无序的字符串元素集合。
有序集合:和集合类似,但是每个字符串元素关联了一个称为分数 (score) 的浮点数。元素总是按照分数排序,所以可以检索一个范围的元素 (例如,给我前 10,或者后 10 个元素)。
哈希:由字段 (field) 及其关联的值组成的映射。字段和值都是字符串类型。这非常类似于 Ruby 或 Python 中的哈希 / 散列。
位数组 (位图):使用特殊的命令,把字符串当做位数组来处理:你可以设置或者清除单个位值,统计全部置位为 1 的位个数,寻找第一个复位或者置位的位,等等。
超重对数 (HyperLogLog):这是一个用于估算集合的基数 (cardinality,也称势,译者注) 的概率性数据结构。不要害怕,它比看起来要简单,稍后为你揭晓。
后面的所有例子我们都是使用 redis-cli 工具,这是一个简单而又方便的命令行工具,用于发送命令给 Redis 服务器
1、Redis 键 (Keys)
Redis 键是二进制安全的,这意味着你可以使用任何二进制序列作为键,从像”foo” 这样的字符串到一个 JPEG 文件的内容。空字符串也是合法的键。
关于键的其他一些规则:
不要使用太长的键,例如,不要使用一个 1024 字节的键,不仅是因为内存占用,而且在数据集中查找键时需要多次耗时的键比较。即使手头需要匹配一个很大值的存在性,对其进行哈希 (例如使用 SHA1) 是个不错的主意,尤其是从内存和带宽的角度。
不要使用太短的键。用”u1000flw” 取代”user:1000:followers” 作为键并没有什么实际意义,后者更具有可读性,相对于键对象本身以及值对象来说,增加的空间微乎其微。然而不可否认,短的键会消耗少的内存,你的任务就是要找到平衡点
坚持一种模式 (schema)。例如,”object-type:id” 就不错,就像”user:1000”。点或者横线常用来连接多单词字段,如”comment:1234:reply.to”,或者”comment:1234:reply-to”。
键的最大大小是 512MB
2、Redis 字符串 (Strings)
Redis 字符串是可以关联给 redis 键的最简单值类型。字符串是 Memcached 的唯一数据类型,所以新手使用起来也是很自然的。
由于 Redis 的键也是字符串,当我们使用字符串作为值的时候,我们是将一个字符串映射给另一个字符串。字符串数据类型适用于很多场景,例如,缓存 HTML 片段或者页面。
让我们用 redis-cli 来玩玩字符串类型 (接下来的例子都是使用 redis-cli)。
> set mykey somevalue
OK
> get mykey
"somevalue"
你可以看到,我们使用 SET 和 GET 命令设置和检索字符串值。注意,如果键已经存在,SET 会替换掉该键已经存在的值,哪怕这个键关联的是一个非字符串类型的值。SET 执行的是赋值操作。
值可以是任何类型的字符串 ,例如,你可以存储一个 JPEG 图像。值不能大于 512MB。
SET 命令还有一些以额外的参数形式提供有意思的选项。例如,如果我要求如果键存在 (或刚好相反) 则执行失败,也就是说健不存在才成功:
#如果不存在则可以插入
> set mykey newval nx
(nil)
#如果存在则可以插入
> set mykey newval xx
OK
尽管字符串是 Redis 最基本的值类型,你仍可以执行很多有趣的操作。例如,原子性增长:
> set counter 100 设置counter只为100
OK
> incr counter 增加1
(integer) 101
> incr counter 增加1
(integer) 102
> incrby counter 50 增加50
(integer) 152
INCR 命令将字符串值解析为整数,并增加一,最后赋值后作为新值。还有一些类似的命令 INCRBY,DECR 和 DECRBY。它们以略微不同的方式执行,但其内部都是一样的命令。
为什么说 INCR 命令是原子的?因为即使多个 客户端对同一个键发送 INCR 命令也不会造成竞争条件 (race condition)。例如,一定不会发生客户端 1 和客户端 2 同时读到”10”,都增加到 11,然后设置新值为 11。最后的结果将会一直是 12,读 - 增加 - 写操作在执行时,其他客户端此时不会执行相关命令。
有许多操作字符串的命令。例如,GETSET 命令给键设置一个新值,同时返回旧值。你可以使用这个命令,例如,如果你有一个系统,每当收到一个访问请求就使用 INRC 来增加一个键。你想每隔一个小时收集一次这个信息,而不想漏掉任何一个增长。你可以使用 GETSET,将新值赋值为 0,之后读取其旧值。
在一个命令中一次设置或者检索多个键有利于减少延迟。为此有了 MSET 和 MGET 命令:
> mset a 10 b 20 c 30
OK
> mget a b c
1) "10"
2) "20"
3) "30"
当使用 MSET 时,Redis 返回一个值数组。
改变和查询键空间 (key space):
有一些命令并不定义在特定的类型上,但是对键空间的交互很有用,因此他们能作用在任意键上。
例如,EXISTS 命令返回 1 或者 0,来表示键在数据库中是否存在。另外,DEL 命令删除键及其关联的值,无论值是什么。
> set mykey hello
OK
> exists mykey
(integer) 1
> del mykey
(integer) 1
> exists mykey
(integer) 0
从上面的例子中我们还可以看到,DEL 命令本身也会返回 1 或者 0,无论键是(存在)否(不存在)删除。
有许多键空间相关的命令,但是上面两个命令与 TYPE 命令关系紧密,TYPE 命令返回某个键的值的类型
> set mykey x
OK
> type mykey
string
> del mykey
(integer) 1
> type mykey
none
Redis 过期 (expires):有限生存时间的键
在我们继续更复杂的数据结构之前,我们先抛出一个与类型无关的特性, 称为 Redis 过期 。你可以给键设置超时,也就是一个有限的生存时间。当生存时间到了,键就会自动被销毁,就像用户调用 DEL 命令一样。
快速过一下 Redis 过期的信息:
过期时间可以设置为秒或者毫秒精度。
过期时间分辨率总是 1 毫秒。
过期信息被复制和持久化到磁盘,当 Redis 停止时时间仍然在计算 (也就是说 Redis 保存了过期时间)。
设置过期非常简单:
> set key some-value
OK
> expire key 5
(integer) 1
> get key (immediately)
"some-value"
> get key (after some time) 5秒后执行
(nil)
键在两次 GET 调用期间消失了,因为第二次调用推迟了超过 5 秒。在上面的例子中,我们使用 EXPIRE 命令设置过期 (也可以为一个已经设置过期时间的键设置不同的过期时间,就像 PERSIST 命令可以删除过期时间使键永远存在)。当然我们也可以使用其他 Redis 命令来创建带过期时间的键。例如使用 SET 选项:
> set key 100 ex 10
OK
> ttl key
(integer) 9
上面的例子中设置 10 秒过期的键,值为字符串 100。然后使用 TTL 命令检查键的生存剩余时间。
为了使用毫秒来设置和检查过期,请查看 PEXPIRE 和 PTTL 命令,以及 SET 命令的全部选项。
四、详细讲解(中)
Redis 列表(Lists)
Redis 的列表是使用链表实现的。这意味着,及时你的列表中有上百万个元素,增加一个元素到列表的头部或者尾部的操作都是在常量时间完成。使用 LPUSH 命令增加一个新元素到拥有 10 个元素的列表的头部的速度,与增加到拥有 1000 万个元素的列表的头部是一样的。
缺点又是什么呢?使用索引下标来访问一个数组实现的列表非常快(常量时间),但是访问链表实现列表就没那么快了(与元素索引下标成正比的大量工作)。
Redis 采用链表来实现列表是因为,对于数据库系统来说,快速插入一个元素到一个很长的列表非常重要。另外一个即将描述的优势是,Redis 列表能在常数时间内获得常数长度。
LPUSH 命令从左边 (头部) 添加一个元素到列表,RPUSH 命令从右边(尾部)添加一个元素的列表。LRANGE 命令从列表中提取一个范围内的元素。
如下:
> 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 命令使用两个索引下标,分别是返回的范围的开始和结束元素。两个索引坐标可以是负数,表示从后往前数,所以 - 1 表示最后一个元素,-2 表示倒数第二个元素,等等。
如你所见,RPUSH 添加元素到列表的右边,LPUSH 添加元素到列表的左边。
两个命令都是可变参数命令,也就是说,你可以在一个命令调用中自由的添加多个元素到列表中:
> 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"
定义在 Redis 列表上的一个重要操作是弹出元素。弹出元素指的是从列表中检索元素,并同时将其从列表中清楚的操作。你可以从左边或者右边弹出元素,类似于你可以从列表的两端添加元素
> rpush mylist a b c
(integer) 3
> rpop mylist
"c"
> rpop mylist
"b"
> rpop mylist
"a"
我们添加了三个元素并且又弹出了三个元素,所以这一串命令执行完以后列表是空的,没有元素可以弹出了。如果我们试图再弹出一个元素,就会得到如下结果:
> rpop mylist
(nil)
Redis 返回一个 NULL 值来表明列表中没有元素了。
列表的通用场景(Common use cases)
列表可以完成很多任务,两个有代表性的场景如下:
记住社交网络中用户最近提交的更新。
使用生产者消费者模式来进程间通信,生产者添加项(item)到列表,消费者(通常是 worker)消费项并执行任务。Redis 有专门的列表命令更加可靠和高效的解决这种问题。
例如,两种流行的 Ruby 库 resque 和 sidekiq,都是使用 Redis 列表作为钩子,来实现后台作业 (background jobs)。
流行的 Twitter 社交网络,使用 Redis 列表来存储用户最新的微博 (tweets)。
为了一步一步的描述通用场景,假设你想加速展现照片共享社交网络主页的最近发布的图片列表。
每次用户提交一张新的照片,我们使用 LPUSH 将其 ID 添加到列表。
当用户访问主页时,我们使用 LRANGE 0 9 获取最新的 10 张照片。
上限列表(Capped)
很多时候我们只是想用列表存储最近的项,随便这些项是什么:社交网络更新,日志或者任何其他东西。
Redis 允许使用列表作为一个上限集合,使用 LTRIM 命令仅仅只记住最新的 N 项,丢弃掉所有老的项。
LTRIM 命令类似于 LRANGE,但是不同于展示指定范围的元素,而是将其作为列表新值存储。所有范围外的元素都将被删除。
举个例子你就更清楚了:
> rpush mylist 1 2 3 4 5
(integer) 5
> ltrim mylist 0 2
OK
> lrange mylist 0 -1
1) "1"
2) "2"
3) "3"
上面 LTRIM 命令告诉 Redis 仅仅保存第 0 到 2 个元素,其他的都被抛弃。这可以让你实现一个简单而又有用的模式,一个添加操作和一个修剪操作一起,实现新增一个元素抛弃超出元素。
LPUSH mylist <some element>
LTRIM mylist 0 999
上面的组合增加一个元素到列表中,同时只持有最新的 1000 个元素。使用 LRANGE 命令你可以访问前几个元素而不用记录非常老的数据。
注意:尽管 LRANGE 是一个O(N)时间复杂度的命令,访问列表头尾附近的小范围是常量时间的操作。
列表的阻塞操作 (blocking)
列表有一个特别的特性使得其适合实现队列,通常作为进程间通信系统的积木:阻塞操作。
假设你想往一个进程的列表中添加项,用另一个进程来处理这些项。这就是通常的生产者消费者模式,可以使用以下简单方式实现:
生产者调用 LPUSH 添加项到列表中。
消费者调用 RPOP 从列表提取 / 处理项。
然而有时候列表是空的,没有需要处理的,RPOP 就返回 NULL。所以消费者被强制等待一段时间并重试 RPOP 命令。这称为轮询(polling),由于其具有一些缺点,所以不合适在这种情况下
强制 Redis 和客户端处理无用的命令 (当列表为空时的所有请求都没有执行实际的工作,只会返回 NULL)。
由于工作者受到一个 NULL 后会等待一段时间,这会延迟对项的处理。
于是 Redis 实现了 BRPOP 和 BLPOP 两个命令,它们是当列表为空时 RPOP 和 LPOP 的会阻塞版本:仅当一个新元素被添加到列表时,或者到达了用户的指定超时时间,才返回给调用者。 这个是我们在工作者中调用 BRPOP 的例子:
> brpop tasks 5
1) "tasks"
2) "do_something"
上面的意思是” 等待 tasks 列表中的元素,如果 5 秒后还没有可用元素就返回”。
注意,你可以使用 0 作为超时让其一直等待元素,你也可以指定多个列表而不仅仅只是一个,同时等待多个列表,当第一个列表收到元素后就能得到通知。
关于 BRPOP 的一些注意事项。
客户端按顺序服务:第一个被阻塞等待列表的客户端,将第一个收到其他客户端添加的元素,等等。
与 RPOP 的返回值不同:返回的是一个数组,其中包括键的名字,因为 BRPOP 和 BLPOP 可以阻塞等待多个列表的元素。
如果超时时间到达,返回 NULL。
还有更多你需要知道的关于列表和阻塞选项,建议你阅读下面的页面:
使用 RPOLPUSH 构建更安全的队列和旋转队列。
BRPOPLPUSH 命令是其阻塞变种命令。
自动创建和删除键
到目前为止的例子中,我们还没有在添加元素前创建一个空的列表,也没有删除一个没有元素的空列表。要注意,当列表为空时 Redis 将删除该键,当向一个不存在的列表键(如使用 LPUSH)添加一个元素时,将创建一个空的列表。
这并不只是针对列表,适用于所有 Redis 多元素组成的数据类型,因此适用于集合,有序集合和哈希。
基本上我们可以概括为三条规则:
当我们向聚合(aggregate)数据类型添加一个元素,如果目标键不存在,添加元素前将创建一个空的聚合数据类型。
当我们从聚合数据类型删除一个元素,如果值为空,则键也会被销毁。
调用一个像 LLEN 的只读命令(返回列表的长度),或者一个写命令从空键删除元素,总是产生和操作一个持有空聚合类型值的键一样的结果。
规则 1 的例子:
> del mylist
(integer) 1
> lpush mylist 1 2 3
(integer) 3
然而,我们不能执行一个错误键类型的操作:
> set foo bar
OK
> lpush foo 1 2 3
(error) WRONGTYPE Operation against a key holding the wrong kind of value
> type foo
string
规则 2 的例子:
> lpush mylist 1 2 3
(integer) 3
> exists mylist
(integer) 1
> lpop mylist
"3"
> lpop mylist
"2"
> lpop mylist
"1"
> exists mylist
(integer) 0
当所有元素弹出后,键就不存在了。
规则 3 的例子:
> del mylist
(integer) 0
> llen mylist
(integer) 0
> lpop mylist
(nil)
Redis 哈希/散列 (Hashes)
> 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"
哈希就是字段值对(fields-values pairs)的集合。由于哈希容易表示对象,事实上哈希中的字段的数量并没有限制,所以你可以在你的应用程序以不同的方式来使用哈希。
HMSET 命令为哈希设置多个字段,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
你可以从命令页找到全部哈希命令列表。
值得注意的是,小的哈希 (少量元素,不太大的值) 在内存中以一种特殊的方式编码以高效利用内存
Redis 集合 (Sets)
Redis 集合是无序的字符串集合 (collections)。SADD 命令添加元素到集合。还可以对集合执行很多其他的操作,例如,测试元素是否存在,对多个集合执行交集、并集和差集,等等。
> sadd myset 1 2 3
(integer) 3
> smembers myset
1. 3
2. 1
3. 2
我们向集合总添加了 3 个元素,然后告诉 Redis 返回所有元素。如你所见,他们没有排序,Redis 在每次调用时按随意顺序返回元素,因为没有与用户有任何元素排序协议。
我们有测试成员关系的命令。一个指定的元素存在吗?
> sismember myset 3
(integer) 1
> sismember myset 30
(integer) 0
“3” 是集合中的成员,”30” 则不是。
假设,我们想标记新闻。如果我们的 ID 为 1000 的新闻,被标签 1,2,5 和 77 标记,我们可以有一个这篇新闻被关联标记 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
获取指定对象的标签很简单:
> smembers news:1000:tags
1. 5
2. 1
3. 77
4. 2
例如,我们想获取所有被标签 1,2,10 和 27 同时标记的对象列表。我们可以使用 SINTER 命令实现这个,也就是对不同的集合执行交集。我们只需要:
> sinter tag:1:news tag:2:news
1) "1000"
并不仅仅是交集操作,你也可以执行并集,差集,随机抽取元素操作等等。
抽取一个元素的命令是 SPOP,就方便为很多问题建模。例如,为了实现一个基于 web 的扑克游戏,你可以将你的一副牌表示为集合。假设我们使用一个字符前缀表示(C)lubs 梅花, (D)iamonds 方块,(H)earts 红心,(S)pades 黑桃。
> 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
现在我们为每位选手提供 5 张牌。SPOP 命令删除一个随机元素,返回给客户端,是这个场景下的最佳操作。
然而,如果我们直接对这副牌调用,下一局我们需要再填充一副牌,这个可能不太理想。所以我们一开始要复制一下 deck 键的集合到 game:1:deck 键。
这是通过使用 SUNIONSTORE 命令完成的,这个命令通常对多个集合执行交集,然后把结果存储在另一个集合中。而对单个集合求交集就是其自身,于是我可以这样拷贝我的这副牌:
> sunionstore game:1:deck deck
(integer) 52
现在我们准备好为第一个选手提供 5 张牌:
> 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"
只有一对 jack,不太理想……
现在是时候介绍提供集合中元素数量的命令。这个在集合理论中称为集合的基数(cardinality,也称集合的势),所以相应的 Redis 命令称为 SCARD。
> scard game:1:deck
(integer) 47
数学计算式为:52 - 5 = 47。
当你只需要获得随机元素而不需要从集合中删除,SRANDMEMBER 命令则适合你完成任务。它具有返回重复的和非重复的元素的能力
五、详细讲解(下)
Redis 有序集合 (Sorted sets)
有序集合类似于集合和哈希的混合体的一种数据类型。像集合一样,有序集合由唯一的,不重复的字符串元素组成,在某种意义上,有序集合也就是集合。
集合中的每个元素是无序的,但有序集合中的每个元素都关联了一个浮点值,称为分数(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
如你所见,ZADD 命令类似于 SADD,但是多一个参数(位于添加的元素之前),即分数。ZADD 命令也是可变参数的,所以你可以自由的指定多个分数值对(score-value pairs),尽管上面的例子中并没有使用。
使用排序集合可以很容易返回按照出生年份排序的黑客列表,因为他们已经是排序好的。 实现注意事项:有序集合是通过双端(dual-ported)数据结构实现的,包括跳跃表(skiplist,后续文章会详细介绍,译者注)和哈希表(hashtable),所以我们每次添加元素时 Redis 执行 O(log(N)) 的操作。这还好,但是当我们请求有序元素时,Redis 根本不需要做什么工作,因为已经是全部有序了:
> 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"
范围操作 (ranges)
有序集合远比这些要强大。他们可以在范围上操作。让我们获取 1950 年前出生的所有人。我们使用 ZRANGEBYSCORE 命令来办到:
> zrangebyscore hackers -inf 1950
1) "Alan Turing"
2) "Hedy Lamarr"
3) "Claude Shannon"
4) "Alan Kay"
5) "Anita Borg"
我们要求 Redis 返回分数在负无穷到 1950 之间的所有元素(包括两个极端)。
也可以删除某个范围的元素。让我们从有序集合中删除出生于 1940 年到 1960 年之间的黑客:
> zremrangebyscore hackers 1940 1960
(integer) 4
ZREMRANGEBYSCORE 也许不是最合适的命令名,但是非常有用,返回删除的元素数目。
另一个非常有用的操作是用来获取有序集合中元素排行的操作。也就是可以询问集合中元素的排序位置。
> zrank hackers "Anita Borg"
(integer) 4
ZREVRANK 命令用来按照降序排序返回元素的排行。
字典分数 (Lexicographical scores)
最近的 Redis2.8 版本引入了一个新的特性,假定集合中的元素都具有相同的分数,允许按字典顺序获取范围(元素按照 C 语言中的 memcmp 函数进行比较,因此可以保证没有整理,每个 Redis 实例会有相同的输出)。
操作字典顺序范围的主要命令是 ZRANGEBYLEX,ZREVRANGEBYLEX,ZREMRANGEBYLEX 和 ZLEXCOUNT。例如,我们再次添加我们的著名黑客清单。但是这次为每个元素使用 0 分数:
> 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"
根据有序集合的排序规则,他们已经按照字典顺序排好了:
> 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"
范围可以是包容性的或者排除性的(取决于第一个字符,即开闭区间,译者注),+ 和 - 分别表示正无穷和负无穷。查看该命令的文档获取更详细信息(该文档后续即奉献,译者注)。
这个特性非常重要,因为这允许有序集合作为通用索引。例如,如果你想用一个 128 位无符号整数来索引元素,你需要做的就是使用相同的分数(例如 0)添加元素到有序集合中,元素加上由 128 位大端(big endian)数字组成的 8 字节前缀。由于数字是大端编码,字典顺序排序(原始 raw 字节顺序)其实就是数字顺序,你可以在 128 位空间查询范围,获取元素后抛弃前缀。如果你想在一个更正式的例子中了解这个特性,可以看看 Redis 自动完成范例(后续献上,译者注)。
更新分数:排行榜 (leader boards)
这一部分是开始新的主题前最后一个关于有序集合的内容。有序集合的分数可以随时更新。对一个存在于有序集合中的元素再次调用 ZADD,将会在 O(log(N))时间复杂度更新他的分数 (和位置),所以有序集合适合于经常更新的场合。
由于这个特性,通常的一个使用场景就是排行榜。最典型的应用就是 facebook 游戏,你可以组合使用按分数高低存储用户,以及获取排名的操作,来展示前 N 名的用户以及用户在排行榜上的排行(你是第 4932 名最佳分数)。