第1章 - 基础知识

是什么使Redis显得这么特别?Redis具体能解决什么类型的问题?要实际应用Redis,开发者必须储备什么知识?在我们能回答这么一些问题之前,我们需要明白Redis到底是什么。

Redis通常被人们认为是一种持久化的存储器关键字-值型存储(in-memory persistent key-value store)。我认为这种对Redis的描述并不太准确。Redis的确是将所有的数据存放于存储器(更多是是按位存储),而且也确实通过将数据写入磁盘来实现持久化,但是Redis的实际意义比单纯的关键字-值型存储要来得深远。纠正脑海里的这种误解观点非常关键,否则你对于Redis之道以及其应用的洞察力就会变得越发狭义。

事实是,Redis引入了5种不同的数据结构,只有一个是典型的关键字-值型结构。理解Redis的关键就在于搞清楚这5种数据结构,其工作的原理都是如何,有什么关联方法以及你能怎样应用这些数据结构去构建模型。首先,让我们来弄明白这些数据结构的实际意义。

应用上面提及的数据结构概念到我们熟悉的关系型数据库里,我们可以认为其引入了一个单独的数据结构——表格。表格既复杂又灵活,基于表格的存储和管理,没有多少东西是你不能进行建模的。然而,这种通用性并不是没有缺点。具体来说就是,事情并不是总能达到假设中的简单或者快速。相对于这种普遍适用(one-size-fits-all)的结构体系,我们可以使用更为专门化的结构体系。当然,因此可能有些事情我们会完成不了(至少,达不到很好的程度)。但话说回来,这样做就能确定我们可以获得想象中的简单性和速度吗?

针对特定类型的问题使用特定的数据结构?我们不就是这样进行编程的吗?你不会使用一个散列表去存储每份数据,也不会使用一个标量变量去存储。对我来说,这正是Redis的做法。如果你需要处理标量、列表、散列或者集合,为什么不直接就用标量、列表、散列和集合去存储他们?为什么不是直接调用exists(key)去检测一个已存在的值,而是要调用其他比O(1)(常量时间查找,不会因为待处理元素的增长而变慢)慢的操作?

数据库(Databases)

与你熟悉的关系型数据库一致,Redis有着相同的数据库基本概念,即一个数据库包含一组数据。典型的数据库应用案例是,将一个程序的所有数据组织起来,使之与另一个程序的数据保持独立。

在Redis里,数据库简单的使用一个数字编号来进行辨认,默认数据库的数字编号是0。如果你想切换到一个不同的数据库,你可以使用select命令来实现。在命令行界面里键入select 1,Redis应该会回复一条OK的信息,然后命令行界面里的提示符会变成类似redis 127.0.0.1:6379[1]>这样。如果你想切换回默认数据库,只要在命令行界面键入select 0即可。

命令、关键字和值(Commands, Keys and Values)

Redis不仅仅是一种简单的关键字-值型存储,从其核心概念来看,Redis的5种数据结构中的每一个都至少有一个关键字和一个值。在转入其它关于Redis的有用信息之前,我们必须理解关键字和值的概念。

关键字(Keys)是用来标识数据块。我们将会很常跟关键字打交道,不过在现在,明白关键字就是类似于users:leto这样的表述就足够了。一般都能很好地理解到,这样关键字包含的信息是一个名为leto的用户。这个关键字里的冒号没有任何特殊含义,对于Redis而言,使用分隔符来组织关键字是很常见的方法。

值(Values)是关联于关键字的实际值,可以是任何东西。有时候你会存储字符串,有时候是整数,还有时候你会存储序列化对象(使用JSON、XML或其他格式)。在大多数情况下,Redis会把值看做是一个字节序列,而不会关注它们实质上是什么。要注意,不同的Redis载体处理序列化会有所不同(一些会让你自己决定)。因此,在这本书里,我们将仅讨论字符串、整数和JSON。

现在让我们活动一下手指吧。在命令行界面键入下面的命令:

  1. set users:leto "{name: leto, planet: dune, likes: [spice]}"

这就是Redis命令的基本构成。首先我们要有一个确定的命令,在上面的语句里就是set。然后就是相应的参数,set命令接受两个参数,包括要设置的关键字,以及相应要设置的值。很多的情况是,命令接受一个关键字(当这种情况出现,其经常是第一个参数)。你能想到如何去获取这个值吗?我想你会说(当然一时拿不准也没什么):

  1. get users:leto

关键字和值的是Redis的基本概念,而getset命令是对此最简单的使用。你可以创建更多的用户,去尝试不同类型的关键字以及不同的值,看看一些不同的组合。

查询(Querying)

随着学习的持续深入,两件事情将变得清晰起来。对于Redis而言,关键字就是一切,而值是没有任何意义。更通俗来看就是,Redis不允许你通过值来进行查询。回到上面的例子,我们就不能查询生活在dune行星上的用户。

对许多人来说,这会引起一些担忧。在我们生活的世界里,数据查询是如此的灵活和强大,而Redis的方式看起来是这么的原始和不高效。不要让这些扰乱你太久。要记住,Redis不是一种普遍使用(one-size-fits-all)的解决方案,确实存在这么一些事情是不应该由Redis来解决的(因为其查询的限制)。事实上,在考虑了这些情况后,你会找到新的方法去构建你的数据。

很快,我们就能看到更多实际的用例。很重要的一点是,我们要明白关于Redis的这些基本事实。这能帮助我们弄清楚为什么值可以是任何东西,因为Redis从来不需要去读取或理解它们。而且,这也可以帮助我们理清思路,然后去思考如何在这个新世界里建立模型。

存储器和持久化(Memory and Persistence)

我们之前提及过,Redis是一种持久化的存储器内存储(in-memory persistent store)。对于持久化,默认情况下,Redis会根据已变更的关键字数量来进行判断,然后在磁盘里创建数据库的快照(snapshot)。你可以对此进行设置,如果X个关键字已变更,那么每隔Y秒存储数据库一次。默认情况下,如果1000个或更多的关键字已变更,Redis会每隔60秒存储数据库;而如果9个或更少的关键字已变更,Redis会每隔15分钟存储数据库。

除了创建磁盘快照外,Redis可以在附加模式下运行。任何时候,如果有一个关键字变更,一个单一附加(append-only)的文件会在磁盘里进行更新。在一些情况里,虽然硬件或软件可能发生错误,但用那60秒有效数据存储去换取更好性能是可以接受的。而在另一些情况里,这种损失就难以让人接受,Redis为你提供了选择。在第5章里,我们将会看到第三种选择,其将持久化任务减荷到一个从属数据库里。

至于存储器,Redis会将所有数据都保留在存储器中。显而易见,运行Redis具有不低的成本:因为RAM仍然是最昂贵的服务器硬件部件。

我很清楚有一些开发者对即使是一点点的数据空间都是那么的敏感。一本《威廉·莎士比亚全集》需要近5.5MB的存储空间。对于缩放的需求,其它的解决方案趋向于IO-bound或者CPU-bound。这些限制(RAM或者IO)将会需要你去理解更多机器实际依赖的数据类型,以及应该如何去进行存储和查询。除非你是存储大容量的多媒体文件到Redis中,否则存储器内存储应该不会是一个问题。如果这对于一个程序是个问题,你就很可能不会用IO-bound的解决方案。

Redis有虚拟存储器的支持。然而,这个功能已经被认为是失败的了(通过Redis的开发者),而且它的使用已经被废弃了。

(从另一个角度来看,一本5.5MB的《威廉·莎士比亚全集》可以通过压缩减小到近2MB。当然,Redis不会自动对值进行压缩,但是因为其将所有值都看作是字节,没有什么限制让你不能对数据进行压缩/解压,通过牺牲处理时间来换取存储空间。)

整体来看(Putting It Together)

我们已经接触了好几个高层次的主题。在继续深入Redis之前,我想做的最后一件事情是将这些主题整合起来。这些主题包括,查询的限制,数据结构以及Redis在存储器内存储数据的方法。

当你将这3个主题整合起来,你最终会得出一个绝妙的结论:速度。一些人可能会想,当然Redis会很快速,要知道所有的东西都在存储器里。但这仅仅是其中的一部分,让Redis闪耀的真正原因是其不同于其它解决方案的特殊数据结构。

能有多快速?这依赖于很多东西,包括你正在使用着哪个命令,数据的类型等等。但Redis的性能测试是趋向于数万或数十万次操作每秒。你可以通过运行redis-benchmark(就在redis-serverredis-cli的同一个文件夹里)来进行测试。

我曾经试过将一组使用传统模型的代码转向使用Redis。在传统模型里,运行一个我写的载入测试,需要超过5分钟的时间来完成。而在Redis里,只需要150毫秒就完成了。你不会总能得到这么好的收获,但希望这能让你对我们所谈的东西有更清晰的理解。

理解Redis的这个特性很重要,因为这将影响到你如何去与Redis进行交互。拥有SQL背景的程序员通常会致力于让数据库的数据往返次数减至最小。这对于任何系统都是个好建议,包括Redis。然而,考虑到我们是在处理比较简单的数据结构,有时候我们还是需要与Redis服务器频繁交互,以达到我们的目的。刚开始的时候,可能会对这种数据访问模式感到不太自然。实际上,相对于我们通过Redis获得的高性能而言,这仅仅是微不足道的损失。

小结

虽然我们只接触和摆弄了Redis的冰山一角,但我们讨论的主题已然覆盖了很大范围内的东西。如果觉得有些事情还是不太清楚(例如查询),不用为此而担心,在下一章我们将会继续深入探讨,希望你的问题都能得到解答。

这一章的要点包括:

  • 关键字(Keys)是用于标识一段数据的一个字符串

  • 值(Values)是一段任意的字节序列,Redis不会关注它们实质上是什么

  • Redis展示了(也实现了)5种专门的数据结构

  • 上面的几点使得Redis快速而且容易使用,但要知道Redis并不适用于所有的应用场景