Skip to content

第四章:多 Worker 与连接池 ​

本章导读 ​

本章围绕“多 Worker 与连接池”展开,先交代适用场景,再说明核心原理,随后给出典型示例,最后补充小结与延伸阅读。

阅读顺序: 结论 → 原理 → 场景 → 代码 → 小结

Q12:3 个 Worker 进程启动后,各自有独立的 redis_client 吗?Redis 服务端看到的是 3 个连接还是 1 个? ​

答:

3 个独立的 redis_client,Redis 端看到 3 个 TCP 连接。

这里的“3 个 Worker 进程”指的是用 3 个独立 OS 进程启动 3 个 Worker。每个 python -m app.worker 是独立进程,各自 import 模块时创建 redis_client 单例。每个进程启动时连接池都是空的(按需建连),brpop 第一次执行时才建立第 1 条 TCP:

进程 A (PID 1001)           进程 B (PID 1002)           进程 C (PID 1003)
┌──────────────────┐       ┌──────────────────┐       ┌──────────────────┐
│ redis_client     │       │ redis_client     │       │ redis_client     │
│  └─ Pool (max=10)│       │  └─ Pool (max=10)│       │  └─ Pool (max=10)│
│  ┌─────┬─────┐   │       │  ┌─────┬─────┐   │       │  ┌─────┬─────┐   │
│  │C-1  │     │   │       │  │C-1  │     │   │       │  │C-1  │     │   │
│  │BRPOP│空闲 │   │       │  │BRPOP│空闲 │   │       │  │BRPOP│空闲 │   │
│  └─────┴─────┘   │       │  └─────┴─────┘   │       │  └─────┴─────┘   │
│  _in_use: {C-1}  │       │  _in_use: {C-1}  │       │  _in_use: {C-1}  │
│  _available: []  │       │  _available: []  │       │  _available: []  │
│  实际连接: 1 个  │       │  实际连接: 1 个  │       │  实际连接: 1 个  │
└───────┬──────────┘       └───────┬──────────┘       └───────┬──────────┘
        │ TCP:54321                │ TCP:54322                │ TCP:54323
        ▼                          ▼                          ▼
┌──────────────────────────────────────────────────────────────────────────┐
│ Redis 服务端(端口 6380)                                                │
│  client(fd=10, addr=:54321, cmd=brpop)  ← 等待链表节点 1  ← 进程 A
│  client(fd=11, addr=:54322, cmd=brpop)  ← 等待链表节点 2  ← 进程 B
│  client(fd=12, addr=:54323, cmd=brpop)  ← 等待链表节点 3  ← 进程 C
│                                                                          │
│  task:queue → [client(fd=10), client(fd=11), client(fd=12)]             │
│                ↑ 第一个被 LPUSH 唤醒                                    │
└──────────────────────────────────────────────────────────────────────────┘

关键点:

  • 每个进程的连接池 max=10,但实际只创建 1 个连接(brpop 独占 Conn-1)
  • 每条 TCP 连接源端口不同(54321/54322/54323),目的端口都是 6380
  • Redis 端 CLIENT LIST 看到 3 行,无法判断它们属于几个进程

用 CLIENT LIST 验证:

id=1  addr=127.0.0.1:54321  fd=10  cmd=brpop  age=5  idle=5
id=2  addr=127.0.0.1:54322  fd=11  cmd=brpop  age=5  idle=5
id=3  addr=127.0.0.1:54323  fd=12  cmd=brpop  age=5  idle=5

3 个 Worker = 3 行 = 3 个 TCP 连接 = 3 个等待链表节点。


Q13:Redis 等待链表里存的是什么?是"Worker 进程"吗? ​

答:

不是 Worker 进程或 Worker 线程这种应用侧执行单元的抽象概念,而是具体的 redisClient 结构体指针,每个对应一个 TCP 连接的文件描述符(fd)。

c
// Redis 源码 server.h
typedef struct client {
    int fd;              // ← TCP 连接的文件描述符
    connection *conn;    // ← 连接对象
    // ... 其他字段
} client;

// 阻塞 key 的等待客户端链表
dict *db->blocking_keys;
//  key → list of client*

唤醒时 Redis 通过 fd 把数据写回给客户端。一个 client = 一个 fd = 一个 TCP 连接。

这里要特别区分两个概念:redis_client 是「应用侧」创建的 Redis 客户端对象,而等待链表中的 client 是「Redis 服务端」维护的、正在阻塞等待某个 key 的 TCP 连接。两者不是同一个层面的概念,但它们通常通过同一条连接建立对应关系。

即使一个进程内有 3 个线程同时 BRPOP,redis-py 连接池也会借出 3 个不同的连接,Redis 端看到 3 个 client,

⚠️ 关键澄清:等待链表节点数 ≠ 连接总数 ​

一个 Python 进程借出 3 个 TCP 连接 → Redis 端有 3 个 client 结构体。但等待链表里存几个,取决于有几个连接正在 BRPOP 等待。

核心规则:

等待链表节点数 = 当前正在执行 BRPOP 且未返回的 TCP 连接数
              ≠ 客户端进程数
              ≠ redis_client 对象数
              ≠ 连接池里的连接总数
              ≠ Redis 端的 client 结构体总数

场景对比:一个进程 + 3 个连接,等待链表节点数不同 ​

场景 A:3 个连接都在 BRPOP(等待链表 3 个节点)

python
# 同一个进程,3 个线程同时 BRPOP 同一个 key
import threading

def worker():
    # 每个线程独立调用一次 brpop,借出各自的连接
    redis_client.brpop("task:queue", timeout=5)
    # ↑ 线程1 借 Conn-1,线程2 借 Conn-2,线程3 借 Conn-3
    # ↑ 3 个线程并发执行,3 个连接同时阻塞在 read_response()

# 启动 3 个线程
threads = [threading.Thread(target=worker) for _ in range(3)]
for t in threads:
    t.start()
for t in threads:
    t.join()

# 3 个线程 → 连接池借出 3 个 TCP 连接 → Redis 端 3 个 client → 都在等待
3 个线程并发执行 brpop 时的连接池状态:
  _in_use: {Conn-1, Conn-2, Conn-3}      ← 3 个连接都被借出
  _available: []                          ← 池空(还有 7 个余量未创建)

Redis 端:
  client(fd=10, cmd=brpop)  ← Conn-1(线程1)
  client(fd=11, cmd=brpop)  ← Conn-2(线程2)
  client(fd=12, cmd=brpop)  ← Conn-3(线程3)

等待链表:
  task:queue → [client(fd=10), client(fd=11), client(fd=12)]
                 ↑ 3 个节点,虽然来自同一个进程

LPUSH 时只唤醒 fd=10(FIFO 第一个),fd=11、fd=12 继续等。

场景 B:1 个 BRPOP + 2 个 hset(等待链表 1 个节点)

python
# 同一个进程,1 个线程 BRPOP 阻塞,2 个线程并行写进度
import threading

# 线程 1:BRPOP 阻塞等待任务
def consumer():
    result = redis_client.brpop("task:queue", timeout=5)
    # ↑ 借出 Conn-1,阻塞在 read_response()
    # ↑ 加入等待链表,未归还 Conn-1

# 线程 2:写任务进度(立即归还)
def writer_progress_image():
    redis_client.hset("task:6:progress", "stage", "IMAGE")
    # ↑ 借出 Conn-2 → 发送 HSET → 收到回复 → 立即归还 Conn-2

# 线程 3:写任务结果(立即归还)
def writer_result_done():
    redis_client.hset("task:6:progress", "stage", "DONE")
    # ↑ 借出 Conn-3 → 发送 HSET → 收到回复 → 立即归还 Conn-3

# 启动 3 个线程(consumer 先启动确保先进入 BRPOP)
t1 = threading.Thread(target=consumer)
t2 = threading.Thread(target=writer_progress_image)
t3 = threading.Thread(target=writer_result_done)

t1.start()
import time; time.sleep(0.1)   # 确保 consumer 先进入 BRPOP
t2.start()
t3.start()

t1.join(); t2.join(); t3.join()
线程并发期间连接池状态:
  _in_use: {Conn-1}                       ← 只有 Conn-1 还在阻塞(BRPOP 没返回)
  _available: [Conn-2, Conn-3]             ← Conn-2、Conn-3 已归还
  ↑ 注意:Conn-2、Conn-3 已归还但连接对象仍在池中(TCP 没断)

Redis 端:
  client(fd=10, cmd=brpop)    ← Conn-1 在等待链表里
  client(fd=11, cmd=hset)     ← Conn-2 已归还,但 client 结构体还在(TCP 仍存活)
  client(fd=12, cmd=hset)     ← Conn-3 已归还,但 client 结构体还在(TCP 仍存活)

等待链表:
  task:queue → [client(fd=10)]
                 ↑ 只有 1 个节点(只有 Conn-1 在 BRPOP)

3 个 client 都存在(TCP 连接还活着),但只有 1 个在等待链表里。

关键澄清:

  • 归还 ≠ 关闭 TCP:连接归还到池中只是从 _in_use 移到 _available,TCP 连接保持打开状态,Redis 端 client 结构体仍然存在
  • client 结构体存活到 TCP 关闭:只有显式 disconnect() 或连接池销毁时,TCP 才关闭,Redis 端 client 才会从 CLIENT LIST 消失

场景 C:本项目实际(等待链表 1 个节点)

python
# 主循环(单线程)
def process_queue():
    while True:
        result = redis_client.brpop("task:queue", timeout=5)
        # ↑ 主线程借 Conn-1 BRPOP 阻塞(等待链表 1 个节点)
        if result:
            task_id = int(result[1])
            # ↑ brpop 已返回,Conn-1 已归还到池中
            run_task(task_id)
            # ↑ 此时主线程在执行 run_task,没有线程在 BRPOP

# run_task 内部启动 3 个分镜线程(ThreadPoolExecutor)
def run_task(task_id):
    redis_client.hset(f"task:{task_id}:progress", "stage", "SCRIPT")
    # ↑ 主线程借 Conn-1(复用)→ 立即归还

    # 3 线程并行调 Seedance API + 写进度
    from concurrent.futures import ThreadPoolExecutor
    def generate_frame_video(frame):
        # 调 Seedance API(不在 Redis 上阻塞)
        # ...
        # 写进度(借连接)
        redis_client.hset(f"task:{task_id}:progress",
                          f"frame_{frame.seq}", "done")
        # ↑ 各线程借 Conn-2/3/4 → 立即归还

    with ThreadPoolExecutor(max_workers=3) as executor:
        futures = [executor.submit(generate_frame_video, f) for f in frames]
        for future in futures:
            future.result()
阶段 1:BRPOP 阻塞期间(主线程在 brpop 中)
  连接池: _in_use: {Conn-1}   _available: []
  Redis 端: client(fd=10, cmd=brpop)
  等待链表: task:queue → [client(fd=10)]    ← 1 个节点

阶段 2:BRPOP 返回后,run_task 执行中(分镜并行)
  连接池: _in_use: {}   _available: [Conn-1, Conn-2, Conn-3, Conn-4]
           ↑ 主线程 hset 借 Conn-1 → 归还
           ↑ 3 个分镜线程 hset 借 Conn-2/3/4 → 归还
  Redis 端: client(fd=10,11,12,13, cmd=hset)
  等待链表: task:queue → []    ← 空!没有线程在 BRPOP

阶段 3:run_task 返回,主线程回到 while 循环顶部再次 brpop
  连接池: _in_use: {Conn-1}   _available: [Conn-2, Conn-3, Conn-4]
  Redis 端: client(fd=10, cmd=brpop)
  等待链表: task:queue → [client(fd=10)]    ← 1 个节点

关键:等待链表节点数随主线程状态变化——brpop 阻塞时 1 个节点,run_task 执行时 0 个节点(brpop 已返回,没有人在 BRPOP)。

速查表 ​

概念场景 A(3 线程 BRPOP)场景 B(1 BRPOP + 2 hset)场景 C(本项目)
Python 进程数111
同时工作的线程数331(主循环)/ 4(分镜并行时)
连接池 _in_use 大小31(另外 2 个已归还)1(brpop 期间)/ 0(run_task 期间)
连接池 TCP 连接总数33(一旦建立不关闭)1-4(按需创建)
Redis 端 client 结构体数33(TCP 仍存活)1-4(动态)
等待链表节点数311(brpop 期间)/ 0(run_task 期间)

核心规律:

关系说明
等待链表节点数 = ?当前正在执行 BRPOP 且未返回的 TCP 连接数
已归还连接 → Redis 端 client 是否消失?❌ 不消失,TCP 仍存活,client 结构体保留
已归还连接 → 等待链表是否还有?❌ 没有,已归还说明 BRPOP 已返回
连接池 TCP 连接总数 = ?历史最大借出数(一旦建立不主动关闭)

用 CLIENT LIST 验证 ​

场景 A(3 个线程都在 BRPOP):

bash
redis-cli CLIENT LIST
id=1  addr=127.0.0.1:54321  fd=10  cmd=brpop  age=5  idle=5   ← 在等待链表
id=2  addr=127.0.0.1:54322  fd=11  cmd=brpop  age=5  idle=5   ← 在等待链表
id=3  addr=127.0.0.1:54323  fd=12  cmd=brpop  age=5  idle=5   ← 在等待链表

3 行 = 3 个 client = 3 个 TCP 连接 = 等待链表 3 个节点(3 个都在等)。

场景 B(1 个 BRPOP + 2 个 hset):

id=1  addr=127.0.0.1:54321  fd=10  cmd=brpop  age=5  idle=5   ← 在等待链表
id=2  addr=127.0.0.1:54322  fd=11  cmd=hset   age=5  idle=0   ← 不在等待链表
id=3  addr=127.0.0.1:54323  fd=12  cmd=hset   age=5  idle=0   ← 不在等待链表

3 行 = 3 个 client = 3 个 TCP 连接,但等待链表只有 1 个节点(只有 cmd=brpop 且 idle 持续增长的在等)。

本章小结 ​

等待链表里存的是"正在 BRPOP 等待的 TCP 连接",不是"客户端进程"。一个进程借出 3 个连接,如果 3 个都在 BRPOP,等待链表就有 3 个节点;如果只有 1 个在 BRPOP,等待链表就只有 1 个节点。Redis 只看 TCP 连接,不看连接属于哪个进程。

补充:每条 TCP 连接都有独立的源端口 ​

关键概念:一条 TCP 连接由四元组唯一标识:

TCP 连接 = (源 IP, 源端口, 目的 IP, 目的端口)

本项目场景:

目的 IP:目的端口 = 127.0.0.1:6380   ← Redis 服务端固定
源 IP            = 127.0.0.1        ← 客户端机器固定
源端口           = ???              ← 每条连接不同!

源端口是操作系统分配的,每建立一条 TCP 连接,内核就分配一个空闲的源端口:

Conn-1: 127.0.0.1:54321 ←► 127.0.0.1:6380
Conn-2: 127.0.0.1:54322 ←► 127.0.0.1:6380
Conn-3: 127.0.0.1:54323 ←► 127.0.0.1:6380
         ↑ 源端口不同       ↑ 目的端口相同(都是 Redis)

同一进程的 3 个连接也是不同端口(代码示例见上文 Q13 场景 A):

python
# 简化示例:一个进程,主线程 + 2 个子线程,共 3 个 TCP 连接
import threading, redis

redis_client = redis.Redis(host="localhost", port=6380, max_connections=10)

# 主线程借 Conn-1
redis_client.hset("k", "main", "v1")

# 子线程各自借 Conn-2、Conn-3
def worker(tag):
    redis_client.hset("k", tag, f"v-{tag}")

t1 = threading.Thread(target=worker, args=("t1",))
t2 = threading.Thread(target=worker, args=("t2",))
t1.start(); t2.start(); t1.join(); t2.join()

此时 CLIENT LIST 输出(3 条 TCP 连接,3 个源端口):

id=1  addr=127.0.0.1:54321  fd=10  cmd=hset   age=1  idle=0   ← Conn-1(主线程)
id=2  addr=127.0.0.1:54322  fd=11  cmd=hset   age=1  idle=0   ← Conn-2(线程 t1)
id=3  addr=127.0.0.1:54323  fd=12  cmd=hset   age=1  idle=0   ← Conn-3(线程 t2)
              ↑                ↑
              3 个源端口不同     3 个 fd 不同
              但是同一个进程!

Redis 端无法从 CLIENT LIST 判断这 3 个连接属于同一个进程还是不同进程——它只看到 3 条独立的 TCP 连接。

为什么每条 TCP 连接需要独立端口 ​

TCP 协议规定,一个端口同一时刻只能被一条 TCP 连接的某一端使用:

端口 54321  → 只能被 Conn-1 的客户端侧占用
端口 54322  → 只能被 Conn-2 的客户端侧占用
端口 54323  → 只能被 Conn-3 的客户端侧占用
端口 6380   → Redis 服务端共用一个端口(但能区分连接)

Redis 服务端怎么用同一个端口 6380 服务多条连接:靠 fd(文件描述符)区分。每个 TCP 连接到达服务端后,Redis 通过 accept() 创建一个新的 fd,然后把它和 client 结构体绑定。

c
// Redis 服务端源码(简化)
while (1) {
    int client_fd = accept(server_fd, ...);  // ← 每个连接 accept 一次
    // ↑ server_fd 绑定 6380 端口,但 client_fd 是新的
    client *c = createClient(client_fd);
    // ↑ 每个 client_fd 对应一个 client 结构体
}
层面端口fd
客户端侧每条 TCP 连接独立源端口每条连接独立 fd
服务端侧共用 6380 监听端口每条连接独立 fd

用 lsof / netstat 验证 ​

bash
# 查看 Redis 服务端(6380)的连接
lsof -i :6380
COMMAND   PID  USER   FD  TYPE  DEVICE  SIZE/OFF  NODE  NAME
redis-ser 1234  user   6u  IPv4  0x1234    0t0   TCP  *:6380 (LISTEN)        ← 监听 socket
redis-ser 1234  user  10u  IPv4  0x5678    0t0   TCP  localhost:6380→localhost:54321
redis-ser 1234  user  11u  IPv4  0x5679    0t0   TCP  localhost:6380→localhost:54322
redis-ser 1234  user  12u  IPv4  0x567a    0t0   TCP  localhost:6380→localhost:54323
                              ↑                             ↑
                              服务端 fd(每条不同)          客户端源端口(每条不同)

3 条连接 = 3 个服务端 fd(10/11/12)= 3 个客户端源端口(54321/54322/54323)。

端口范围 ​

操作系统给客户端分配源端口时,从一个固定范围内选:

bash
# Linux 查看端口范围
$ cat /proc/sys/net/ipv4/ip_local_port_range
32768   60999

# macOS 查看
$ sysctl net.inet.ip.portrange.first net.inet.ip.portrange.last
net.inet.ip.portrange.first: 1024
net.inet.ip.portrange.last: 65535

理论上一个客户端机器最多能连 28000 多条 TCP 到同一个 Redis 端口(Linux),但实际 Worker 进程不会这么多。

端口总结表 ​

问题答案
同一客户端的 3 个连接,CLIENT LIST 显示 3 行吗?✅ 是
这 3 行的端口相同吗?❌ 不同,每条 TCP 连接独立源端口
每条 TCP 连接都有独立端口吗?✅ 是(客户端侧源端口不同)
Redis 服务端端口相同吗?✅ 相同(都是 6380,靠 fd 区分)
Redis 能从 CLIENT LIST 判断连接属于哪个进程吗?❌ 不能,只看 TCP 连接

核心:一条 TCP 连接 = (源 IP, 源端口, 目的 IP, 目的端口) 四元组。同一个进程的 3 条连接,源端口必然不同,所以 CLIENT LIST 看到 3 行端口不同的记录。

端口与连接速查表 ​

维度客户端侧服务端(Redis)侧
IP127.0.0.1(客户端机器)127.0.0.1(Redis 机器)
端口每条 TCP 连接独立源端口(54321/54322/54323)共用 6380 监听端口
fd每条连接独立 fd每条连接独立 fd(10/11/12)
区分连接靠什么源端口fd(accept 返回的新 fd)
一个端口能被多少连接使用同一时刻只能被一条 TCP 连接的客户端侧占用同一端口可服务多条连接(靠 fd 区分)
场景Python 进程数TCP 连接数源端口数Redis 端 fd 数CLIENT LIST 行数
3 进程 × 1 连接333(每进程 1 个)33
1 进程 × 3 连接(3 线程 BRPOP)133(同一进程)33
1 进程 × 1 连接(本项目主循环)11111
1 进程 × 4 连接(分镜并行)14444
问题答案
同一客户端的 3 个连接,CLIENT LIST 显示 3 行吗?✅ 是
这 3 行的端口相同吗?❌ 不同,每条 TCP 连接独立源端口
每条 TCP 连接都有独立端口吗?✅ 是(客户端侧源端口不同)
Redis 服务端端口相同吗?✅ 相同(都是 6380,靠 fd 区分)
Redis 能从 CLIENT LIST 判断连接属于哪个进程吗?❌ 不能,只看 TCP 连接
同一进程的 3 条连接源端口能相同吗?❌ 不能,TCP 协议规定一个端口同一时刻只能被一条连接的某一端使用
Redis 服务端怎么用 6380 服务多条连接?监听 socket 绑定 6380,accept 时为每条连接创建独立 fd
Linux 客户端源端口范围32768-60999(约 28000 个)
macOS 客户端源端口范围1024-65535
验证命令作用
redis-cli CLIENT LIST查看 Redis 端所有 client(含 addr、fd、cmd、idle)
lsof -i :6380查看 6380 端口的所有 TCP 连接(含客户端源端口)
netstat -an | grep 6380查看与 6380 相关的连接状态
cat /proc/sys/net/ipv4/ip_local_port_rangeLinux 查看客户端源端口范围
sysctl net.inet.ip.portrange.first net.inet.ip.portrange.lastmacOS 查看客户端源端口范围

Q14:如果用 multiprocessing.Process 的 fork 模式启动 Worker,Redis 连接会出什么问题? ​

答:

fork 会复制父进程的内存,包括 redis_client 和已建立的 TCP 连接的 fd。两个进程向同一个 socket 写数据会导致 Redis 协议错乱。

python
# ❌ fork 模式的问题
import multiprocessing
from app.redis_client import redis_client  # ← 父进程已创建

def worker():
    # ← fork 后子进程继承了父进程的 redis_client
    # ← 连接池中的 TCP fd 被复制
    # ← 两个进程操作同一个 socket → 数据错乱!
    redis_client.brpop("task:queue", timeout=5)

p = multiprocessing.Process(target=worker)
p.start()  # fork!

正确做法:子进程中重新创建 redis_client:

python
def worker():
    import redis
    # 子进程中重新创建,不复用父进程的连接池
    client = redis.Redis(
        host="localhost",
        port=6380,
        decode_responses=True,
        max_connections=10,
    )
    client.brpop("task:queue", timeout=5)

本项目不受影响:python -m app.worker 每次都是全新进程,不走 fork,不存在继承问题。


Q15:一个 Worker 进程实际同时占用的连接数是多少?连接池 max_connections=10 是不是浪费? ​

答:

实际同时占用的连接数取决于是否启用 Seedance 分镜并行:

模式占用连接数说明
单线程顺序执行(无分镜并行)1 个brpop 返回后才执行 hset,全程只用 1 个连接
3 线程分镜并行(本项目实际启用)3-4 个3 个分镜线程 + 主线程 BRPOP

1. 单线程顺序执行模式(1 个连接) ​

阶段占用连接说明
process_queue 中 BRPOP 阻塞1 个只有 BRPOP 在执行,线程被挂起
run_task 中写进度1 个BRPOP 已归还,HSET 复用 Conn-1
run_task 中推审核队列1 个同上
峰值1 个brpop 和 hset 顺序执行,不会同时

2. 3 线程分镜并行模式(3-4 个连接,本项目实际配置) ​

python
# orchestrator.py 实际代码
with ThreadPoolExecutor(max_workers=3) as executor:
    futures = [executor.submit(generate_frame_video, frame) for frame in frames]
    # ↑ 3 个线程并行调用 Redis 写进度
    # ↑ 同时主线程可能还在 brpop 阻塞(Conn-1 被独占)

# 连接池状态:
#   _in_use: {Conn-1(BRPOP), Conn-2, Conn-3, Conn-4}
#   _available: [Conn-5, ..., Conn-10]
#   ↑ max_connections=10 留 6 个余量,不浪费
阶段占用连接说明
主线程 BRPOP 阻塞Conn-1独占
分镜线程 1 写进度Conn-2并行借出
分镜线程 2 写进度Conn-3并行借出
分镜线程 3 写进度Conn-4并行借出
峰值4 个1 BRPOP + 3 分镜并行

3. 为什么 max_connections=10 不浪费 ​

  • 连接是惰性创建的:max_connections=10 只是上限,实际用几个建几个,不用的不建
  • 分镜并行需要 3-4 个:3 线程并行 + 主线程 BRPOP,至少需要 4 个连接
  • 留 6 个余量:未来扩展到 5-6 线程并行也不用改配置

4. 多 Worker 进程的总连接数 ​

Worker 进程数单进程连接峰值总 TCP 连接Redis max_clients
144默认 10000
3412足够
10440足够
504200足够
2004800足够

详见 Redis连接池详解.md 1.3 节 — 单线程单连接 vs 多线程多连接的完整对比。