Redis 跑不动了,我没急着加机器,先把它换成了 Dragonfly
压测一上来,接口没先红,Redis 先开始喘。
监控里最扎眼的不是 CPU,是连接数和延迟一起往上拱。GET/SET这种看着最没技术含量的操作,P99 竟然能飙到十几毫秒。这个数平时看着不大,真落到下单、验证码、会话续期这些链路上,业务侧就开始一层层超时。像这种问题,我一般不先怀疑代码,先看缓存层顶没顶住。很多系统慢,不是 Java 慢,是你后面那个缓存实例已经开始排队了。行文气质参考了你给的几篇技术文里的现场感和排查节奏。
这次没继续在 Redis 上拧参数,直接试了个平替:Dragonfly。
它这东西说白了不是“兼容 Redis 的另一个玩具”,而是奔着高并发场景来的。对 SpringBoot 项目最友好的一点,是大部分代码基本不用动。你原来用StringRedisTemplate、RedisTemplate、Spring Cache,那套调用方式照旧,改的主要是连接地址。
先把依赖带上,没什么花活:
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
配置也很直接,SpringBoot 眼里它就是个 Redis 服务:
spring:
data:
redis:
host:10.10.2.18
port:6379
timeout:3000ms
lettuce:
pool:
max-active:64
max-idle:16
min-idle:8
max-wait:2000ms
业务代码基本不用配合你演戏,还是老样子:
@Service
publicclass LoginCacheService {
privatefinal StringRedisTemplate stringRedisTemplate;
public LoginCacheService(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
public void saveToken(Long userId, String token) {
String key = "login:token:" + userId;
stringRedisTemplate.opsForValue().set(key, token, Duration.ofMinutes(30));
}
public String getToken(Long userId) {
return stringRedisTemplate.opsForValue().get("login:token:" + userId);
}
public void refreshToken(Long userId) {
String key = "login:token:" + userId;
stringRedisTemplate.expire(key, Duration.ofMinutes(30));
}
}
真正要注意的,反而不是“能不能连上”,而是你原来代码里那些隐性坏习惯会不会被放大。
比如很多项目喜欢这么写:
public UserProfile queryProfile(Long userId) {
String userKey = "user:base:" + userId;
String extKey = "user:ext:" + userId;
String base = stringRedisTemplate.opsForValue().get(userKey);
String ext = stringRedisTemplate.opsForValue().get(extKey);
if (base == null || ext == null) {
return loadAndRebuild(userId);
}
return merge(base, ext);
}
单看没毛病,线上一多就是两次网络往返。缓存服务扛得住时你感觉不到,压力一上来这类碎读特别烦。我一般会先收敛 key 设计,能合并就合并,或者至少批量拿:
public List<Object> batchQuery(List<String> keys) {
return stringRedisTemplate.executePipelined((RedisCallback<Object>) connection -> {
for (String key : keys) {
connection.stringCommands().get(key.getBytes(StandardCharsets.UTF_8));
}
return null;
});
}
为什么换成 Dragonfly 后性能会明显起来?
不是一句“它更快”就完了。真到线上,感知最明显的是三个点:
第一,高并发下延迟更稳。原来 Redis 在热点 key、连接暴涨、pipeline 不规范这些场景下,尾延迟很容易难看。Dragonfly 扛并发时没那么容易抖。
第二,更省机器。以前为了顶住流量,最土的办法就是横向堆 Redis 实例。机器加了,运维复杂度也跟着加。换完之后,有些场景一台就能顶原来两三台的活,这个账很现实。
第三,接入成本低。这点很关键。很多“性能暴涨”的方案最后推不动,不是因为没效果,是改造面太大。Dragonfly 至少在 SpringBoot 这层,迁移路径比较短。
我当时还专门补了一段启动后的自检代码,先别急着让业务流量全切过去,先把最基础的读写、过期、计数器这些能力跑一遍:
@Component
publicclass CacheWarmChecker implements CommandLineRunner {
privatefinal StringRedisTemplate redisTemplate;
public CacheWarmChecker(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
@Override
public void run(String... args) {
String key = "df:health:check";
redisTemplate.opsForValue().set(key, "ok", Duration.ofSeconds(20));
String value = redisTemplate.opsForValue().get(key);
Long count = redisTemplate.opsForValue().increment("df:counter");
if (!"ok".equals(value) || count == null) {
thrownew IllegalStateException("dragonfly connection check failed");
}
}
}
这一段看着土,但很有用。很多缓存切换事故,不是挂在大功能上,是挂在最基本的 TTL、序列化、连接池这些细碎地方。你不先做兜底,后面排查会很烦。
再说一句容易被忽略的:别把“换了 Dragonfly”理解成性能优化的全部。
它能救的是缓存层顶不住的问题,救不了你乱设计的 key、无脑穿透、热点全集中、把缓存当数据库扫的习惯。缓存中间件再快,挡不住业务代码自己往墙上撞。