可以。redis + node.js 支撑百万级并发,核心不是“redis 能不能扛百万”,而是把请求链路做成无状态、异步化、缓存化,并把单机瓶颈拆到多个实例。
一个典型架构可以是:
┌──────────────┐
│ 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 进程不能简单理解成能处理百万连接。
通常做法:
百万客户端连接
↓
load balancer
↓
100 × node.js instances
↓
redis cluster例如:
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 连接它,能不能百万并发?”
通常答案是:不能直接这么设计。
应该考虑:
redis cluster
┌────────┬────────┬────────┐
│shard 1 │shard 2 │shard 3 │
│master │master │master │
└───┬────┴───┬────┴───┬────┘
│ │ │
replica replica replicaredis cluster 根据 key 做分片:
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 连接
完全不是这么回事。
应该是:
100 万客户端
↓
几百/几千个 node 实例
↓
每个 node 使用 redis connection pool
↓
redis cluster例如单个 node 实例可能维持有限数量的 redis 连接,而不是每个用户创建一个连接。
4. 缓存设计比 redis 本身更重要
例如用户请求:
get /api/user/10086
不要每次:
node ↓ mysql ↓ 返回
而是:
node ↓ redis get ↓ 命中 ↓ 返回
代码类似:
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. 最危险的问题:缓存击穿
假设:
user:10086
突然过期。
然后同时来了:
100,000 requests
结果:
100,000 node requests
↓
redis miss
↓
100,000 requests 查询 mysql数据库直接被打爆。
这就是缓存击穿。
可以使用:
single flight / 分布式锁
redis miss
│
┌─────────┴─────────┐
│ │
请求 1 获取锁 请求 2~n
│ │
▼ │
查询 db │
│ │
▼ │
写 redis │
│ │
└──────────┬────────┘
▼
返回数据例如:
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. 缓存雪崩也必须处理
假设你有:
1,000,000 keys
全部:
ttl = 300
如果大量 key 在同一时间过期:
redis ↓ 大量 miss ↓ db ↓ db cpu 100% ↓ 服务雪崩
不要:
ex = 300
全部固定。
可以加入随机 ttl:
const ttl = 300 + math.floor(math.random() * 60);
await redis.set(
key,
json.stringify(data),
{ ex: ttl }
);让过期时间分散。
7. 热点 key 是百万并发架构里的大坑
例如:
商品 id = 10086
突然:
500,000 qps
全部访问:
product:10086
即使 redis cluster 有 20 个 shard:
product:10086
↓
同一个 hash slot
↓
同一个 redis shard这叫热点 key。
可以考虑:
product:10086:1 product:10086:2 product:10086:3 ... product:10086:n
或者在 node 本地做极短时间的 l1 cache:
request ↓ node l1 cache ↓ miss redis ↓ miss db
例如:
l1:几十毫秒~几秒 l2:redis l3:db
对于极热、变化不频繁的数据非常有效。
8. 写请求不要全部同步打数据库
假设用户行为:
post /api/like
100 万并发下,如果每个请求都:
node ↓ mysql update ↓ 等待 ↓ response
数据库很容易成为瓶颈。
可以变成:
client ↓ node ↓ redis ↓ mq ↓ worker ↓ mysql
例如:
点赞 ↓ 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:
/api/login /api/order /api/payment
必须限流。
redis 可以实现 token bucket / sliding window:
user ↓ rate limiter ↓ redis ↓ 允许 → node 拒绝 → 429
例如:
ip:100 req/s user:50 req/s api:10,000 req/s global:1,000,000 req/s
不同维度分别控制。
11. node.js 还要特别注意事件循环
下面这种代码:
app.get('/api/data', (req, res) => {
const result = heavycalculation();
res.json(result);
});如果:
heavycalculation()
耗时 500ms,并且占满 cpu,那么一个 node 进程的事件循环会被阻塞。
结果:
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 百万级并发系统
我会考虑:
internet
│
┌──────▼──────┐
│ cdn / waf │
└──────┬──────┘
│
┌──────▼──────┐
│ loadbalancer│
└──────┬──────┘
│
┌─────────────────┼─────────────────┐
│ │ │
node #1 node #2 node #n
│ │ │
└─────────────────┼─────────────────┘
│
┌─────────▼─────────┐
│ redis cluster │
│ │
│ shard 1 │
│ shard 2 │
│ shard 3 │
│ ... │
└─────────┬─────────┘
│
┌──────────┴──────────┐
│ │
▼ ▼
message queue mysql
│ read replicas
▼
workers再配:
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
1,000,000 rps 每次请求 redis get
重点变成:
redis cluster 网络 node 实例数量 请求大小 命令类型 热点 key
场景 c
1,000,000 rps 每次请求都查询 mysql
这时候 redis 也救不了你。
最后给你一个实际落地思路
如果你是准备做真正的百万级并发项目,不要一上来就堆几十台服务器。
建议按这个顺序:
① node.js 单实例压测
↓
② redis 单实例压测
↓
③ node × n 横向扩容
↓
④ redis cluster
↓
⑤ 缓存击穿/雪崩/热点 key
↓
⑥ db 读写分离
↓
⑦ mq 异步化
↓
⑧ 限流 + 熔断 + 降级
↓
⑨ 全链路监控
↓
⑩ 压测到目标 qps千万不要用“百万并发”直接反推机器数量。 应该先定义目标,例如:
并发连接: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、数据库分库分表以及压测方案。
到此这篇关于redis + node 如何支撑百万级并发的实现的文章就介绍到这了,更多相关redis node百万级并发内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论