首页
学习
活动
专区
圈层
工具
发布

Redis 平替,SpringBoot 集成 Dragonfly,性能暴涨

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、无脑穿透、热点全集中、把缓存当数据库扫的习惯。缓存中间件再快,挡不住业务代码自己往墙上撞。

  • 发表于:
  • 原文链接https://page.om.qq.com/page/Oh4pJxcGSRd_MJxVhL8__Mrg0
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。
领券