Via 由 [ ] 提供

从入门到精通(上)

Redis 不是一个无格式 (plain) 的键值存储,而是一个支持各种不同类型值的数据结构服务器。这就是说,传统键值存储是关联字符串值到字符串键,但是 Redis 的值不仅仅局限于简单字符串,还可以持有更复杂的数据结构。下面列的是 Redis 支持的所有数据结构,后面将逐一介绍:

  • 二进制安全 (binary-safe) 的字符串。
  • 列表:按照插入顺序排序的字符串元素 (element) 的集合 (collection)。通常是链表。
  • 集合:唯一的,无序的字符串元素集合。
  • 有序集合:和集合类似,但是每个字符串元素关联了一个称为分数 (score) 的浮点数。元素总是按照分数排序,所以可以检索一个范围的元素 (例如,给我前 10,或者后 10 个元素)。
  • 哈希:由字段 (field) 及其关联的值组成的映射。字段和值都是字符串类型。这非常类似于 Ruby 或 Python 中的哈希 / 散列。
  • 位数组 (位图):使用特殊的命令,把字符串当做位数组来处理:你可以设置或者清除单个位值,统计全部置位为 1 的位个数,寻找第一个复位或者置位的位,等等。
  • 超重对数 (HyperLogLog):这是一个用于估算集合的基数 (cardinality,也称势,译者注) 的概率性数据结构。不要害怕,它比看起来要简单,稍后为你揭晓。

理解这些数据结构是如何工作的,以及从命令参考手册中选择什么命令来解决实际问题,并不总是一件繁琐的事情,本文档就是学习 Redis 数据结构及其最常用模式的速成班。

后面的所有例子我们都是使用 redis-cli 工具,这是一个简单而又方便的命令行工具,用于发送命令给 Redis 服务器。

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。

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  
OK  
> incr counter  
(integer) 101  
> incr counter  
(integer) 102  
> incrby counter 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)  
(nil)  

键在两次 GET 调用期间消失了,因为第二次调用推迟了超过 5 秒。在上面的例子中,我们使用 EXPIRE 命令设置过期 (也可以为一个已经设置过期时间的键设置不同的过期时间,就像 PERSIST 命令可以删除过期时间使键永远存在)。当然我们也可以使用其他 Redis 命令来创建带过期时间的键。例如使用 SET 选项:

> set key 100 ex 10  
OK  
> ttl key  
(integer) 9  

上面的例子中设置 10 秒过期的键,值为字符串 100。然后使用 TTL 命令检查键的生存剩余时间。

为了使用毫秒来设置和检查过期,请查看 PEXPIRE 和 PTTL 命令,以及 SET 命令的全部选项。