Redis QPS 上不去?3 个痛点 + 7 个实战技巧,帮你解决性能瓶颈

内容分享2个月前发布
0 0 0

Redis QPS 上不去?3 个痛点 + 7 个实战技巧,帮你解决性能瓶颈

你是不是也遇到过这样的情况?项目里用了 Redis 做缓存,可一到高并发场景,QPS 就卡在几千上不去,接口延迟还一个劲飙升?明明跟着教程调了不少配置,重启 Redis 后效果却微乎其微,甚至偶尔还会触发线上报警?作为互联网开发,咱们都知道 Redis 性能直接影响系统稳定性,可真遇到瓶颈时,要么找不到核心问题,要么试了一堆方法还是没改善 —— 今天这篇内容,就帮你把 Redis 性能优化的 “坑” 和 “招” 说透,看完就能落地实操。

先说说咱们常踩的 3 个 Redis 性能痛点,你中了几个?

做技术的都知道,解决问题得先找根源。我接触过不少开发同事,提到 Redis 优化都皱眉头,总结下来无非是这 3 个高频痛点:

第一个痛点是 “盲目调参”。许多人遇到性能问题,第一反应就是搜 “Redis 最优配置”,把 maxmemory、timeout 这些参数改一遍,可最后 QPS 没涨,反而由于配置不匹配业务场景,出现了内存溢出的情况。就像之前有个电商项目的同事,为了提高 QPS 把 redis.conf 里的 tcp-backlog 从 511 改成了 2048,结果忽略了操作系统的 somaxconn 参数没同步调整,导致新连接根本建立不起来,反而引发了接口超时。

第二个痛点是 “数据结构用错”。Redis 有 String、Hash、List 这些数据结构,可不少人不管存什么数据都用 String,明明用 Hash 能减少内存占用、提高查询效率,却偏要拆成多个 String 键值对。列如存储用户信息,用 “user:1001:name”“user:1001:age” 这种多个 String 键,比用 “user:1001” 一个 Hash 键,内存占用多了 30% 不说,批量查询时还要多执行好几次命令,QPS 自然上不去。

第三个痛点是 “连接池配置不当”。咱们开发时常用 Jedis、Redisson 这些客户端,可许多人对连接池的 maxTotal、maxIdle 参数没概念,要么设得太小导致并发时连接不够用,要么设得太大让 Redis 服务器频繁处理连接建立和关闭,消耗额外资源。之前有个支付项目,连接池 maxTotal 只设了 50,高峰期并发请求超过 200,结果大量线程阻塞在等待 Redis 连接上,接口响应时间从 50ms 涨到了 500ms,排查半天才发现是连接池的问题。

为什么这些痛点会反复出现?聊聊 Redis 性能的核心影响因素

实则这些痛点背后,本质是咱们对 Redis 的 “工作原理” 和 “业务匹配度” 理解不够深。先说说 Redis 的性能逻辑:Redis 是单线程模型,主要瓶颈不在 CPU,而在内存、网络和 IO—— 内存不够会触发淘汰策略,频繁删除数据;网络延迟高会增加命令响应时间;IO 操作(列如持久化)太频繁会占用主线程资源,这些都会直接拉低 QPS。

再结合咱们的业务场景看,互联网项目大多有 “高并发、高读写” 的特点,列如电商秒杀、直播弹幕、接口缓存这些场景,对 Redis 的响应速度和稳定性要求极高。但许多时候,咱们在设计阶段没思考到业务增长后的性能需求,列如一开始用 Redis 存少量热点数据,后来数据量涨到几十万、几百万,却没调整数据结构和过期策略;或者没根据业务类型选择合适的持久化方式,列如写频繁的场景还用 RDB+AOF 混合持久化,导致 IO 开销过大。

还有一个容易被忽略的点是 “监控不到位”。许多项目只监控 Redis 的内存使用率和是否在线,却没监控 QPS、响应时间、连接数这些关键指标,等线上出了问题才去排查,这时已经影响用户体验了。就像之前有个社交项目,Redis 响应时间从 10ms 涨到 80ms,运维团队没及时发现,直到用户反馈 “刷新页面变慢”,才定位到是某个大 Key 频繁查询导致的 —— 如果早监控这些指标,就能提前规避问题。

7 个实战技巧,从配置到代码帮你把 Redis QPS 拉满

知道了痛点和缘由,接下来就是最核心的解决方案。这 7 个技巧都是我在多个项目中验证过的,从配置优化到代码实操,每个步骤都有具体示例,你照着做就能看到效果:

技巧 1:优化连接池参数,避免连接阻塞

咱们用 Jedis 客户端时,重点调整 3 个参数:maxTotal(最大连接数)、maxIdle(最大空闲连接数)、minIdle(最小空闲连接数)。计算公式参考 “maxTotal = 业务并发量 × 1.2”,列如业务高峰期并发是 200,maxTotal 设 240 就够了;maxIdle 提议设为 maxTotal 的 80%,避免频繁创建连接;minIdle 设为 maxTotal 的 20%,保证有足够的空闲连接应对突发流量。

示例代码(Jedis 连接池配置):

JedisPoolConfig poolConfig = new JedisPoolConfig();
// 最大连接数
poolConfig.setMaxTotal(240);
// 最大空闲连接数
poolConfig.setMaxIdle(192);
// 最小空闲连接数
poolConfig.setMinIdle(48);
// 连接空闲多久后关闭(单位:毫秒)
poolConfig.setMinEvictableIdleTimeMillis(300000);
// 定时检测空闲连接
poolConfig.setTimeBetweenEvictionRunsMillis(60000);
JedisPool jedisPool = new JedisPool(poolConfig, "127.0.0.1", 6379, 2000);

同时要记得同步调整操作系统的 somaxconn 参数(Linux 系统),执行 “echo 2048>
/proc/sys/net/core/somaxconn”,让操作系统允许更多的待处理连接,避免连接被拒绝。

技巧 2:选对数据结构,减少内存占用和命令执行次数

不同业务场景对应不同的数据结构,这里给你整理了 4 个高频场景的选择提议:

  • 存储对象(如用户信息、商品详情):用 Hash,比 String 节省 30%-50% 内存,批量查询用 hgetall,避免多次 get;
  • 存储列表(如消息队列、排行榜):用 List 或 Sorted Set,List 适合先进先出场景,Sorted Set 适合需要排序的场景(如按分数排名);
  • 存储集合(如用户标签、好友列表):用 Set,支持交集、并集操作,比自己用 String 处理效率高 10 倍以上;
  • 存储计数器(如点赞数、访问量):用 String 的 incr 命令,单线程原子操作,避免并发问题。

举个例子,之前有个资讯项目,用 String 存储文章的 “阅读量 + 点赞数 + 评论数”,每个指标一个键,列如 “article:1001:read”“article:1001:like”,后来改成 Hash 结构 “article:1001”,字段分别是 read、like、comment,内存占用减少了 42%,查询时用 hgetall 一次就能获取所有指标,QPS 提升了 25%。

技巧 3:大 Key 拆分,避免阻塞主线程

Redis 单线程模型下,一个大 Key 的查询或删除操作会阻塞主线程,导致其他命令排队。判断大 Key 的标准是:String 类型超过 10KB,Hash、List 等集合类型元素数超过 1000 个。

拆分方法分两种:

  • String 大 Key:列如存储长文本(如文章内容),按段落拆成多个小 Key,列如 “article:1001:content:1”“article:1001:content:2”,查询时再拼接;
  • 集合大 Key:列如 Hash 类型的 “user:orders:1001”(存储用户所有订单),按时间拆成 “user:orders:1001:202505”“user:orders:1001:202506”,每个 Hash 只存一个月的订单。

同时要定期用 “redis-cli –bigkeys” 命令检测大 Key,列如每周执行一次,发现大 Key 就及时拆分。之前有个电商项目,一个 “product:stock” 的 String 大 Key 存了 10 万种商品的库存,每次 get 都要花 50ms,拆分后每个小 Key 存 1000 种商品库存,响应时间降到了 5ms 以内。

技巧 4:合理设置过期策略,减少内存占用

许多项目用 Redis 存缓存,却没设置过期时间,导致内存越用越多,最后触发淘汰策略(默认是 noeviction,内存满了就拒绝写操作)。正确的做法是根据业务场景设置过期时间,同时选择合适的淘汰策略。

过期时间设置提议:

  • 热点数据(如首页 Banner、热门商品):设 1-2 小时过期,配合定时更新;
  • 临时数据(如验证码、临时 Token):设 5-10 分钟过期,避免无效数据占用内存;
  • 持久化数据(如用户基本信息):不设过期时间,但要定期清理冗余数据。

淘汰策略选择:

  • 内存有限、需要优先保留热点数据:选 allkeys-lru(移除最近最少使用的 Key);
  • 所有 Key 都有过期时间、需要按过期时间清理:选 volatile-ttl(移除剩余时间最短的 Key);
  • 写操作多、需要避免淘汰热点数据:选 volatile-lru(只淘汰有过期时间的 Key 中的最近最少使用 Key)。

配置示例(redis.conf):

# 设置淘汰策略为allkeys-lru
maxmemory-policy allkeys-lru
# 内存使用达到90%时触发淘汰
maxmemory-samples 5
maxmemory 4gb  # 根据服务器内存调整,列如8GB内存设4GB

技巧 5:优化持久化方式,减少 IO 开销

Redis 持久化有 RDB 和 AOF 两种方式,不同业务场景选择不同:

  • 高读写、对数据一致性要求不高(如缓存):只用 RDB,列如每 6 小时生成一次 RDB 文件,避免 AOF 的 IO 开销;
  • 对数据一致性要求高(如存储用户余额):用 RDB+AOF 混合持久化,AOF 设为 everysec(每秒同步一次),既能保证数据不丢失,又不会太影响性能。

同时要注意 AOF 重写的配置,避免重写时占用太多资源:

# AOF文件大小超过上次重写后的100%时触发重写
auto-aof-rewrite-percentage 100
# AOF文件大小超过64MB时触发重写
auto-aof-rewrite-min-size 64mb

之前有个支付项目,一开始用 AOF 的 always(每次写操作都同步)方式,导致 Redis 的写 QPS 只能到 3000,改成 everysec 后,写 QPS 直接涨到了 15000,响应时间也从 20ms 降到了 8ms。

技巧 6:使用 Pipeline 批量操作,减少网络往返

咱们开发时如果需要执行多个 Redis 命令,列如批量查询 10 个用户的信息,许多人会用循环调用 get 命令,这样每次命令都要走一次网络往返(从应用服务器到 Redis 服务器),10 个命令就有 10 次往返,延迟很高。

正确的做法是用 Pipeline 批量执行,把多个命令打包成一个请求发送,减少网络往返次数。以 Jedis 为例,批量查询 10 个用户信息的示例代码:

try (Jedis jedis = jedisPool.getResource()) {
    Pipeline pipeline = jedis.pipelined();
    // 批量添加10个get命令
    for (int i = 1; i <= 10; i++) {
        pipeline.hgetAll("user:" + i);
    }
    // 执行批量命令并获取结果
    List<Object> results = pipeline.syncAndReturnAll();
    // 处理结果
    for (Object result : results) {
        Map<String, String> userInfo = (Map<String, String>) result;
        // 业务逻辑处理
    }
}

实测下来,用 Pipeline 执行 10 个命令,比循环执行快 3-5 倍,网络延迟越高,效果越明显。列如应用服务器和 Redis 服务器不在一个机房,网络延迟 20ms,循环执行 10 个命令要 200ms,用 Pipeline 只要 25ms 左右。

技巧 7:监控关键指标,提前发现问题

最后一个技巧是做好监控,别等线上出问题才补救。重点监控以下 6 个指标,提议用 Prometheus+Grafana 搭建监控面板:

  1. QPS:正常情况下稳定在某个范围,突然下降可能是连接池或网络问题;
  2. 响应时间(latency):一般要控制在 10ms 以内,超过 50ms 就要排查;
  3. 连接数(connected_clients):不能超过 maxTotal,否则会出现连接超时;
  4. 内存使用率(used_memory_rss /total_system_memory):提议控制在 70% 以内,超过 90% 容易触发淘汰;
  5. 大 Key 数量:定期检测,数量多了要及时拆分;
  6. 持久化耗时(rdb_last_save_time、aof_last_rewrite_time):耗时太长说明 IO 压力大,要优化持久化配置。

列如之前有个直播项目,通过监控发现 Redis 的响应时间从 8ms 涨到了 35ms,进一步排查发现是某个 “room:message” 的 List 大 Key,元素数超过了 5000,每次 lpush 都要花 20ms,及时拆分后响应时间又恢复到了正常范围。

总结一下,这 3 个核心要点要记牢

今天分享的 7 个技巧,实则核心就是 3 件事:匹配业务场景、减少资源消耗、提前监控预警。你在项目中做 Redis 优化时,不用把所有技巧都用上,而是根据自己的场景选重点:

如果是高并发缓存场景,重点做连接池优化、Pipeline 批量操作和过期策略设置;如果是数据存储场景,重点做持久化优化、大 Key 拆分和数据结构选择;如果是跨机房部署,重点做网络优化和连接池配置。

最后呼吁大家,优化 Redis 不要盲目跟风,别人的 “最优配置” 不必定适合你的项目,必定要结合业务场景做测试 —— 列如调整连接池参数后,用 JMeter 压测看看 QPS 和响应时间的变化;拆分大 Key 后,检查内存占用和查询效率是否提升。

如果你在实操过程中遇到问题,或者有更好的优化技巧,欢迎在评论区留言分享,咱们一起交流进步!毕竟做技术的,就是在不断踩坑和解决问题中成长的~

© 版权声明

相关文章

暂无评论

none
暂无评论...