原文:https://dev.to/adamthedeveloper/redis-vs-dragonfly-a-hands-on-comparison-22ko(作者 @adamthedeveloper)
我印象里,几乎每一个后端项目我都用过 Redis:缓存、会话、限流、队列,还有那些塞进 Postgres 里实在别扭的临时状态。到后来,选 Redis 已经不再是一个决策,而成了默认动作。
大约三个月前,我接触到 Dragonfly——它保留了 Redis 的 API,但底层实现完全不同。我实在好奇,就把两者放在同样的负载上跑了一遍,看看会有什么结果。这篇文章讲我看到的现象、我搞明白的两者的实现思路,以及我认为各自适合什么场景。
声明:本文非赞助内容,我与 DragonflyDB 没有任何关联。写它只是因为试用之后确实被 Dragonfly 打动了,它的表现对我来说非常好。它是不是适合你,仍然取决于你的负载,所以请把它当作一份评估参考,而不是换库建议。
先简单说说 Redis 是什么
把 Redis 只当成缓存,是低估了它。这些年我用它做过会话、计数器、分布式锁、基于有序集合的排行榜、发布订阅、Stream,还有普通队列。它之所以在这些场景下都好用,是因为它提供了实用的数据结构,而且操作极其便宜——一条命令通常就是一次调用:
GET user:123 INCR rate_limit:user:123 ZADD leaderboard 9000 adam
不需要解析 SQL,也没有查询规划器参与。这种简单正是它快的一个重要原因。
那么,Redis 为什么是单线程的?
Redis 在一个主线程上执行命令,这经常被描述成一种缺陷。我认为这种说法低估了当年的设计。
Redis 诞生于 2009 年。当时的服务器顶多也就几个核心,目标是在那样的硬件上做一个简单、可预测、足够快的数据存储。单执行线程给它带来了这些好处:
* 数据结构不需要加锁。
* 命令逐个执行,原子性很容易推理。
* 行为可预测,调试起来更省事。
* 代码库保持小巧、易于理解。
以当时的硬件条件看,这是个合理的设计,而且放到今天依然站得住脚。Node.js 是个不错的参照:它基本也是单线程,这并没有让它变慢,还避开了一整类并发 bug。
Redis 也一直在演进。它从 6.0 起支持 I/O 线程,Redis 8 又进一步加强了这点:网络工作可以分散到多个线程,而命令执行仍然串行,既保留了核心模型,又削弱了最明显的瓶颈。
放在它当初所围绕的那些约束之下,Redis 的设计是优秀的。只不过那些约束后来变了,这正是 Dragonfly 出现的地方。
Dragonfly 的做法有什么不同
Dragonfly 的出发点是另一个假设:现在的服务器有 16 核、32 核甚至更多,而单执行线程让其中大多数核心闲置。它采用多线程、shared-nothing 架构:数据集被切分成多个 shard,每个 shard 由一个线程独占,请求会被路由到持有对应数据的那个线程。
Redis
Requests --> [ Main thread ] --> [ Data ]
Dragonfly
Requests --> [ Thread 1 ] --> [ Shard A ]
--> [ Thread 2 ] --> [ Shard B ]
--> [ Thread 3 ] --> [ Shard C ]
真实实现要复杂得多,尤其是跨多个 shard 的操作,但大致思路就是这样。收益来自把工作摊到机器上的每一个核心。
实际跑一下
我第一个好感来自启动有多省事。Dragonfly 兼容 Redis 协议,所以我把现有的连接串直接指过去,客户端库一行没改就能用。
这篇文里我没有跑什么复杂的基准测试套件。说实话,你本来就该拿 你自己的负载 去测。合成的 GET/SET 基准测试,几乎说明不了 Dragonfly 能不能改善你的实际应用。
真正有意思的是:当你的 Redis 负载变成 CPU 密集或内存受限时会发生什么。Dragonfly 的多线程架构和内存效率正是在这种时候开始起作用。
如果你的 Redis 实例连一个核心都用不满,也没有内存压力,那可能没什么换的理由。
这也没什么问题。不是每一样基础设施都只因为出现了更新的东西就非换不可。
Dragonfly 还有哪些短板
最初的兴奋劲过去之后,我把 Dragonfly 的文档和 GitHub issue 翻了一遍,看看哪些地方可能出问题。下面是最显眼的几处。这些都不足以让我放弃 Dragonfly,但在把真实数据放上去之前,都值得先知道。
持久化只有快照
Redis 提供 RDB 快照和 AOF 日志两种方式。Dragonfly 的文档写得很直白:不支持 AOF,给出的理由是社区里没有高优先级的需求。如果你要求每一次确认写入都能在崩溃后存活,这一点就很要紧。
对部分用户来说,快照本身也有问题。有一个 GitHub issue 描述了快照上传 S3 静默停了一周,随后一次崩溃让系统恢复出了一周前的数据。该 issue 被作为另一个 issue 的重复关闭,从讨论串里也看不出根因是什么,所以姑且当作一个个例来看。
Lua 脚本需要多留心
Dragonfly 使用 Lua 5.4,而 Redis 用的是 Lua 5.1。大多数脚本照样能跑,但语言版本变了,值得专门测一遍。
差别更大的是 key 的处理方式。默认情况下,Dragonfly 会拒绝访问未声明 key 的脚本,并返回相应报错。可以用 allow-undeclared-keys 标志放开,但文档警告说,这样一来脚本执行期间 Dragonfly 必须停掉其他所有操作。如果要迁移,动态拼 key 名的脚本是重点盯防对象。
多 key 操作可用,但有协调开销
我原本以为多 key 命令会是弱项,毕竟数据分布在不同线程上。Dragonfly 用一套基于 VLL 算法的事务框架来处理,官方称这能实现不依赖互斥锁和自旋锁的多 key 原子操作。所以原子性是保住的。
代价是协调开销。按团队自己那篇事务实现解析,多 key 操作要拿一个分片锁,持锁期间该分片上的其他事务都得等着。单线程的 Redis 不需要付这笔成本。我没有实测它到底影响多大,所以如果你的负载重度依赖大型多 key 命令或脚本,就针对这条路径专门测。
集群的玩法不一样
Dragonfly 的集群模式在设计上对 Redis Cluster 客户端呈现为同样的接口,但它不像 Redis Cluster 那样自我管理。Redis Cluster 里,节点之间互相通信来发现集群状态;Dragonfly Cluster 用的是集中式管理,部署和运维方式都不一样。如果你的计划是跑几十个节点,Redis Cluster 的历史积累更长。
模块
Dragonfly 自己的对比指南也指出,部分高级 Redis 模块可能尚未完全兼容。如果你的技术栈依赖 search 或 time series 之类的模块,规划迁移前先核对你的具体使用场景。
Bug
Dragonfly 是个更年轻的项目,issue 列表能看出来。以下是发行说明里的几个例子,均已标记修复:
* 一个旧版本副本重连到被提升的主节点时,主节点崩溃(#7491)。
* 惰性过期字段上执行 HEXPIRE、HSETEX 之类的哈希字段过期命令时,复制数据出现分歧(#7948)。
* Lua 脚本对大字符串值调用 GET 时崩溃(#7934)。
* 命令合并(command squashing)中的跨线程数据竞争(#7927)。
也有一些尚未关闭或未解决的用户报告,比如 v1.28.0 在 31 GiB 数据集上压测期间发生 SIGSEGV 崩溃,出现在一个运行 BGSAVE 的副本上,以及一次全量同步中新提升的主节点上。报告者无法按需复现。
每个数据库都有一长串 bug,Redis 也一样。要点在于 Dragonfly 这份列表更新、历史更短,而且大量问题集中在复制和持久化路径上。如果这是你的主数据库,这一点值得记在心里。
Redis 仍然占优的方面
这些并不意味着 Redis 在所有场景下都是更差的选择。两种都用过之后,我认为 Redis 依然胜出的地方有这些。
成熟度。 Redis 经过了多年的生产环境检验,各种故障模式已经被研究得很透。凌晨三点出问题时,有大量现成的经验可以参考。Dragonfly 在这方面积累还远远不够。
持久化选项。 AOF、RDB 以及围绕它们的整套配置都经过了长期验证。如果你需要比周期性快照更强的持久化保证,Redis 目前能提供的手段更多。
生态。 客户端库、框架集成、监控工具、各大云上的托管服务、本地开发环境、CI 容器——这一切都已经支持 Redis,而且你的团队几乎肯定已经熟悉它。
可预测的行为。 Dragonfly 兼容 Redis 协议和大部分命令,但兼容不等于行为完全一致。脚本、集群、持久化和复制正是它存在差异的地方。在把重要业务交给他之前,先把你依赖的具体命令逐一测一遍。
足够的性能余量。 如果你的 Redis 实例 CPU 只用了 8%,而应用大部分时间都在等 Postgres,那 Dragonfly 不会让应用明显变快。基准测试图表看不出是什么在拖慢你的系统,性能剖析(profiling)才能。
我会怎么选
以下情况我会继续用 Redis:
* 负载在 Redis 轻松胜任的范围内
* 团队已经在运维它,而且很熟悉
* 我依赖某个特定的 Redis 功能、模块或托管服务
* 我需要 AOF 级别的持久化保证
* 熟悉度比额外的硬件效率更值钱
以下情况我会评估 Dragonfly:
* 负载是 CPU 密集型
* 我在跑大核数多路服务器
* 内存成本很关键
* Redis Cluster 的复杂度已经开始成为负担
* 纵向扩展比加节点更有吸引力
* 对这类数据来说,基于快照的持久化可以接受
替换一个能正常工作的东西是有成本的,所以需要一个可衡量的理由。
写在最后
我不认为 Dragonfly 已经让 Redis 过时了,也不认为 Redis 因为资历更老就一定是个糟糕的选择。
它们用不同的架构决策在解决同一类问题。Redis 围绕简洁性和命令执行的可预测性做了大量优化,Dragonfly 则走了另一条路,把我们今天手上的硬件利用得更好。
这也是我觉得它有意思的地方。
问题其实不在于谁更好,而是:它当初为什么被设计成这样?
想明白这一点,很多看起来奇怪的设计决策就不再奇怪了。
如果你的 Redis 用得好好的,就继续用。如果你开始碰到 CPU、内存或扩展性的瓶颈,那 Dragonfly 绝对值得放在旁边,实际跑一下你的负载看看结果。
有时候,理解一套基础设施最好的方式,就是别再看基准测试,直接去问:它的架构到底想解决什么问题。
原文:https://dev.to/adamthedeveloper/redis-vs-dragonfly-a-hands-on-comparison-22ko(作者 @adamthedeveloper)



