IT加油站

Redis vs Dragonfly实测:单线程与多线程谁更好

21浏览 • 20小时前 • 软件教程 • MA124581

原文: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)

#Redis #Dragonfly #多线程 #性能对比 #缓存 #数据库