企业官网平时流量平稳,可一旦赶上活动推广、新闻爆点或节假日促销,瞬间涌入的流量可能翻上几十倍。如果架构没有提前做好准备,服务器会在几秒内被压垮。高并发处理不是单一技术,而是一套组合拳——从前端限流到后端削峰,从横向扩展到降级保命,缺一不可。本篇梳理尧图实战过的高并发应对方案。
一、负载均衡分流
单台服务器再强也有上限,应对高并发的第一步是把流量分散到多台机器。负载均衡器(Nginx、云厂商 SLB)站在入口,按算法把请求转发到后端多台应用服务器。常用调度算法各有适用场景:
- 轮询(Round Robin):依次分配,适合服务器配置相同的场景。
- 加权轮询:按服务器性能分配权重,强机器多干活。
- IP Hash:同一 IP 固定到同一机器,保留会话状态。
- 最少连接:优先分配给当前连接数最少的机器,负载更均衡。
# Nginx 负载均衡配置
upstream web_servers {
least_conn; # 最少连接算法
server 192.168.1.11:8080 weight=3;
server 192.168.1.12:8080 weight=2;
server 192.168.1.13:8080 weight=1;
keepalive 32; # 保持长连接,减少握手开销
}
server {
location / {
proxy_pass http://web_servers;
proxy_set_header X-Real-IP $remote_addr;
}
}
健康检查是负载均衡的关键配套。均衡器要定期探测后端机器是否存活,发现故障立即剔除,避免把请求转发到死机节点。同时保留足够的冗余容量——平时 4 台机器跑 30% 负载,流量来了才有余量承接。尧图建议冗余度不低于 50%,宁可平时闲置也不能关键时刻掉链子。
二、限流与降级
流量超过系统承载能力时,与其让所有请求都慢死,不如主动丢弃部分请求保住核心功能。限流是控制进入系统的请求速率,降级是关闭非核心功能释放资源。两者配合,能在洪峰中保住系统不崩。
// 令牌桶限流:固定速率生成令牌,请求需拿到令牌才处理
class TokenBucket {
constructor(capacity, rate) {
this.capacity = capacity; // 桶容量(突发上限)
this.rate = rate; // 令牌生成速率(每秒)
this.tokens = capacity;
this.lastTime = Date.now();
}
allow() {
const now = Date.now();
const elapsed = (now - this.lastTime) / 1000;
this.tokens = Math.min(this.capacity, this.tokens + elapsed * this.rate);
this.lastTime = now;
if (this.tokens >= 1) {
this.tokens -= 1;
return true; // 放行
}
return false; // 限流
}
}
// 降级策略:商品详情页库存查询降级
function getProductDetail(id) {
const product = cache.get(`product:${id}`);
if (!product) return null;
if (system.isHighLoad()) {
product.stock = '库存查询繁忙,请稍后'; // 降级:不实时查库存
} else {
product.stock = db.queryStock(id);
}
return product;
}
限流算法常用两种:令牌桶允许突发流量(桶里攒的令牌可瞬间消耗),适合允许短时脉冲的场景;漏桶以恒定速率处理请求,把突发流量抹平,适合需要严格匀速的场景。降级要提前规划好"哪些功能可以关、关了返回什么默认值",到关键时刻一键切换,而不是临时手忙脚乱。
三、异步削峰与数据库扩展
有些操作不需要即时返回结果,比如发邮件、生成报表、记录日志。把它们从主流程剥离,丢进消息队列异步处理,能瞬间释放主链路压力。Redis、RabbitMQ、Kafka 都是常用队列,尧图在企业官网里偏好用 Redis 做轻量队列——简单够用、运维成本低。
// 同步发邮件 vs 异步发邮件
// 同步:用户提交表单后等邮件发完才返回,慢
function submitFormSync(data) {
db.save(data);
mailer.send(data.email); // 耗时2-3秒
return success();
}
// 异步:存库后丢队列立即返回,邮件后台慢慢发
function submitFormAsync(data) {
db.save(data);
queue.push('send_mail', { email: data.email }); // 毫秒级
return success();
}
// 消费者:后台 worker 不断从队列取任务执行
queue.consume('send_mail', (task) => {
mailer.send(task.email);
});
数据库是并发链路最脆弱的一环。单机数据库扛不住时,先做读写分离——主库写、从库读,绝大多数官网读多写少,一主两从能扛住相当可观的流量。再不够就分库分表,按用户 ID 或时间拆分数据到不同库。但分库分表会带来分布式事务、跨库 JOIN 等复杂问题,尧图的原则是"能不分就不分,先用缓存和读写分离顶住,确实撑不住再分"。高并发是一场系统工程,提前规划、留足余量、降级兜底,才能在流量洪峰中稳如磐石。