Redis + Node 如何支撑百万级并发

可以。Redis + Node.js 支撑百万级并发,核心不是"Redis 能不能扛百万",而是把请求链路做成无状态、异步化、缓存化,并把单机瓶颈拆到多个实例。

一个典型架构可以是:

less 复制代码
                    ┌──────────────┐
                    │   CDN / WAF  │
                    └──────┬───────┘
                           │
                    ┌──────▼───────┐
                    │ Load Balancer│
                    └──────┬───────┘
                           │
          ┌────────────────┼────────────────┐
          ▼                ▼                ▼
     Node.js #1       Node.js #2       Node.js #N
     stateless        stateless        stateless
          │                │                │
          └────────────────┼────────────────┘
                           │
                ┌──────────▼──────────┐
                │    Redis Cluster    │
                │  Shard 1 ... Shard N│
                └──────────┬──────────┘
                           │
                ┌──────────▼──────────┐
                │ MySQL / PostgreSQL  │
                │ Read Replica / Shard│
                └─────────────────────┘

1. Node.js:不要让单进程成为瓶颈

Node.js 本身适合大量 I/O 并发,但单个 Node 进程不能简单理解成能处理百万连接。

通常做法:

markdown 复制代码
百万客户端连接
       ↓
Load Balancer
       ↓
100 × Node.js instances
       ↓
Redis Cluster

例如:

ini 复制代码
import http from 'node:http';

const server = http.createServer(async (req, res) => {
  const data = await redis.get(`user:${req.userId}`);

  res.end(data ?? '{}');
});

server.listen(3000);

然后通过 Kubernetes / Docker / PM2 / ECS 等横向扩容。

关键原则:

  • Node 服务无状态
  • 不在进程内保存用户 Session
  • 不依赖本地内存作为唯一数据源
  • 不做同步 CPU 密集型任务
  • 长任务放 MQ / Worker
  • 使用连接池
  • Keep-Alive
  • 合理设置超时
  • 做好 graceful shutdown

2. Redis:单机 Redis ≠ 百万级架构

这是最容易踩坑的地方。

如果你说:

"我启动一个 Redis,然后 Node.js 连接它,能不能百万并发?"

通常答案是:不能直接这么设计。

应该考虑:

markdown 复制代码
                Redis Cluster
        ┌────────┬────────┬────────┐
        │Shard 1 │Shard 2 │Shard 3 │
        │Master  │Master  │Master  │
        └───┬────┴───┬────┴───┬────┘
            │         │        │
          Replica   Replica  Replica

Redis Cluster 根据 key 做分片:

makefile 复制代码
user:10001 → Shard 1
user:10002 → Shard 2
user:10003 → Shard 3
...

这样才能把:

复制代码
100 万 QPS

拆成:

复制代码
Shard 1 → 20 万
Shard 2 → 20 万
Shard 3 → 20 万
Shard 4 → 20 万
Shard 5 → 20 万

具体数字当然需要根据机器 CPU、内存、value 大小、命令类型和网络情况压测,不能简单按节点数线性估算。


3. Node → Redis 不要创建大量连接

一个非常重要的误区:

复制代码
100 万用户
=
100 万 Redis TCP 连接

完全不是这么回事。

应该是:

markdown 复制代码
100 万客户端
      ↓
几百/几千个 Node 实例
      ↓
每个 Node 使用 Redis connection pool
      ↓
Redis Cluster

例如单个 Node 实例可能维持有限数量的 Redis 连接,而不是每个用户创建一个连接。


4. 缓存设计比 Redis 本身更重要

例如用户请求:

sql 复制代码
GET /api/user/10086

不要每次:

复制代码
Node
 ↓
MySQL
 ↓
返回

而是:

sql 复制代码
Node
 ↓
Redis GET
 ↓
命中
 ↓
返回

代码类似:

vbnet 复制代码
const key = `user:${userId}`;

let user = await redis.get(key);

if (!user) {
  user = await db.query(
    'SELECT * FROM users WHERE id = ?',
    [userId]
  );

  await redis.set(
    key,
    JSON.stringify(user),
    { EX: 300 }
  );
}

return JSON.parse(user);

如果数据库平均只能承受:

复制代码
20,000 QPS

但 Redis 可以承受远高于这个数量级的请求,那么大量读请求就不会直接打到数据库。


5. 最危险的问题:缓存击穿

假设:

makefile 复制代码
user:10086

突然过期。

然后同时来了:

复制代码
100,000 requests

结果:

markdown 复制代码
100,000 Node requests
        ↓
Redis MISS
        ↓
100,000 requests 查询 MySQL

数据库直接被打爆。

这就是缓存击穿。

可以使用:

Single Flight / 分布式锁

markdown 复制代码
             Redis MISS
                 │
       ┌─────────┴─────────┐
       │                   │
   请求 1 获取锁        请求 2~N
       │                   │
       ▼                   │
    查询 DB                │
       │                   │
       ▼                   │
   写 Redis                │
       │                   │
       └──────────┬────────┘
                  ▼
              返回数据

例如:

vbnet 复制代码
const lockKey = `lock:${key}`;

const locked = await redis.set(
  lockKey,
  '1',
  { NX: true, EX: 5 }
);

if (locked) {
  try {
    const data = await loadFromDB();
    await redis.set(key, JSON.stringify(data), { EX: 300 });
  } finally {
    await redis.del(lockKey);
  }
}

生产环境还要考虑锁 token、续期、异常释放等问题,不能简单把 SET NX 当成完整分布式锁方案。


6. 缓存雪崩也必须处理

假设你有:

perl 复制代码
1,000,000 keys

全部:

ini 复制代码
TTL = 300

如果大量 key 在同一时间过期:

erlang 复制代码
Redis
 ↓
大量 MISS
 ↓
DB
 ↓
DB CPU 100%
 ↓
服务雪崩

不要:

ini 复制代码
EX = 300

全部固定。

可以加入随机 TTL:

javascript 复制代码
const ttl = 300 + Math.floor(Math.random() * 60);

await redis.set(
  key,
  JSON.stringify(data),
  { EX: ttl }
);

让过期时间分散。


7. 热点 Key 是百万并发架构里的大坑

例如:

ini 复制代码
商品 ID = 10086

突然:

复制代码
500,000 QPS

全部访问:

makefile 复制代码
product:10086

即使 Redis Cluster 有 20 个 shard:

bash 复制代码
product:10086
       ↓
同一个 hash slot
       ↓
同一个 Redis shard

这叫热点 Key。

可以考虑:

makefile 复制代码
product:10086:1
product:10086:2
product:10086:3
...
product:10086:N

或者在 Node 本地做极短时间的 L1 Cache:

vbscript 复制代码
Request
   ↓
Node L1 Cache
   ↓ miss
Redis
   ↓ miss
DB

例如:

复制代码
L1:几十毫秒~几秒
L2:Redis
L3:DB

对于极热、变化不频繁的数据非常有效。


8. 写请求不要全部同步打数据库

假设用户行为:

bash 复制代码
POST /api/like

100 万并发下,如果每个请求都:

sql 复制代码
Node
 ↓
MySQL UPDATE
 ↓
等待
 ↓
Response

数据库很容易成为瓶颈。

可以变成:

arduino 复制代码
Client
  ↓
Node
  ↓
Redis
  ↓
MQ
  ↓
Worker
  ↓
MySQL

例如:

arduino 复制代码
点赞
 ↓
Redis INCR
 ↓
Kafka / Redis Stream
 ↓
Worker 批量处理
 ↓
MySQL

于是 Web 请求不需要等待数据库事务完成。


9. Redis 不应该承担所有事情

比较合理的职责:

数据 推荐
Session Redis
登录 Token Redis
热点数据 Redis
排行榜 Redis
Rate Limit Redis
分布式锁 Redis
Pub/Sub Redis
持久业务数据 MySQL/PostgreSQL
大文件 OSS/S3
日志 Kafka/Log system
大规模异步任务 Kafka/RabbitMQ 等

尤其不要把 Redis 当成:

"天下无敌的数据库"

Redis 的内存成本很高,而且很多业务数据仍然需要关系数据库提供事务、复杂查询和持久化能力。


10. 限流是百万并发的必需品

百万并发并不意味着:

所有请求都应该进入业务系统。

例如 API:

bash 复制代码
/api/login
/api/order
/api/payment

必须限流。

Redis 可以实现 Token Bucket / Sliding Window:

sql 复制代码
User
 ↓
Rate Limiter
 ↓
Redis
 ↓
允许 → Node
拒绝 → 429

例如:

bash 复制代码
IP:100 req/s
User:50 req/s
API:10,000 req/s
Global:1,000,000 req/s

不同维度分别控制。


11. Node.js 还要特别注意事件循环

下面这种代码:

ini 复制代码
app.get('/api/data', (req, res) => {
  const result = heavyCalculation();
  res.json(result);
});

如果:

scss 复制代码
heavyCalculation()

耗时 500ms,并且占满 CPU,那么一个 Node 进程的事件循环会被阻塞。

结果:

vbscript 复制代码
Request 1 ──┐
Request 2 ──┤
Request 3 ──┼── Event Loop 被卡住
Request 4 ──┤
Request 5 ──┘

所以 CPU 密集型任务应该考虑:

复制代码
Node
 ↓
Worker Threads / Job Queue
 ↓
Worker

或者直接拆成独立 Worker 服务。


12. 一个比较完整的百万并发架构

如果让我设计一个典型的:

Node.js + Redis + MySQL 百万级并发系统

我会考虑:

less 复制代码
                         Internet
                            │
                     ┌──────▼──────┐
                     │ CDN / WAF   │
                     └──────┬──────┘
                            │
                     ┌──────▼──────┐
                     │ LoadBalancer│
                     └──────┬──────┘
                            │
          ┌─────────────────┼─────────────────┐
          │                 │                 │
      Node #1           Node #2           Node #N
          │                 │                 │
          └─────────────────┼─────────────────┘
                            │
                  ┌─────────▼─────────┐
                  │    Redis Cluster  │
                  │                   │
                  │ Shard 1           │
                  │ Shard 2           │
                  │ Shard 3           │
                  │ ...               │
                  └─────────┬─────────┘
                            │
                 ┌──────────┴──────────┐
                 │                     │
                 ▼                     ▼
             Message Queue          MySQL
                 │               Read Replicas
                 ▼
              Workers

再配:

diff 复制代码
CDN
+
WAF
+
Load Balancer
+
Node.js Cluster
+
Redis Cluster
+
MQ
+
MySQL Read Replica
+
Monitoring
+
Rate Limiting
+
Circuit Breaker
+
Autoscaling

13. 真正需要计算的是这几个数字

"百万并发"本身其实不是一个完整的性能指标。

必须区分:

复制代码
100 万 TCP/WebSocket 长连接

和:

复制代码
100 万 RPS

完全是两个问题。

例如:

场景 A

复制代码
1,000,000 长连接
10,000 RPS

Node.js 非常适合。

场景 B

sql 复制代码
1,000,000 RPS
每次请求 Redis GET

重点变成:

vbnet 复制代码
Redis Cluster
网络
Node 实例数量
请求大小
命令类型
热点 Key

场景 C

复制代码
1,000,000 RPS
每次请求都查询 MySQL

这时候 Redis 也救不了你。


最后给你一个实际落地思路

如果你是准备做真正的百万级并发项目,不要一上来就堆几十台服务器。

建议按这个顺序:

markdown 复制代码
① Node.js 单实例压测
        ↓
② Redis 单实例压测
        ↓
③ Node × N 横向扩容
        ↓
④ Redis Cluster
        ↓
⑤ 缓存击穿/雪崩/热点 Key
        ↓
⑥ DB 读写分离
        ↓
⑦ MQ 异步化
        ↓
⑧ 限流 + 熔断 + 降级
        ↓
⑨ 全链路监控
        ↓
⑩ 压测到目标 QPS

千万不要用"百万并发"直接反推机器数量。 应该先定义目标,例如:

erlang 复制代码
并发连接:1,000,000
目标 QPS:200,000
P99:< 100ms
Redis 命中率:> 95%
单请求平均响应:20ms
可用性:99.99%

然后根据压测结果倒推 Node、Redis、数据库和网络规模。

如果你愿意,我也可以进一步给你一套**"Node.js + Redis Cluster + MySQL 实现 100 万并发"的生产级项目架构**,包括 Docker/K8s、Redis Cluster、Node.js 代码、限流、缓存击穿、MQ、数据库分库分表以及压测方案。

相关推荐
国奉1 小时前
iOS 音频格式转换怎么实现?从 AVAudioFile、AAC、MP3 到 FLAC 与批量转码架构
前端·后端
旺仔不是程序员1 小时前
索引膨胀与重建:PostgreSQL REINDEX 生产实践
数据库·后端·sql
AllFiles1 小时前
K8s Node Exporter 异常 Write 超时排查实录
运维·后端·kubernetes
Thneonl1 小时前
etcd 不只是数据库:K8s 控制面的命脉
后端·kubernetes
SimonKing1 小时前
一个 jar 搞定实时推送:我的轻量级 SSE 中间件 Stream Nexus
java·后端·程序员
the局外人1 小时前
轻松掌握 LangGraph 的状态与节点
后端·langchain·llm
IT_陈寒1 小时前
Vite的静态资源引用把我坑惨了
前端·人工智能·后端
mldong1 小时前
13 套工作流后端共用一个 App 检查更新接口:0 张表,一行 SQL 没写
后端·架构
编程老船长1 小时前
模型中立——把大模型做成"可替换零件",而不是焊死在业务里
java·前端·后端