连接池与性能调优
本教程共 40 篇 · 第 35 篇 · 更新于 2026-08-02
35. 连接池与性能调优
前两章把 Java、Python 客户端的「怎么连、怎么用」讲清楚了,这一章码上学把注意力放到「连得好不好、快不快」上。Redis 是单线程处理命令的,客户端侧的任何浪费——频繁建连、慢查询、超大输出——都会被放大成全局延迟。掌握连接池与本节列出的调优项,才能让你的应用真正发挥 Redis 8.x 的吞吐优势。
提示:先用
redis-cli看一下与服务端连接相关的几个关键配置,它们正是调优的旋钮。
127.0.0.1:6379> CONFIG GET timeout
1) "timeout"
2) "0"
127.0.0.1:6379> CONFIG GET tcp-keepalive
1) "tcp-keepalive"
2) "300"
127.0.0.1:6379> CONFIG GET maxclients
1) "maxclients"
2) "10000"
本节目标
- 理解为什么必须复用连接(连接池),以及连接建立的成本到底高在哪。
- 能配置 Jedis 的连接池参数(最大/空闲连接、等待超时),并理解 redis-py 的内建池。
- 掌握服务端
timeout、tcp-keepalive、maxclients、maxmemory-clients各自控制什么。 - 识别并规避「阻塞单线程」的性能红线:大 key、KEYS、慢脚本、超大输出缓冲区。
- 会用
INFO clients、SLOWLOG、redis-cli --bigkeys做连接与性能诊断。
一、为什么需要连接池
每一次客户端连接 Redis,都要经历 TCP 三次握手、可能的 TLS 协商、Redis 协议握手(HELLO/AUTH/SELECT)这一系列开销。如果「来一个请求就新建一个连接、用完就关」,这些握手成本会吃掉大量时间,而且瞬时高并发时还会把 maxclients 迅速打满。
连接池的思路是:预先建立一批连接放在池里,需要时借出、用完归还,而不是销毁。这样绝大多数请求都跳过了建连开销,连接数也始终受池大小约束。
Jedis 连接池
第 33 章用的是「每次 new 一个 Jedis」的写法,适合演示;生产环境一定要用 JedisPool:
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(64); // 池中最多 64 个连接
config.setMaxIdle(16); // 最多保留 16 个空闲连接
config.setMinIdle(4); // 至少预热 4 个空闲连接
config.setMaxWaitMillis(2000); // 借不到连接时最多等 2 秒,超时抛异常
try (JedisPool pool = new JedisPool(config, "localhost", 6379)) {
try (Jedis jedis = pool.getResource()) { // 从池里借
jedis.set("k", "v");
System.out.println(jedis.get("k"));
} // 归还连接,不是关闭
}
几个参数怎么定:maxTotal 要大于应用的并发线程数,但别大到把服务端 maxclients 压垮;maxIdle 让空闲时也有热连接可用,减少突发时的建连;maxWaitMillis 是保护项,取不到连接就快速失败,避免线程无限堆积。
redis-py 的内建池
第 34 章提到过,redis-py 的 Redis 对象默认就带连接池,你也可以显式控制大小:
import redis
pool = redis.ConnectionPool(
host='localhost', port=6379,
decode_responses=True,
max_connections=16
)
r = redis.Redis(connection_pool=pool)
max_connections 默认几乎是「不限制」,在连接数可能失控的场景(如 serverless、短生命周期函数)显式限制会更安全。
二、服务端的连接相关配置
redis.conf 里有几个旋钮直接决定连接的生死与资源占用:
timeout 0:客户端空闲多少秒后服务端主动断开它。0表示永不因空闲断开。如果你的客户端用了长连接池,保持0没问题;但如果客户端连接泄漏、从不释放,把timeout设成比如300(5 分钟)可以让服务端自动清理「半死不活」的连接,避免maxclients被占满。tcp-keepalive 300:每隔 300 秒发送 TCP keepalive 探测,用来识别已经挂掉的对端(比如客户端机器突然断电),及时释放其连接与文件描述符。公网或不太稳定的网络环境下建议保持开启。tcp-backlog 511:TCP 全连接队列长度,高并发建连时适当调大可避免握手请求被丢弃。maxclients 10000:最大客户端数,受操作系统文件描述符限制(详见第 32 章)。
客户端输出缓冲区限制 maxmemory-clients
这是 Redis 8.x(最新稳定版)里尤其要关注的一项。每个客户端在接收服务端回复时,服务端会为其分配一块输出缓冲区暂存数据。如果某个客户端消费得很慢(比如订阅了海量 Pub/Sub 消息、或者一次性查询返回了超大结果),这块缓冲区就会无限膨胀,吃掉内存甚至拖垮整个实例。
maxmemory-clients 用来给「所有客户端输出缓冲区」设上限,超出后 Redis 会主动驱逐(断开)最占内存的客户端:
127.0.0.1:6379> CONFIG GET maxmemory-clients
1) "maxmemory-clients"
2) "5%"
默认值通常是 5%(即 maxmemory 的 5%,若未设 maxmemory 则按可用内存估算)。也可以写成绝对大小,例如 maxmemory-clients 1g。对 Pub/Sub 或大数据返回场景较多的业务,合理设置它能防止「一个慢消费者拖垮全站」。
三、性能红线:别阻塞单线程
Redis 顺序执行命令,一次只处理一条。任何「耗时命令」都会让排在后面的所有客户端一起卡住。调优的核心就是别制造慢命令:
- 避免大 key。一个存了上百万成员的 Set、或几 MB 的 String,光是读取/删除就要毫秒甚至秒级。用
redis-cli --bigkeys定期扫描找出它们,拆分存储。 - 用 SCAN 代替 KEYS。第 16、34 章都强调过,
KEYS *会全库遍历并阻塞事件循环;SCAN游标分批、不阻塞。 - 控制 Lua 脚本与事务体量。脚本里的循环、大范围读写都会变成一次长阻塞,脚本要尽量短小(见第 23 章)。
- 限制单次返回大小。比如
LRANGE mylist 0 -1对超长列表极危险,改成分页或LIMIT。返回越小,输出缓冲区压力越小,也越不容易触发maxmemory-clients驱逐。
四、监控与诊断
调优离不开观测。几个最常用:
127.0.0.1:6379> INFO clients
# Clients
connected_clients:1
maxclients:10000
blocking_clients:0
connected_clients 持续逼近 maxclients,说明连接池可能泄漏或上限该调;blocking_clients 高说明大量连接卡在阻塞命令上。
慢查询日志帮你定位「是谁慢」:
127.0.0.1:6379> SLOWLOG GET 10
1) 1) (integer) 14
2) (integer) 1710000000
3) (integer) 12345
4) 1) "KEYS"
2) "*"
...
第三条数字就是耗时(微秒),上面这条 KEYS * 花了 12 毫秒——典型的反面教材。
定期扫描大 key:
$ redis-cli --bigkeys
此外,INFO stats 里的 instantaneous_ops_per_sec(每秒命令数)和 INFO memory 的 used_memory 是观察整体负载与内存走向的基础指标(详见第 38 章性能监控)。
提示:码上学建议把「连接池大小」「
timeout」「maxmemory-clients」「慢查询阈值slowlog-log-slower-than」作为上线前必查的四项,缺一项都可能在上线后半夜报警。
小结
- 连接池通过复用连接消除频繁建连开销,并约束连接总数;Jedis 用
JedisPool,redis-py 内建池且可显式设max_connections。 - 服务端
timeout清理空闲连接、tcp-keepalive探活死连接、maxclients限制并发连接数。 maxmemory-clients限制客户端输出缓冲区总内存,是防止慢消费者拖垮实例的关键护栏。- 性能红线只有一条原则:别让单条命令阻塞事件循环——远离大 key、
KEYS、重 Lua 和超大返回。 - 用
INFO clients、SLOWLOG GET、redis-cli --bigkeys持续观测,问题才藏不住。
常见误区
- 以为「不关连接」最省事,于是不用连接池、每次新建。结果是握手开销巨大,且容易把
maxclients打满。 - 把
timeout设得过小(如 5 秒)又用长连接池,导致正常空闲连接被服务端频繁踢掉,连接池反复重建。长连接池通常配合timeout 0。 - 只盯着 CPU 和内存,忽视客户端输出缓冲区。一个慢消费者就能靠膨胀的缓冲区吃光内存,必须靠
maxmemory-clients兜底。
提示:官方 redis.io 下载页另将 8.8 标注为 “Latest stable”,而 GitHub 上的最新发布 tag 为 8.10.0;二者同属 8.x,配置与协议层面差异对本教程影响极小,本教程统一以 Redis 8.x(最新稳定版)表述。