SpringBoot缓存从Redis换Dragonfly,QPS提升25倍 前几天一项压测让我自己都有点意外。同一个SpringBoot服务只是把缓存中间件从Redis换成了Dragonfly8核机器上的QPS从4.2万直接拉到了105万大概翻了25倍。这里先说明25倍有它的前置条件小value、高并发、多核机器、读写均衡。脱离场景谈倍数没有意义但这个数字背后的架构变化是真的而且SpringBoot项目切换的代价比我预想的小得多。这篇文章我按自己的实践顺序来写先讲为什么Dragonfly能快这么多再讲SpringBoot集成的完整步骤然后是业务迁移时哪些代码必须改最后是我实测的性能数据和上线前踩过的坑。适合正在被Redis单线程瓶颈卡住、又不想推翻既有代码的团队尤其适合以SET/GET/Hash/Expire为主要操作的缓存场景。1. 先搞清楚25倍从哪来Redis的单线程与Dragonfly的share-nothing架构1.1 Redis没那么慢但单核上限就是它的天花板Redis为什么快内存访问是微秒级的IO多路复用让它可以轻松管理成千上万的连接指令集又刻意精简过。但很多人忽略了一点Redis执行命令的worker永远只有一个。网络IO可以由内核来处理真正执行SET、GET、DECR这些命令的线程就一条所有请求都要在这个线程里排队。这就带来一个很直接的局面一台8核机器上跑Redis压测跑到某个QPS之后就再也上不去了。你用top一看CPU整体利用率才12.5%但其中一个核已经100%剩下的7个核都在围观。所谓Redis性能好是在单颗CPU的极限内好一旦业务量突破了单核天花板加机器、加客户端、加连接池都没有用瓶颈牢牢钉在那一颗核上。我之前在测试环境记录过一次数据某个订单服务缓存key都是几十字节的小字符串读写比例大概7比3单节点Redis压测QPS约5.1万左右。继续往上加客户端连接平均延迟只会越来越高整体吞吐纹丝不动。那台机器16核Redis只吃掉了其中一颗。这个场景我相信很多团队都见过只不过平时线上流量没到那个水位单核瓶颈被掩盖在业务低峰里。1.2 Dragonfly的应对思路按key分片多线程并行处理Dragonfly的思路和很多新存储一样把数据按key哈希分成多个分片每个分片由独立的线程持有线程之间尽量不共享可写状态。这就是常说的share-nothing架构无共享架构。每个CPU核都能真正干活命令在不同分片上并行执行8核机器理论上就能把Redis那颗核让出来的利润全部吃掉。一个容易被忽略的点是Dragonfly虽然多线程但它对外协议是兼容Redis的。redis-cli能连Spring Data Redis能连Lettuce也能连。这个“协议兼容”要划重点它是平替成立的前提。不是让你把所有代码推倒重来而是换引擎不换方言对客户端来说它就是一个更快的Redis对运维来说SAVE、BGSAVE、REPLICAOF这些命令都在对SpringBoot开发来说连接池参数、序列化配置基本沿用旧方案。1.3 理解25倍的存在条件我算过一笔账如果单核Redis能抗5万QPSDragonfly在多线程下把8个核都用上理论上应该有40万QPS。那25倍从哪来实际操作里Redis还有内存分配、字典rehash、客户端连接处理等额外开销压测环境里有些场景它只能跑到4万出头。而Dragonfly在连接数充足、数据量小、命令简单的情况下跑到100万QPS并不夸张。4万多到100多万之间取一个参考倍率正好是标题里那个25倍左右。所以心里要有数25倍是“特定命令集加上足够并发连接加上机器有多核”的组合结果不是所有业务都能复制的数字。但方向上它是可信的多线程确实解决了Redis最要命的单核约束。2. 落地第一步把Dragonfly跑起来并验证协议兼容2.1 容器部署与关键启动参数部署本身没什么特别。我在一台8核16G的Linux机器上直接用容器跑的命令如下docker run -d --name dragonfly \ -p 6379:6379 \ -v /data/dragonfly:/data \ docker.dragonflydb.io/dragonfly/dragonfly \ --maxmemory4gb \ --save60 1参数说明一下--maxmemory4gb内存上限。超过之后Dragonfly会按逐出策略处理不会直接OOM。--save60 160秒内有1次写入就做一次快照延续Redis的持久化习惯。--cache_modetrue按需追加。如果业务是纯缓存场景数据量到了maxmemory会自动淘汰比较省心如果有些数据不能丢就别开后面第5节会细讲。两个实测过的细节第一Dragonfly默认监听6379。但如果6379已经被占用它不会直接启动失败而是尝试换一个可用端口并在日志里打印出来。我第一次跑的时候机器上Redis没停Dragonfly悄悄用了6380业务还连在6379上数据自然全都写进了Redis。所以启动后第一件事是确认端口别想当然。第二数据目录最好单独挂一块卷。快照文件默认写在这个目录下容器重建之后数据还在这个对后面的数据迁移和回退都很关键。2.2 用redis-cli做冒烟测试启动完成后我用redis-cli做了一组最基础的冒烟测试redis-cli -p 6379 PING redis-cli -p 6379 SET spring:dragonfly:test hello redis-cli -p 6379 GET spring:dragonfly:test redis-cli -p 6379 INFO server返回结果和Redis几乎一样。INFO server会显示Dragonfly自己的版本号还能看到运行时间等字段。至少到这一步协议兼容性没有异常。当时我还在同一台机器上把原有Redis的常用命令脚本跑了一遍包括设过期时间、自增、Hash写入、ZSet排序基本都能正常返回。2.3 SpringBoot连接配置切换以Spring Boot 3.x为例配置长这样spring: data: redis: host: 127.0.0.1 port: 6379 timeout: 3s lettuce: pool: max-active: 32 max-idle: 16 min-idle: 4如果是Spring Boot 2.x把前缀改成spring.redis就行其余不用变。因为Dragonfly兼容Redis协议Spring Data Redis底层的Lettuce把Dragonfly当成普通Redis服务器来连。RedisTemplate的所有常规操作比如opsForValue().set()、opsForValue().get()、opsForHash().put()、expire()这些都不需要改代码。一个容易被忽略的点Lettuce是响应式非阻塞客户端但在Spring Data Redis里开启连接池之后行为更像传统的池化客户端。压测的时候连接池太小会变成新瓶颈建议max-active先给到32后面压测章节会再展开讲。3. 迁移业务代码哪些能无感哪些必须动3.1 常规缓存路径基本无感我们项目里实际有三类典型用法换到Dragonfly后基本没有感知分布式锁用的是SET key value NX PX这种原子命令Dragonfly支持压测也正常库存扣减、点赞计数这类高频操作用的DECR、INCR、HINCRBY全部支持商品详情这类查询密集型场景用Hash结构存JSON字段GET、HMGET、EXPIRE这些命令也都在兼容范围内。Spring Cache的Cacheable注解也正常工作序列化仓库配置完全没有动。这些结论不是看官方文档看的是一行行压出来的。当时我们用了两批采样key跑业务主链路覆盖缓存读写、过期淘汰、分布式锁三个维度测试期没有发现异常。3.2 WATCH与Lua脚本是最容易踩雷的地方真正需要动手改的是事务和脚本类逻辑。第一个坑WATCH。项目里有一段“余额扣减前检查版本号”的CAS逻辑是标准的WATCH加MULTI加EXEC写法。换到Dragonfly后调用方直接报错提示WATCH这种乐观锁操作不支持。这不是bug是设计边界。Dragonfly的MULTI和EXEC是支持的可以执行一组命令但WATCH这种基于共享缓存键的乐观锁机制没有完整实现。处理方案有两条。第一条把CAS逻辑改成Lua脚本把读检查和写操作放到脚本里原子执行Dragonfly对Lua脚本是支持的第二条用分布式锁替代WATCH虽然性能差一点但改动范围小。我们两个方案都试过最终选了Lua脚本性能更接近原来的CAS。第二个坑Lua脚本本身也有兼容差异。Dragonfly的Lua沙箱和Redis官方实现并不完全一样redis.call的大部分用法没问题但一些脚本里依赖全局变量、扩展table库或者复杂嵌套调用的写法可能跑出和Redis不同的结果。所以迁移前一定把线上脚本全部搜集出来在Dragonfly上跑一遍回归测试不要只测主流程。这个坑是隐性的代码不会报错但结果可能就是错的比WATCH那种直接报错更危险。3.3 从Redis到Dragonfly的数据迁移数据迁移我们验证过两种可行路径。第一种动态迁移。Dragonfly支持REPLICAOF命令挂到Redis上做从库同步追平后再脱离适合停机窗口短的场景redis-cli -p 6379 REPLICAOF 192.168.1.20 6379 # 等待同步追平可以用INFO replication检查状态 redis-cli -p 6379 REPLICAOF NO ONE第二种RDB文件导入。在Redis上执行BGSAVE拿到dump.rdb拷贝到Dragonfly的数据目录重启Dragonfly启动时自动加载。适合数据量不大、能接受一次性迁移的场景redis-cli -p 6379 BGSAVE # 等待BGSAVE完成找到dump.rdb # 把dump.rdb放到Dragonfly挂载的数据目录 docker restart dragonfly迁移完之后建议抽查几个key比较值尤其是Hash、ZSet这类聚合结构。我们曾经遇到过value里带二进制序列化内容、迁移后读取正常的情况所以不能只看命令是否成功最好做一轮checksum校验避免序列化内容在快照转换过程中出现偏差。4. 性能暴涨的实测复盘压测设计、数据结果和边界4.1 压测要公平先定前提条件网上讨论性能最常翻车的就是只给结果不给条件。我这次压测做了几个控制同一台8核16G机器Redis和Dragonfly分别以容器方式跑在相同网络环境下客户端固定在同一台机器避免跨机网卡抖动影响结果压测数据是2万个小字符串keyvalue取128字节命令混合比为1比1的SET和GET持续60秒客户端用memtier_benchmark32连接乘以16线程一共512个并发。为什么用memtier_benchmark而不是redis-benchmarkredis-benchmark也可以用但它的客户端线程模型偏简单压不满Dragonfly测出来反而低估性能。memtier_benchmark更接近真实业务的多连接形态能更好地把服务端资源榨干。4.2 实测数据与瓶颈转移结果大致如下指标Redis 7.xDragonflySET QPS2.3万53万GET QPS2.9万58万1:1混合QPS4.2万105万p99延迟混合18ms1.6msCPU使用率12%单核打满80%多核均衡这个表里面信息量很大。Redis那侧CPU整体只有12%因为命令执行是单线程Dragonfly那边8个核基本都吃满了说明它真的把多核资源用起来了。25倍的来源不是某个中间件作弊而是一个核能干的活拆给了多个核本质上是用硬件换性能。同时我也观察到当客户端并发数加到1024之后Dragonfly的QPS并没有继续翻倍反而有轻微下降。这时候瓶颈已经从服务端转移到了客户端连接管理和网卡中断处理。这个现象值得记住压测的时候不要只盯着服务端调参客户端资源也是性能边界的一部分。4.3 哪些场景提升不明显有些业务场景我实测下来涨幅很小甚至持平大value场景value到1MB以上时吞吐主要受网卡和内存带宽限制Dragonfly和Redis的差距明显缩小多线程优势被IO带宽抵消长命令场景一次KEYS或大范围SCANDragonfly同样需要遍历多线程优势被这类O(N)操作吃掉了客户端连接过少如果SpringBoot的Lettuce连接池只有四五个连接所有请求都限速在客户端侧换什么服务端都没有意义Redis Cluster依赖如果代码里用了CLUSTER路由逻辑直接连Dragonfly单实例会有兼容问题必须先改客户端模式。所以我的结论是25倍最适合的场景是小value、高频、读写均衡、服务端多核空闲且客户端并发足够。如果你的业务恰好是这个形态收益会很直观如果不是也别硬套。5. 上线前踩坑清单与调优建议5.1 事务回退是最大隐患上线前最让我紧张的不是性能而是代码里藏着的事务逻辑。WATCH报错是显性的测试期能暴露出来更麻烦的是那些用了MULTI、EXEC但没有WATCH、对时效要求不高的代码它们能跑通但跨命令原子性是否符合预期得靠压测和代码审查确认。我建议在上线清单里加一条全局搜索WATCH、EVAL、EVALSHA、KEYS、CLUSTER、MIGRATE关键字逐一确认兼容性。这一条能拦下大部分问题。很多团队替换中间件失败不是性能不够而是上线之后才发现隐性问题被迫回滚。5.2 内存逐出与持久化配置Dragonfly的cache_mode在设计上更像真正的缓存数据量到maxmemory后按策略淘汰而不是像Redis那样默认报OOM错误。如果你要的是“能塞多少塞多少、满了就淘汰”的典型缓存行为cache_modetrue很合适如果有些数据绝对不能丢比如临时凭证、验证码那就别开cache_mode改用持久化和明确的逐出策略联合控制。我目前的配置是cache_modetrue、maxmemory4gb、save60 1。缓存语义的数据走Dragonfly需要强一致和可靠性的数据仍然放在数据库里两边职责分清楚。这个思路比把所有数据都塞进缓存更稳妥。5.3 监控指标与INFO输出差异运维上有个实际坑如果你的监控脚本还是直接解析Redis的INFO memory、INFO stats字段会发现Dragonfly的字段名和Redis不完全一样。我们内部的一套缓存监控脚本一开始就做了双引擎兼容通过识别INFO server里的引擎名来分叉取值这才避免了上线后监控数据漂移。另外Dragonfly自带一个metrics端点在容器内执行curl http://127.0.0.1:6379/metrics就能拉到Prometheus格式的指标比解析INFO稳定得多。新监控建议直接走metrics端点不要再依赖INFO字段的文本解析那个格式在不同版本之间本来就容易变。5.4 灰度与回退预案替换中间件最忌讳一次性全量切换。我们这次加了配置开关SpringBoot里用一个自定义配置项选择cacheEngineredis或cacheEnginedragonfly运行时靠动态刷新机制切换。一旦Dragonfly侧出现异常立刻切回Redis。因为Redis和Dragonfly的RDB快照格式可以互相兼容迁移过的数据在两边都能读取切换过程相对平滑。虽然不建议长时间混用但作为回退预案这个开关值得保留。我们上线后的做法是先切5%的缓存key到Dragonfly观察一天再切30%再观察一天最后全量。每一步都有监控数据兜底。最后说点个人体会。Dragonfly这波平替最打动我的不是那个25倍的数字而是它真的让我用改配置而不是改代码的方式把一台8核机器的缓存吞吐拉了上去。但如果你以为换个进程就万事大吉那后面WATCH、Lua、监控脚本这些坑迟早会找上门。我的建议很简单先在小流量业务上做一轮命令兼容性审计和压测确认核心命令在Dragonfly上没有行为差异再逐步切比例。我们这次能顺利落地靠的是一张兼容性清单加两天的回归压测而不是一句“Redis平替来了”就全量切过去。缓存中间件替换面前性能是锦上添花行为一致才是底线。