在熔炉 - Redis

作者:Reuven M. Lerner

过去几个月,我一直在介绍非关系型数据库,有时也称为 NoSQL 数据库。对于铁杆 NoSQL 支持者来说,关系型数据库不再是数据存储的最终解决方案。相反,NoSQL 系统具有灵活性、易于复制和使用现代数据结构进行存储等优点,是未来的发展方向,甚至可能是现在的方向。

大多数 NoSQL 拥护者并不完全如此极端,而是指出 NoSQL 是解决相对较新的问题的有用方案,例如具有大量负载的网站所面临的问题。对他们(和我)而言,NoSQL 数据库提供了相当于新数据结构的存储。您可以使用整数、字符串和数组来构建程序,但是通过在您的武器库中添加哈希表,您的代码将变得更易于编写和维护。同样,拥有额外的存储机制可以提高软件的质量、效率和可维护性。

NoSQL 是一个在过去一两年中像野火一样蔓延的流行语,但是这是一个有问题的短语,因为它描述了这些数据库不是什么,而不是它们是什么。实际上,存在许多不同类型的 NoSQL 数据库。在过去的几个月中,我在本专栏中探讨了两种数据库:MongoDB 和 CouchDB。这两种都是“文档”数据库——它们存储名称-值对的集合,很像 Ruby 哈希或 Python 字典。

另一种类型的 NoSQL 数据库是键值存储。您可以将文档数据库视为包含多个哈希表,而键值存储则相当于单个哈希表。顾名思义,键值存储允许存储由单个键标识的单个值(可能是聚合数据结构,例如数组或哈希表)。

文档数据库或键值存储哪个更适合您的应用程序在很大程度上取决于您的需求。我最近重写了我的博士论文软件的一部分,该软件以前使用 PostgreSQL 进行所有后端存储,现在改用 PostgreSQL 和 MongoDB 的组合。我选择 MongoDB 是因为我需要使用各种字段和字段组合来检索文档。每个文档的单个键是不够的。

在另一个案例中,在我一直在从事的金融应用程序中,我需要快速访问许多货币对的最新汇率。因为我将仅基于单个唯一键(即货币对的六个字母表示形式)检索数据,所以使用文档数据库会导致不必要的开销。我感兴趣的只是存储货币对的当前汇率或检索该货币对的当前汇率,这与键值存储完美匹配。

因此,我花了一些时间研究键值存储,并决定使用 Redis,这是一个开源键值存储,最初由意大利程序员 Salvatore Sanfilippo 开发,他被 VMware 聘用全职从事 Redis 的工作。Redis 于 2009 年 2 月发布,但由于其惊人的速度,它迅速吸引了大量追随者。

在许多方面,Redis 类似于 memcached,memcached 是另一种键值存储,在扩展 Web 应用程序中很受欢迎。与 memcached 一样,Redis 将键和值存储在 RAM 中。与 memcached 一样,Redis 速度非常快。与 memcached 一样,Redis 具有以多种语言编写的绑定和客户端。

但是,存在重大差异。Redis 可以存储和操作大量数据结构(例如列表、集合和哈希)。Redis 将值存储在 RAM 中,但会定期异步地将其写入磁盘。这意味着,如果有人拔掉计算机的电源插头,您只会丢失自上次 Redis 保存以来添加的项目。当您下次启动 Redis 时,所有其他内容都将读入 RAM 并以通常的方式提供。

而且,我提到 Redis 速度很快吗?听到人们谈论 Redis 每秒获得数万次读取和写入是很常见的。

下载和安装

现在我已经介绍了 Redis,让我们尝试安装它。在大多数现代 Linux 发行版上,您应该能够通过 apt-get 或 yum 安装 Redis(通常作为软件包 redis-server)。但是,请注意版本号。我的运行 Ubuntu 9.10 的 Linux 服务器很高兴地为我安装了一个非常旧版本的 Redis。我卸载了它,并从 Redis 主页(请参阅“资源”)下载了它。

如果您下载源代码,您可能会惊讶地发现没有 configure 脚本。相反,您只需运行make来编译 Redis。完成后,您可以手动将程序(尤其是 redis-server)安装到适当的目录中,例如 /usr/local/bin。不要忘记将 Redis 配置文件 redis.conf 安装到适当的位置,例如 /etc。要启动,请说

/usr/local/bin/redis-server /etc/redis.conf

这告诉 Redis 启动并从 /etc/redis.conf 读取其配置。配置文件易于阅读和修改,您应该在有机会时查看一下。如果您只是有兴趣开始使用 Redis 而不关心摆弄控件,则可以这样做。默认配置对于大多数基本用途都非常有效。

可能最受关注的配置设置是“daemonize”,它指示 Redis 是否应将自身置于后台。当我第一次开始使用 Redis 时,我将 Redis 保留在前台(并启用 debug-level 日志记录),但是当我最终将其投入生产时,我打开了 daemonize,这样我就不会在系统使用时收到大量日志和更新消息。

另一个配置设置指示 Redis 应多久将其状态保存到磁盘。我的安装随附的默认配置参数如下所示

save 900 1
save 300 10
save 60 10000

这意味着,如果发生一次更改,Redis 应每 900 秒保存一次其状态;如果发生十次更改,则每 300 秒保存一次;如果发生 10,000 次更改,则每 60 秒保存一次。Redis 异步保存到磁盘,因此在执行保存操作时不会有显着减速的危险。

您可以根据您的特定应用程序的需求更改这些设置,从而在服务器关闭时您愿意丢失多少数据与对高性能的需求之间取得适当的平衡。Redis 附带了一个单独的程序 redis-benchmark,它使您可以了解在您的特定硬件上,使用您已配置的配置选项,您可以期望每秒执行多少次读取和写入操作。

默认情况下,Redis 侦听端口 6379。您可以通过 telnet 或使用随附的 redis-cli 程序在本地连接到它,该程序使您可以与 Redis 服务器交互。

使用 Redis

现在您有了一个 Redis 服务器,您如何使用它呢?一种简单的方法是使用命令行界面,它以名为 redis-cli 的程序形式出现。如果您愿意,可以使用编程语言,它将协议隐藏在一组对象和方法之后,但是我见过的大多数库都使用与底层 Redis 协议相同的方法名称。

Redis 中两个最基本的命令是 GET 和 SET,它们分别检索和设置值。SET 接受两个参数:键和值,而 GET 接受单个参数

redis> GET name
(nil)
redis> SET name reuven
OK
redis> GET name
reuven
redis> GET Name
(nil)

从这个例子中,您可以看到几件事。首先,如果您检索尚未设置的键,Redis 将返回 nil 值。其次,键是区分大小写的,因此“name”与“Name”不同。如果您使用名称或电子邮件地址作为 Redis 数据库中的键,这可能很重要,因此请小心!最后,您可以看到 Redis 将所有内容都存储为字符串,至少当您以这种方式存储内容时是这样,因此您无需在值周围加上引号,除非它们包含引号。

协议的性质意味着您的键可能不包含空格字符。我在某处读到,此限制可能会在某个时候解除。但是,为了与旧版本的 Redis 兼容,您可能需要对键命名约定保持保守。除此之外,您可以自由地在键和值中使用任何您想要的字符。

附加功能

如果 Redis 只能做到这些,您可能会认为它是一个超级 memcached,它可以定期将其状态保存到磁盘。毕竟,memcached 也是一个键值存储,它将数据保存在 RAM 中并且速度非常快。

但是,Redis 在服务器上提供了许多超出 memcached 提供的功能。例如,有一个 setnx 命令,它为特定键设置一个新值,但前提是该值尚不存在。换句话说,这是一个测试并设置功能,使您可以确信您不会覆盖现有的重要数据。例如

redis> setnx name Kermit
(integer) 0
redis> get name
reuven

您还可以要求 Redis 为您递增和递减计数器。例如

redis> set counter 10
OK
redis> incr counter
(integer) 11
redis> incr counter
(integer) 12
redis> decr counter
(integer) 11
redis> decr counter
(integer) 10

Redis 还提供了一组丰富的功能;它允许您存储和操作列表。列表的存储和检索使用与 GET 和 SET 不同的命令集,这在我刚开始使用它时让我感到困惑,但是随着时间的推移,它变得更加自然。您可以使用一些简单的命令创建列表、向列表添加成员和从列表中删除成员

redis> lpush atflist first
OK
redis> lpush atflist next
OK
redis> rpush atflist last
OK
redis> lrange atflist 0 -1
1. next
2. first
3. last
redis> lindex atflist 2
last

lpush 和 rpush 命令分别在列表的左侧或右侧添加元素(或创建列表,如果列表尚不存在)。它们类似于 lpop 和 rpop 命令,后者从列表的指定侧删除元素。lrange 命令允许您列出列表中从特定索引到另一个索引的所有元素。如果您将 -1 作为结束索引,您将获得返回给您的整个列表。最后,您可以使用 lindex 检索特定索引处的元素。

尽管此时可能不明显,但 Redis 是严格类型的。这意味着尝试将列表作为标量值检索,反之亦然,将导致错误

redis> get atflist
(error) ERR Operation against a key holding the wrong kind of value
redis> lpush name lerner
(error) ERR Operation against a key holding the wrong kind of value

因此,在使用 Redis 时,记住每个键值对的类型非常重要。

与列表相关但用途不同的是集合。您可以使用以下命令向集合添加项目sadd,使用以下命令获取成员列表smembers,并使用以下命令查找集合的长度(“基数”)scard:

redis> sadd children atara
(integer) 1
redis> smembers children
1. atara
redis> sadd children shikma
(integer) 1
redis> sadd children amotz
(integer) 1
redis> smembers children
1. amotz
2. shikma
3. atara
redis> sadd children amotz
(integer) 0
redis> scard children
(integer) 3

正如您从上面的示例中看到的,向集合添加元素通常会导致响应 1,表明该元素已添加。但是,集合的每个元素在集合中都必须是唯一的;不允许重复。如果您尝试重新添加已存在于集合中的元素,Redis 会响应 0,表明无需添加该元素。与 Redis 的所有其他部分一样,集合是区分大小写的,因此,如果您尝试添加相同的名称,但大小写不同,则操作将成功。

Redis 提供了用于处理集合的功能,例如并集和交集。一种可能的用途是在网站上的社交标签中。每个 URL 都可以是集合的名称,并且该集合可以包含应用于该 URL 的所有社交标签。然后,您可以找到已应用于两个不同 URL 的标签,而无需在应用程序级别检索和计算它。

Redis 还提供了排序集,它与您到目前为止看到的集合相同,但是它们将项目保持在可以修改的特定顺序(或“排名”)中。

最新版本的 Redis 现在支持哈希表。(当您阅读本文时,Redis 2.0 可能已经发布,并完全支持此功能。)这似乎有点奇怪,因为您可以将 Redis 视为一个大型哈希表,但这意味着您可以在 Redis 中存储多个哈希表。哈希表函数都以 h 开头,并提供与您在主 Redis 存储机制中看到的相同类型的设置、获取和测试功能。

最后,最新版本还提供了“multi-exec”功能,使您可以在单个原子操作中执行多个命令。这与您从关系数据库中了解的事务不太相同,但是它在很大程度上实现了此类功能,使 Redis 不仅对基本的键值操作具有吸引力,而且对更复杂的操作也具有吸引力。

Redis 适合所有人吗?

在阅读了大量好评之后,我研究了 Redis,并且我期望发现它的严重问题。到目前为止,我还没有发现任何问题。的确,我发现自己是其热情的支持者之一。也就是说,我之所以喜欢使用 Redis,是因为我在适合它的地方使用它。我可以处理自上次检查点以来存储的数据丢失。我正在存储的数据很容易放入 Redis 的数据结构中,并且我正在存储的数据适合我服务器的可用 RAM 中。此外,还有一个出色的 Ruby 库可用于使用 Redis,这使我可以无缝且轻松地将其集成到我的工作中。

也就是说,Redis 并不适合所有人。如果您要存储多级哈希表,或者如果您无法承受服务器关闭时哪怕是瞬间的数据丢失,或者如果您希望将数据跨主服务器复制(而不是主从复制,Redis 可以轻松处理),您可能需要考虑其他解决方案,例如 Cassandra。但是到目前为止,Redis 在我的工作中给我留下了深刻的印象并让我感到高兴,而且据我所知,我并不是唯一有这种感觉的人。

结论

如果您需要一个高速存储或缓存系统,该系统提供 memcached 所能提供的所有功能以及更多功能,那么您可能应该看看 Redis。它易于安装、性能高,并且在每种主要的编程语言中都有客户端库。Redis 已在众多应用程序(包括许多网站)中投入生产使用一年多,其用户继续对其功能和性能赞不绝口。即使您现在不需要键值存储,也可能值得安装并试用 Redis。如果经过几分钟的实验后,您会想到一些以前没有考虑过的用途,我不会感到惊讶。

资源

Redis 的主页以及您可以从中下载最新源代码的网站是 code.google.com/p/redis。此页面包含大量指向 Redis 用户教程和库的链接,其中大多数至少值得快速浏览一下。

Engine Yard(一家 Ruby 托管公司)的 Kirk Hanes 对 Redis 的良好介绍,以及如何从 Ruby 程序中使用它是 www.engineyard.com/blog/2009/key-value-stores-for-ruby-part-4-to-redis-or-not-to-redis

最后,Mason Jones 在他的 GitHub 页面上提供了一个有用的 Redis 协议备忘单,包括最新的新增功能,例如哈希表和 multi-exec:github.com/masonoise/redis-cheatsheet。(考虑到 GitHub 是 Redis 的重度用户,这是合适的。)

Reuven M. Lerner 是一位长期的 Web 开发人员、架构师和培训师。他是西北大学学习科学专业的博士候选人,研究协作在线社区的设计和分析。Reuven 与他的妻子和三个孩子住在以色列的莫迪因。

加载 Disqus 评论