关于 Redis 和 Memcached 的澄清
如果你了解我,就会知道我不是那种认为竞争产品是坏事的人。我其实很乐意用户拥有选择,因此我很少做比较 Redis 与其他技术之类的事情。
不过,同样毋庸置疑的是,为了选对解决方案,用户必须获得正确的信息。
这篇文章源于我读到 Mike Perham(迈克·珀勒姆)发表的一篇博客文章。你可能知道,他是一个名为 Sidekiq 的热门库的作者,而 Sidekiq 恰好使用 Redis 作为后端。所以,我完全不会认为迈克是一个“反对” Redis 的人。然而,在他的博客文章中——你可以在以下 URL 找到:http://www.mikeperham.com/2015/09/24/storing-data-with-redis/——他表示,对于缓存,“你可能应该使用 Memcached,而不是 [Redis]”。所以,迈克确实只是认为 Redis 不适合缓存,并以如下方式论证他的观点:
- Memcached 就是为缓存而设计的。
- 它完全不会执行磁盘 I/O。
- 它是多线程的,可以通过扩展到多核来处理数十万请求。
我会回应上述说法,之后还会提供一些上述句子没有涵盖、但在我看来对大多数缓存用户和使用场景更为相关的其他信息。
Memcached 就是为缓存而设计的:这一点我就不讨论了,因为这算不上论据。我可以说“Redis 就是为缓存而设计的”。所以在这一点上两者完全相同,让我们继续看下一点。
它完全不会执行磁盘 I/O:在 Redis 中,如果你愿意,完全可以禁用磁盘 I/O,从而获得纯内存体验。不过,如果确实需要,你也可以只在准备重启时持久化数据库,例如使用“SHUTDOWN SAVE”。归根结底,即使你完全不用 Redis 的持久化功能,它仍然是一项额外价值。
它是多线程的:这是真的,而我的目标之一就是让 Redis 的 I/O 也采用线程化处理(像 memcached 一样;基本上,memcached 的数据访问本身并不是线程化的)。不过,Redis 尤其是在使用 pipelining(流水线处理) 时,每个线程每秒可以处理数量惊人的请求(在非常密集的流水线处理中,常见数字是 50 万;不使用流水线时约为 100,000 ops/sec)。在典型的缓存场景中,每个 Redis 实例都相同、都作为 master 运行、禁用磁盘操作,并且像“memcached 分片模型”一样由客户端负责分片,此时在每台系统上运行多个 Redis 进程并不糟糕。这样做之后,你得到的是一种 shared-nothing(无共享)多线程架构,因此关键在于每个线程能够处理的操作数量。上次我检查时,Redis 的每个线程至少和 memcached 一样快。实现会随时间变化,所以今天的优势可能属于其中一方或另一方,但我敢打赌它们的性能会非常接近,因为两者都倾向于最大化利用可用资源。memcached 的多线程仍然是一项优势,因为它让使用和管理变得更简单,但我认为这并不是关键部分。
还有一点。迈克谈论的是每秒操作数,却没有说明操作的*质量*。问题在于,在 Redis 和 Memcached 这样的系统中,与实际操作内存数据结构相比,命令分发和 I/O 的成本占主导地位。因此,在 Redis 中,执行一个简单的 GET、SET,或者执行一个复杂的操作(例如 ZRANK),成本大致相同。但从应用层面看,你通过复杂操作所能完成的工作要多得多。也许原本需要获取五个缓存值,现在只需发送一个小型 Lua 脚本即可。因此,这两个系统的实际“可扩展性”有很多维度,而你能够完成的工作量就是其中之一。
在迈克提出的担忧中,我能看到的唯一合理一点是多线程。即使把 Redis 看作 memcached 的替代品,这一点也可以通过运行多个进程来解决;或者干脆只运行一个进程,因为要让一个线程在执行类似 memcached 的操作时达到饱和,会非常非常困难。
真正的差异
现在该谈谈这两个系统之间的*真正*差异了。
内存效率
过去,Memcached 在这方面优于 Redis。对于一个旨在表示简单字符串到字符串字典的系统来说,更容易实现更高效的内存利用。这种差异并不显著,而且我已经大约 5 年没有检查过了,但过去确实能够察觉到差异。
不过,如果考虑一个长期运行进程的内存效率,情况会有些不同。请看下一节。
但要真正评估内存效率,还应该考虑到,Redis 中经过特殊编码的小型聚合值非常节省内存。例如,小整数集合在内部会表示为由 8、16、32 或 64 位整数组成的数组;由于这些整数有序,因此可以使用二分查找,在检查某个整数是否存在时以对数时间访问。
当你使用哈希来表示对象,而不是采用 JSON 时,情况也一样。因此,真正的内存效率必须结合具体使用场景进行评估。
Redis LRU 与 Slab allocator
从内存利用的角度看,Memcached 并不完美。如果你的应用中缓存值的大小会随时间发生剧烈变化,就很可能出现严重的内存碎片,而唯一的解决办法是重启。Redis 在这方面要确定性得多。
此外,Redis 的 LRU(最近最少使用) 最近得到了大量改进,现在已经非常接近真正的 LRU。更多信息可以在这里找到:http://redis.io/topics/lru-cache。如果我的理解正确,memcached 的 LRU 仍然根据其 slab allocator( slab 分配器) 进行过期处理,因此有时其行为可能与真正的 LRU 相去甚远,不过我希望听听专家对此的看法。如果你想测试 Redis LRU,现在可以使用近期版本 Redis 中提供的 redis-cli LRU 测试模式。
智能缓存
如果你想把 Redis 用作缓存,却只是以 memcached 的方式使用它,那你实际上错过了很多东西。在我看来,这是迈克那篇博客文章中最大的错误。人们越来越多地转向 Redis,是因为他们发现可以用更有用的方式表示缓存数据。想保留某个事物最新的 N 个项目?使用有上限的列表。想获取缓存的热度排行?使用有序集合,诸如此类。
持久化与复制
如果你需要这些功能,它们就是非常重要的资产。例如,利用这种模型来扩展、处理巨大的读取负载非常简单。持久化带来的重启能力也是如此,还可以随时间对缓存进行快照,等等。不过,完全可以理解的是,有些使用场景与这两项功能完全无关。我想说的是,确实存在“纯缓存”使用场景,在这些场景中持久化和复制非常重要。
可观测性
Redis 的可观测性非常非常强。它能够详细报告大量内部指标,你可以 SCAN 数据集,观察对象的过期情况,调整 LRU 算法;可以为客户端命名,并在 CLIENT LIST 中查看这些名称;可以使用“MONITOR”调试应用,以及进行许多其他高级操作。我认为这是一个优势。
Lua scripting(Lua 脚本编程)
我认为 Lua 脚本编程在许多缓存使用场景中都能提供很大的帮助。例如,如果你有一个缓存的 JSON 块,可以通过 Lua 命令提取单个字段并将其返回给客户端,而不必传输全部内容(从概念上说,也可以直接使用 Redis 哈希来表示对象,实现同样的效果)。
结论
Memcached 是一款很棒的软件。我曾多次阅读其源代码;它曾给我们的行业带来一场革命。你应该评估一下,对你而言它是否比 Redis 更值得选择。不过,事物必须根据其本来面目进行评估。归根结底,多年来读到迈克的报告以及许多非常相似的报告,还是让我有些恼火。因此,我决定向你展示我的观点。如果你发现任何事实性错误,请联系我,我会根据“EDIT”部分更新这篇博客文章。
随机一篇博客