互联网业务的核心挑战之一,是应对瞬时爆发的海量流量。从电商秒杀、直播带货,到春节红包、热点事件舆情,脉冲式的流量峰值往往会超出系统常规承载能力的数十倍甚至上百倍,稍有不慎就会引发系统雪崩、服务不可用,最终造成巨大的业务损失。
高并发架构的核心目标,就是让系统在海量流量冲击下,依然保持稳定可用、响应及时。而流量削峰、异步化、水平扩展,正是支撑高并发系统的三大核心支柱,三者不是孤立存在的,而是互为补充、协同生效的组合拳。
一、流量削峰:从源头管控流量冲击,把脉冲洪水转为平稳溪流
底层核心逻辑
系统的处理能力本质上是线性的。无论是CPU的计算能力、内存的读写速度,还是数据库的IO吞吐量、磁盘的随机读写性能,都存在明确的物理上限,且在固定配置下,单位时间内能够处理的请求量是相对稳定的。
而互联网业务的流量特征,却是高度脉冲化的。比如秒杀活动开始的瞬间,数万甚至数百万用户会同时发起请求,瞬时QPS会瞬间飙升到日常的上百倍。如果不对流量做任何管控,直接将所有请求放通到后端服务,会直接打满系统的所有资源,导致请求堆积、响应超时,甚至引发数据库宕机、服务雪崩,最终所有用户都无法正常使用。
流量削峰的核心本质,就是通过一系列技术手段,将瞬时的脉冲流量,转化为系统可以平稳承载的匀速流量,拉长流量的处理周期,摊平峰值压力,让系统始终运行在安全的承载范围内,同时尽可能处理更多的合法用户请求。
核心实现方案
我们采用从前端到后端的层层设防模式,实现全链路的流量削峰管控。
1. 前端层削峰:用户侧拦截无效流量,从源头减少请求量
前端是流量的第一入口,也是成本最低、效果最明显的削峰环节。核心目标是拦截掉大量无效、重复的请求,避免这些请求进入后端系统,占用宝贵的服务器资源。
核心实现手段:
- 按钮状态管控:用户点击提交按钮后,立即置灰按钮,禁止重复点击,避免用户因手抖、网络卡顿等原因发起重复请求。
- 验证码/人机校验:通过图形验证码、滑块验证、短信验证码等方式,将同时发起的请求在时间维度上打散,大幅降低瞬时峰值。
- 排队机制:对高并发场景的请求,在前端实现虚拟排队,用户进入排队队列,按顺序放行,避免所有请求同时到达后端。
2. 网关层削峰:全局限流管控,守住系统的第一道大门
网关是所有后端服务的统一入口,是流量进入业务系统的最后一道集中管控关口。网关层削峰的核心,是通过限流算法,控制进入系统的总请求量,超过系统承载能力的请求直接拒绝,避免流量冲击后端服务。
主流限流算法对比:
| 限流算法 | 核心原理 | 优点 | 缺点 | 适用场景 |
| 固定窗口限流 | 将时间划分为固定大小的窗口,每个窗口内限制最大请求数 | 实现简单,易于理解 | 存在窗口边界溢出问题,两个相邻窗口的交界处可能出现超阈值流量 | 对限流精度要求不高的常规场景 |
| 滑动窗口限流 | 将固定窗口拆分为多个小的时间格子,每次滑动一个格子统计请求数 | 解决了固定窗口的边界问题,限流精度更高 | 实现相对复杂,统计成本更高 | 对限流精度要求高的核心场景 |
| 漏桶算法 | 请求进入漏桶,漏桶以固定的速率匀速流出处理请求,溢出的请求直接丢弃 | 严格控制流出速率,绝对平滑流量 | 无法应对突发流量,即使系统有空闲资源,也只能按固定速率处理 | 消息消费、数据同步等需要严格控制速率的场景 |
| 令牌桶算法 | 系统以固定速率向令牌桶中添加令牌,请求进入时需要先获取令牌,获取成功才能放行,桶满时不再添加新令牌 | 可以应对突发流量,桶内积攒的令牌可以支持瞬时的高并发,同时也能控制平均速率 | 实现相对复杂 | 互联网业务的网关限流、API接口限流等绝大多数高并发场景 |
令牌桶算法执行流程:
网关层限流落地实现,基于Spring Cloud Gateway结合Redis实现分布式限流:
核心maven依赖:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.2.4</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<version>4.1.4</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
<version>3.2.4</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.32</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.5.0</version>
</dependency>
</dependencies>
application.yml配置:
spring:
cloud:
gateway:
routes:
- id: seckill_route
uri: lb://seckill-service
predicates:
- Path=/seckill/**
filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 1000
redis-rate-limiter.burstCapacity: 2000
redis-rate-limiter.requestedTokens: 1
key-resolver: "#{@userKeyResolver}"
data:
redis:
host: 127.0.0.1
port: 6379
database: 0
限流规则配置类:
package com.jam.demo.gateway.config;
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;
import reactor.core.publisher.Mono;
/**
* 网关限流规则配置
* @author ken
*/
@Configuration
public class GatewayRateLimitConfig {
/**
* 按用户ID维度限流
* @return 限流key解析器
*/
@Bean
public KeyResolver userKeyResolver() {
return exchange -> {
String userId = exchange.getRequest().getHeaders().getFirst("userId");
if (!StringUtils.hasText(userId)) {
userId = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
}
return Mono.just(userId);
};
}
}
3. 应用层削峰:业务层缓冲管控,精细化处理流量
网关层的限流是全局限流,而应用层削峰是针对具体的业务场景,做更精细化的流量管控和缓冲。核心思路是将请求先放入缓冲队列,再通过后台线程匀速消费,避免瞬时请求直接打满数据库等底层资源。
基于内存阻塞队列的秒杀场景削峰实现:
package com.jam.demo.seckill.service;
import com.jam.demo.seckill.entity.SeckillOrder;
import com.jam.demo.seckill.mapper.SeckillOrderMapper;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 秒杀服务-应用层削峰实现
* @author ken
*/
@Slf4j
@Service
public class SeckillService {
private final LinkedBlockingQueue<SeckillOrder> seckillQueue = new LinkedBlockingQueue<>(10000);
private final AtomicInteger stockCount = new AtomicInteger(0);
@Resource
private SeckillOrderMapper seckillOrderMapper;
@Resource
private TransactionTemplate transactionTemplate;
/**
* 初始化库存数量
*/
@PostConstruct
public void initStock() {
stockCount.set(1000);
new Thread(this::processSeckillQueue, "seckill-consumer-thread").start();
log.info("秒杀库存初始化完成,剩余库存:{}", stockCount.get());
}
/**
* 接收秒杀请求,放入缓冲队列
* @param seckillOrder 秒杀订单信息
* @return 提交结果
*/
public String submitSeckillOrder(SeckillOrder seckillOrder) {
if (stockCount.get() <= 0) {
return "活动已结束,商品已售罄";
}
boolean offerResult = seckillQueue.offer(seckillOrder);
if (!offerResult) {
return "当前排队人数过多,请稍后再试";
}
return "订单已提交,正在排队处理中,请稍后查看订单状态";
}
/**
* 匀速消费秒杀队列,处理订单
*/
private void processSeckillQueue() {
while (true) {
try {
SeckillOrder seckillOrder = seckillQueue.take();
int currentStock = stockCount.decrementAndGet();
if (currentStock < 0) {
stockCount.incrementAndGet();
log.warn("用户{}秒杀失败,库存不足", seckillOrder.getUserId());
continue;
}
Boolean saveResult = transactionTemplate.execute(status -> {
try {
seckillOrderMapper.insert(seckillOrder);
return Boolean.TRUE;
} catch (Exception e) {
status.setRollbackOnly();
log.error("订单入库失败,用户ID:{}", seckillOrder.getUserId(), e);
return Boolean.FALSE;
}
});
if (Boolean.FALSE.equals(saveResult)) {
stockCount.incrementAndGet();
}
Thread.sleep(1);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.error("秒杀队列消费线程被中断", e);
break;
} catch (Exception e) {
log.error("秒杀订单处理异常", e);
}
}
}
}
4. 分布式限流:跨实例的全局限流,确保集群整体流量可控
集群部署场景下,单实例限流无法控制集群总流量,需要基于Redis+Lua实现分布式限流,利用Redis单线程模型保证限流操作的原子性,避免并发统计误差。
滑动窗口限流Lua脚本:
local key = KEYS[1]
local now = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local limit = tonumber(ARGV[3])
local clearBefore = now - window
redis.call('ZREMRANGEBYSCORE', key, 0, clearBefore)
local count = redis.call('ZCARD', key)
if count < limit then
redis.call('ZADD', key, now, now .. '-' .. math.random())
redis.call('EXPIRE', key, window / 1000)
return 1
end
return 0
分布式限流服务实现:
package com.jam.demo.common.service;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.List;
/**
* 分布式限流服务
* @author ken
*/
@Slf4j
@Service
public class DistributedRateLimitService {
@Resource
private StringRedisTemplate stringRedisTemplate;
private static final DefaultRedisScript<Long> RATE_LIMIT_SCRIPT;
static {
RATE_LIMIT_SCRIPT = new DefaultRedisScript<>();
RATE_LIMIT_SCRIPT.setScriptText("""
local key = KEYS[1]
local now = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local limit = tonumber(ARGV[3])
local clearBefore = now - window
redis.call('ZREMRANGEBYSCORE', key, 0, clearBefore)
local count = redis.call('ZCARD', key)
if count < limit then
redis.call('ZADD', key, now, now .. '-' .. math.random())
redis.call('EXPIRE', key, window / 1000)
return 1
end
return 0
""");
RATE_LIMIT_SCRIPT.setResultType(Long.class);
}
/**
* 尝试获取限流令牌
* @param limitKey 限流唯一key
* @param windowMs 滑动窗口时间,单位毫秒
* @param limitCount 窗口内最大请求数
* @return 是否获取成功
*/
public boolean tryAcquire(String limitKey, long windowMs, int limitCount) {
List<String> keys = Collections.singletonList(limitKey);
long now = System.currentTimeMillis();
try {
Long result = stringRedisTemplate.execute(RATE_LIMIT_SCRIPT, keys,
String.valueOf(now), String.valueOf(windowMs), String.valueOf(limitCount));
return result != null && result == 1;
} catch (Exception e) {
log.error("分布式限流执行异常,key:{}", limitKey, e);
return true;
}
}
}
接口使用示例:
package com.jam.demo.seckill.controller;
import com.jam.demo.common.service.DistributedRateLimitService;
import com.jam.demo.seckill.entity.SeckillOrder;
import com.jam.demo.seckill.service.SeckillService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 秒杀接口控制器
* @author ken
*/
@Tag(name = "秒杀接口", description = "秒杀相关操作接口")
@RestController
@RequestMapping("/seckill")
public class SeckillController {
@Resource
private SeckillService seckillService;
@Resource
private DistributedRateLimitService distributedRateLimitService;
@Operation(summary = "秒杀下单", description = "提交秒杀订单请求")
@PostMapping("/submit")
public String submitSeckill(@RequestBody SeckillOrder seckillOrder) {
boolean acquireResult = distributedRateLimitService.tryAcquire("seckill:limit", 1000, 5000);
if (!acquireResult) {
return "当前系统繁忙,请稍后再试";
}
return seckillService.submitSeckillOrder(seckillOrder);
}
}
二、异步化:解耦流量峰值,指数级提升系统吞吐量
底层核心逻辑
同步调用模式下,一个请求的完整处理流程是串行执行所有业务环节,上游服务必须等待下游服务返回结果之后,才能继续执行后续操作,最终给用户返回响应。这种模式下,整个请求的响应时间是所有环节的耗时之和,同时每个环节都会占用服务的线程资源,一旦某个下游服务响应变慢,会导致上游服务的线程池被打满,无法处理新的请求,最终引发服务级联雪崩。
异步化的核心本质,是对业务流程进行拆解,区分「必须同步等待完成的核心链路」和「可以延后处理的非核心链路」,将非核心链路从同步流程中剥离出来,通过异步的方式进行处理。核心链路只需要完成最关键的操作,就可以快速给用户返回响应,非核心链路则由其他线程或服务在后台异步处理,既大幅降低了用户的等待时间,又极大提升了系统的吞吐量,同时实现了服务之间的解耦,避免了级联故障的发生。
核心概念区分:异步化≠多线程
- 多线程的核心是并行执行,将一个流程拆分为多个子任务,用多个线程并行执行,最终还是要等待所有子任务执行完成,才能返回结果,本质上还是同步等待的模式。
- 异步化的核心是不需要等待结果,核心流程完成后直接返回,非核心流程的执行结果不影响主流程的响应,是完全的解耦模式。
核心实现方案
1. 本地异步化:单服务内的流程解耦,提升接口响应速度
本地异步化适用于单服务内部的非核心流程,基于JDK17的CompletableFuture和虚拟线程实现,大幅降低异步线程的调度成本。
本地异步化代码实现:
package com.jam.demo.order.service;
import com.alibaba.fastjson2.JSON;
import com.jam.demo.order.entity.OrderInfo;
import com.jam.demo.order.mapper.OrderInfoMapper;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
/**
* 订单服务-本地异步化实现
* @author ken
*/
@Slf4j
@Service
public class OrderService {
@Resource
private OrderInfoMapper orderInfoMapper;
@Resource
private TransactionTemplate transactionTemplate;
@Resource
private SmsService smsService;
@Resource
private PointService pointService;
@Resource
private StatisticService statisticService;
private static final Executors.VirtualThreadExecutor VIRTUAL_THREAD_EXECUTOR = (Executors.VirtualThreadExecutor) Executors.newVirtualThreadPerTaskExecutor();
/**
* 创建订单
* @param orderInfo 订单信息
* @return 订单创建结果
*/
public String createOrder(OrderInfo orderInfo) {
Boolean saveResult = transactionTemplate.execute(status -> {
try {
orderInfoMapper.insert(orderInfo);
return Boolean.TRUE;
} catch (Exception e) {
status.setRollbackOnly();
log.error("订单入库失败,订单信息:{}", JSON.toJSONString(orderInfo), e);
return Boolean.FALSE;
}
});
if (Boolean.FALSE.equals(saveResult)) {
return "订单创建失败,请稍后再试";
}
CompletableFuture.runAsync(() -> {
try {
smsService.sendOrderSuccessSms(orderInfo.getUserId(), orderInfo.getOrderNo());
} catch (Exception e) {
log.error("发送订单短信失败,订单号:{}", orderInfo.getOrderNo(), e);
}
}, VIRTUAL_THREAD_EXECUTOR);
CompletableFuture.runAsync(() -> {
try {
pointService.addUserPoint(orderInfo.getUserId(), orderInfo.getOrderAmount().intValue());
} catch (Exception e) {
log.error("增加用户积分失败,订单号:{}", orderInfo.getOrderNo(), e);
}
}, VIRTUAL_THREAD_EXECUTOR);
CompletableFuture.runAsync(() -> {
try {
statisticService.updateOrderStatistic(orderInfo.getGoodsId(), orderInfo.getOrderAmount());
} catch (Exception e) {
log.error("更新销售统计数据失败,订单号:{}", orderInfo.getOrderNo(), e);
}
}, VIRTUAL_THREAD_EXECUTOR);
return "订单创建成功,订单号:" + orderInfo.getOrderNo();
}
}
2. 分布式异步化:跨服务解耦,削峰填谷,避免级联故障
微服务架构下,跨服务的流程解耦需要基于消息队列实现分布式异步化,核心载体是RocketMQ,实现服务解耦、异步处理、流量削峰三大核心价值。
同步vs异步调用架构对比:
核心maven依赖:
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.3.0</version>
</dependency>
application.yml配置:
rocketmq:
name-server: 127.0.0.1:9876
producer:
group: order-producer-group
send-message-timeout: 3000
消息实体类:
package com.jam.demo.order.entity;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 订单消息实体
* @author ken
*/
@Data
public class OrderMessage implements Serializable {
private static final long serialVersionUID = 1L;
private String orderNo;
private Long userId;
private Long goodsId;
private BigDecimal orderAmount;
private LocalDateTime orderTime;
}
消息生产者实现:
package com.jam.demo.order.service;
import com.alibaba.fastjson2.JSON;
import com.jam.demo.order.entity.OrderInfo;
import com.jam.demo.order.entity.OrderMessage;
import com.jam.demo.order.mapper.OrderInfoMapper;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;
/**
* 订单服务-分布式异步化实现
* @author ken
*/
@Slf4j
@Service
public class OrderMqService {
private static final String ORDER_TOPIC = "order_success_topic";
@Resource
private OrderInfoMapper orderInfoMapper;
@Resource
private TransactionTemplate transactionTemplate;
@Resource
private RocketMQTemplate rocketMQTemplate;
/**
* 创建订单,发送分布式异步消息
* @param orderInfo 订单信息
* @return 订单创建结果
*/
public String createOrderWithMq(OrderInfo orderInfo) {
Boolean saveResult = transactionTemplate.execute(status -> {
try {
orderInfoMapper.insert(orderInfo);
return Boolean.TRUE;
} catch (Exception e) {
status.setRollbackOnly();
log.error("订单入库失败,订单信息:{}", JSON.toJSONString(orderInfo), e);
return Boolean.FALSE;
}
});
if (Boolean.FALSE.equals(saveResult)) {
return "订单创建失败,请稍后再试";
}
OrderMessage orderMessage = new OrderMessage();
BeanUtils.copyProperties(orderInfo, orderMessage);
try {
rocketMQTemplate.syncSend(ORDER_TOPIC, orderMessage);
log.info("订单消息发送成功,订单号:{}", orderInfo.getOrderNo());
} catch (Exception e) {
log.error("订单消息发送失败,订单号:{}", orderInfo.getOrderNo(), e);
}
return "订单创建成功,订单号:" + orderInfo.getOrderNo();
}
}
消息消费者实现:
package com.jam.demo.logistics.service;
import com.alibaba.fastjson2.JSON;
import com.jam.demo.order.entity.OrderMessage;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Service;
/**
* 订单消息消费者-物流服务
* @author ken
*/
@Slf4j
@Service
@RocketMQMessageListener(
topic = "order_success_topic",
consumerGroup = "logistics-consumer-group"
)
public class OrderLogisticsConsumer implements RocketMQListener<OrderMessage> {
@Override
public void onMessage(OrderMessage orderMessage) {
log.info("收到订单消息,订单号:{},消息内容:{}", orderMessage.getOrderNo(), JSON.toJSONString(orderMessage));
try {
this.createLogisticsOrder(orderMessage);
} catch (Exception e) {
log.error("物流单创建失败,订单号:{}", orderMessage.getOrderNo(), e);
throw e;
}
}
private void createLogisticsOrder(OrderMessage orderMessage) {
log.info("物流单创建成功,订单号:{}", orderMessage.getOrderNo());
}
}
3. 异步化的核心保障机制
异步化场景下,必须实现幂等性、重试、死信处理三大保障机制,确保数据一致性。
幂等性处理服务实现:
package com.jam.demo.common.service;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
/**
* 幂等性处理服务
* @author ken
*/
@Slf4j
@Service
public class IdempotentService {
@Resource
private StringRedisTemplate stringRedisTemplate;
private static final String IDEMPOTENT_KEY_PREFIX = "idempotent:";
/**
* 检查是否重复消费
* @param messageId 唯一消息ID
* @param expireTime 过期时间
* @param timeUnit 时间单位
* @return 是否首次消费
*/
public boolean checkAndMark(String messageId, long expireTime, TimeUnit timeUnit) {
String key = IDEMPOTENT_KEY_PREFIX + messageId;
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", expireTime, timeUnit);
return Boolean.TRUE.equals(result);
}
}
带幂等性的消费者实现:
package com.jam.demo.logistics.service;
import com.jam.demo.common.service.IdempotentService;
import com.jam.demo.order.entity.OrderMessage;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
/**
* 订单消息消费者-带幂等性处理
* @author ken
*/
@Slf4j
@Service
@RocketMQMessageListener(
topic = "order_success_topic",
consumerGroup = "logistics-consumer-group"
)
public class OrderLogisticsIdempotentConsumer implements RocketMQListener<OrderMessage> {
@Resource
private IdempotentService idempotentService;
@Override
public void onMessage(OrderMessage orderMessage) {
String messageId = orderMessage.getOrderNo();
boolean isFirstConsume = idempotentService.checkAndMark(messageId, 24, TimeUnit.HOURS);
if (!isFirstConsume) {
log.warn("重复消费消息,订单号:{},直接跳过", orderMessage.getOrderNo());
return;
}
log.info("首次消费订单消息,订单号:{}", orderMessage.getOrderNo());
}
}
三、水平扩展:打破单机瓶颈,实现系统能力的线性增长
底层核心逻辑
任何单机系统的处理能力都存在明确的物理天花板。单机配置升级的成本是指数级增长的,而性能提升却是亚线性的,当配置达到一定级别后,再升级配置的收益会越来越低,这就是垂直扩展的天花板。
水平扩展的核心本质,是通过增加服务器的数量,来线性提升系统的整体处理能力。而水平扩展能够实现的核心前提,是服务的无状态化——服务本身不存储任何和请求相关的上下文信息,所有状态数据都存储在分布式中间件中,任何一个服务实例接收到请求后,只需要根据请求携带的参数就可以完成处理,请求可以被分发到集群中的任意一个实例,实现真正的线性扩容。
核心概念区分:水平扩展vs垂直扩展
| 扩展方式 | 核心实现 | 性能增长 | 成本变化 | 天花板 | 适用场景 |
| 垂直扩展 | 升级单机的硬件配置 | 亚线性增长,配置越高,性能提升幅度越小 | 指数级增长,高端硬件成本极高 | 有明确的物理天花板 | 中小流量场景,无法做分布式改造的单体应用 |
| 水平扩展 | 增加服务器的数量,通过集群提升整体处理能力 | 线性增长,机器数量与性能提升成正比 | 线性增长,普通服务器成本极低 | 理论上无天花板,受限于分布式存储的承载能力 | 高并发互联网业务,微服务架构,海量流量场景 |
完整落地步骤
1. 应用层无状态化改造,实现水平扩展的基础
无状态化是水平扩展的核心前提,改造核心要点:
- 移除本地Session存储,基于Spring Session + Redis实现集群会话共享
- 移除本地内存缓存,所有缓存数据存储到分布式Redis集群
- 所有请求携带完整上下文参数,服务不存储本地请求上下文
- 定时任务改为分布式定时任务,文件存储改为分布式对象存储
核心maven依赖:
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
<version>3.2.3</version>
</dependency>
application.yml配置:
spring:
session:
store-type: redis
timeout: 1800
data:
redis:
host: 127.0.0.1
port: 6379
database: 0
2. 服务层水平扩展,通过注册中心实现自动发现与负载均衡
基于Spring Cloud Alibaba Nacos实现服务注册与发现,服务实例启动后自动注册到注册中心,消费者自动获取可用实例列表,实现负载均衡与无缝扩容。
核心maven依赖:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2023.0.1.2</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>4.1.4</version>
</dependency>
application.yml配置:
spring:
application:
name: order-service
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
namespace: public
group: DEFAULT_GROUP
Feign客户端实现:
package com.jam.demo.user.feign;
import com.jam.demo.user.entity.OrderInfo;
import io.swagger.v3.oas.annotations.Operation;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
/**
* 订单服务Feign客户端
* @author ken
*/
@FeignClient(name = "order-service")
public interface OrderFeignClient {
@Operation(summary = "创建订单")
@PostMapping("/order/create")
String createOrder(@RequestBody OrderInfo orderInfo);
}
3. 数据层水平扩展,打破数据库的性能瓶颈
数据层水平扩展分为两个核心阶段:读写分离、分库分表。
读写分离架构:
基于MyBatis Plus实现读写分离,核心maven依赖:
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.7</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>4.3.0</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.4.0</version>
<scope>runtime</scope>
</dependency>
application.yml配置:
spring:
datasource:
dynamic:
primary: master
strict: false
datasource:
master:
url: jdbc:mysql://127.0.0.1:3306/jam_demo?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
slave_1:
url: jdbc:mysql://127.0.0.1:3307/jam_demo?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
slave_2:
url: jdbc:mysql://127.0.0.1:3308/jam_demo?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
load-balancer: round_robin
Mapper接口实现:
package com.jam.demo.order.mapper;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jam.demo.order.entity.OrderInfo;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 订单Mapper接口
* @author ken
*/
public interface OrderInfoMapper extends BaseMapper<OrderInfo> {
@DS("master")
int insertOrder(OrderInfo orderInfo);
@DS("slave")
List<OrderInfo> selectOrderByUserId(@Param("userId") Long userId);
}
分库分表实现,基于Sharding-JDBC实现水平分表,maven依赖:
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
<version>5.5.0</version>
</dependency>
application.yml分表规则配置:
spring:
shardingsphere:
datasource:
names: order_db
order_db:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://127.0.0.1:3306/order_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: root
password: root
rules:
sharding:
tables:
order_info:
actual-data-nodes: order_db.order_info_$->{0..7}
table-strategy:
standard:
sharding-column: user_id
sharding-algorithm-name: order_inline
sharding-algorithms:
order_inline:
type: INLINE
props:
algorithm-expression: order_info_$->{user_id % 8}
props:
sql-show: true
MySQL分表建表语句:
CREATE TABLE `order_info_0` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`order_no` varchar(64) NOT NULL COMMENT '订单号',
`user_id` bigint NOT NULL COMMENT '用户ID',
`goods_id` bigint NOT NULL COMMENT '商品ID',
`order_amount` decimal(10,2) NOT NULL COMMENT '订单金额',
`order_status` tinyint NOT NULL DEFAULT '0' COMMENT '订单状态',
`order_time` datetime NOT NULL COMMENT '下单时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_order_no` (`order_no`),
KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表0';
4. 中间件水平扩展,实现全链路无瓶颈架构
- Redis集群:采用Redis Cluster集群模式,实现数据分片存储,通过增加节点线性提升存储能力和并发吞吐量
- RocketMQ集群:采用多Master多Slave模式,NameServer集群部署,Broker集群分片存储消息,通过增加Broker节点线性提升消息处理能力
四、三板斧组合落地实战:秒杀系统完整实现
高并发场景下,三大方案需要组合使用,形成完整的防护体系。这里以电商秒杀系统为例,完整落地流量削峰、异步化、水平扩展三大核心能力。
秒杀系统整体架构:
MySQL核心表结构:
CREATE TABLE `seckill_goods` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`goods_id` bigint NOT NULL COMMENT '商品ID',
`goods_name` varchar(128) NOT NULL COMMENT '商品名称',
`seckill_price` decimal(10,2) NOT NULL COMMENT '秒杀价格',
`stock_count` int NOT NULL COMMENT '库存数量',
`start_time` datetime NOT NULL COMMENT '秒杀开始时间',
`end_time` datetime NOT NULL COMMENT '秒杀结束时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_goods_id` (`goods_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='秒杀商品表';
CREATE TABLE `seckill_order` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`order_no` varchar(64) NOT NULL COMMENT '订单号',
`user_id` bigint NOT NULL COMMENT '用户ID',
`goods_id` bigint NOT NULL COMMENT '商品ID',
`order_amount` decimal(10,2) NOT NULL COMMENT '订单金额',
`order_status` tinyint NOT NULL DEFAULT '0' COMMENT '订单状态',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_order_no` (`order_no`),
UNIQUE KEY `uk_user_goods` (`user_id`,`goods_id`),
KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='秒杀订单表';
秒杀核心服务实现:
package com.jam.demo.seckill.service;
import com.jam.demo.seckill.entity.SeckillGoods;
import com.jam.demo.seckill.entity.SeckillOrder;
import com.jam.demo.seckill.mapper.SeckillGoodsMapper;
import com.jam.demo.seckill.mapper.SeckillOrderMapper;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import java.util.UUID;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
/**
* 秒杀核心服务
* @author ken
*/
@Slf4j
@Service
public class SeckillCoreService {
private static final String SECKILL_STOCK_KEY_PREFIX = "seckill:stock:";
private static final String SECKILL_USER_ORDER_KEY_PREFIX = "seckill:user:order:";
private static final String SECKILL_ORDER_TOPIC = "seckill_order_topic";
private final LinkedBlockingQueue<SeckillOrder> seckillQueue = new LinkedBlockingQueue<>(20000);
@Resource
private StringRedisTemplate stringRedisTemplate;
@Resource
private SeckillGoodsMapper seckillGoodsMapper;
@Resource
private SeckillOrderMapper seckillOrderMapper;
@Resource
private RocketMQTemplate rocketMQTemplate;
@Resource
private IdempotentService idempotentService;
@PostConstruct
public void initSeckillStock() {
SeckillGoods seckillGoods = seckillGoodsMapper.selectById(1);
if (!ObjectUtils.isEmpty(seckillGoods)) {
String stockKey = SECKILL_STOCK_KEY_PREFIX + seckillGoods.getGoodsId();
stringRedisTemplate.opsForValue().set(stockKey, seckillGoods.getStockCount().toString());
log.info("秒杀商品库存加载完成,商品ID:{},库存数量:{}", seckillGoods.getGoodsId(), seckillGoods.getStockCount());
}
new Thread(this::processSeckillQueue, "seckill-order-consumer").start();
}
/**
* 秒杀下单核心方法
* @param userId 用户ID
* @param goodsId 商品ID
* @return 秒杀结果
*/
public String doSeckill(Long userId, Long goodsId) {
if (ObjectUtils.isEmpty(userId) || ObjectUtils.isEmpty(goodsId)) {
return "参数错误,请重试";
}
String userOrderKey = SECKILL_USER_ORDER_KEY_PREFIX + goodsId + ":" + userId;
Boolean hasOrder = stringRedisTemplate.hasKey(userOrderKey);
if (Boolean.TRUE.equals(hasOrder)) {
return "您已经参与过本次秒杀,请勿重复下单";
}
String stockKey = SECKILL_STOCK_KEY_PREFIX + goodsId;
Long remainStock = stringRedisTemplate.opsForValue().decrement(stockKey);
if (remainStock == null || remainStock < 0) {
stringRedisTemplate.opsForValue().increment(stockKey);
return "商品已售罄,秒杀结束";
}
SeckillOrder seckillOrder = new SeckillOrder();
seckillOrder.setOrderNo(UUID.randomUUID().toString().replace("-", ""));
seckillOrder.setUserId(userId);
seckillOrder.setGoodsId(goodsId);
SeckillGoods seckillGoods = seckillGoodsMapper.selectById(goodsId);
seckillOrder.setOrderAmount(seckillGoods.getSeckillPrice());
seckillOrder.setOrderStatus(0);
boolean offerResult = seckillQueue.offer(seckillOrder);
if (!offerResult) {
stringRedisTemplate.opsForValue().increment(stockKey);
return "当前排队人数过多,请稍后再试";
}
stringRedisTemplate.opsForValue().set(userOrderKey, seckillOrder.getOrderNo(), 24, TimeUnit.HOURS);
return "秒杀请求已提交,正在处理中,请稍后查看订单状态";
}
private void processSeckillQueue() {
while (true) {
try {
SeckillOrder seckillOrder = seckillQueue.take();
log.info("开始处理秒杀订单,订单号:{}", seckillOrder.getOrderNo());
boolean isFirst = idempotentService.checkAndMark(seckillOrder.getOrderNo(), 24, TimeUnit.HOURS);
if (!isFirst) {
log.warn("重复处理秒杀订单,订单号:{}", seckillOrder.getOrderNo());
continue;
}
rocketMQTemplate.syncSend(SECKILL_ORDER_TOPIC, seckillOrder);
log.info("秒杀订单消息发送成功,订单号:{}", seckillOrder.getOrderNo());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.error("秒杀队列消费线程被中断", e);
break;
} catch (Exception e) {
log.error("秒杀订单处理异常", e);
}
}
}
}
秒杀订单消费者实现:
package com.jam.demo.seckill.service;
import com.jam.demo.seckill.entity.SeckillOrder;
import com.jam.demo.seckill.mapper.SeckillOrderMapper;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;
/**
* 秒杀订单消费者
* @author ken
*/
@Slf4j
@Service
@RocketMQMessageListener(
topic = "seckill_order_topic",
consumerGroup = "seckill-order-consumer-group"
)
public class SeckillOrderConsumer implements RocketMQListener<SeckillOrder> {
@Resource
private SeckillOrderMapper seckillOrderMapper;
@Resource
private TransactionTemplate transactionTemplate;
@Override
public void onMessage(SeckillOrder seckillOrder) {
log.info("收到秒杀订单消息,订单号:{}", seckillOrder.getOrderNo());
try {
Boolean saveResult = transactionTemplate.execute(status -> {
try {
seckillOrderMapper.insert(seckillOrder);
return Boolean.TRUE;
} catch (Exception e) {
status.setRollbackOnly();
log.error("秒杀订单入库失败,订单号:{}", seckillOrder.getOrderNo(), e);
return Boolean.FALSE;
}
});
if (Boolean.TRUE.equals(saveResult)) {
log.info("秒杀订单入库成功,订单号:{}", seckillOrder.getOrderNo());
}
} catch (Exception e) {
log.error("秒杀订单消费异常,订单号:{}", seckillOrder.getOrderNo(), e);
throw e;
}
}
}
秒杀接口控制器:
package com.jam.demo.seckill.controller;
import com.jam.demo.common.service.DistributedRateLimitService;
import com.jam.demo.seckill.service.SeckillCoreService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* 秒杀核心接口控制器
* @author ken
*/
@Tag(name = "秒杀核心接口", description = "秒杀业务核心操作接口")
@RestController
@RequestMapping("/seckill/core")
public class SeckillCoreController {
@Resource
private SeckillCoreService seckillCoreService;
@Resource
private DistributedRateLimitService distributedRateLimitService;
@Operation(summary = "秒杀下单", description = "提交秒杀请求,参与商品秒杀")
@PostMapping("/do")
public String doSeckill(
@Parameter(description = "用户ID", required = true) @RequestParam Long userId,
@Parameter(description = "商品ID", required = true) @RequestParam Long goodsId
) {
boolean acquireResult = distributedRateLimitService.tryAcquire("seckill:core:limit", 1000, 10000);
if (!acquireResult) {
return "当前系统繁忙,请稍后再试";
}
return seckillCoreService.doSeckill(userId, goodsId);
}
}
五、避坑指南与易混淆点区分
常见坑与避坑方案
流量削峰常见坑
- 固定窗口限流的边界溢出问题:相邻窗口交界处可能出现超阈值流量,避坑方案是采用滑动窗口限流或令牌桶算法
- 限流粒度太粗导致正常用户被误伤:避坑方案是采用全局限流+用户维度限流+IP维度限流的多级限流策略
- 无降级策略导致用户体验极差:避坑方案是限流后返回友好提示或排队页面,而非直接报错
异步化常见坑
- 消息丢失:避坑方案是采用消息持久化、生产者确认机制、消费者手动确认机制
- 消息重复消费导致数据错乱:避坑方案是消费者必须实现幂等性处理,基于唯一消息ID去重
- 异步事务不一致:避坑方案是采用事务消息机制,或通过定时任务对账修复不一致数据
- 线程池参数不合理导致OOM:避坑方案是使用有界队列,合理设置线程池参数,配置降级拒绝策略
水平扩展常见坑
- 服务有状态导致扩容异常:避坑方案是严格实现无状态化,所有状态数据存储到分布式中间件
- 应用层扩容但数据库成为瓶颈:避坑方案是提前做好数据层扩展规划,先做读写分离,再做分库分表
- 缓存单点故障引发数据库雪崩:避坑方案是采用Redis集群,配置缓存降级、熔断策略
易混淆点明确区分
- 限流vs削峰:限流是控制进入系统的总流量,超阈值直接拒绝,核心是「控总量」;削峰是将瞬时峰值流量摊平,拉长处理时间,尽量处理所有合法请求,核心是「平峰值」
- 异步化vs多线程:多线程是并行执行任务,最终仍需等待所有任务完成,本质是同步模式;异步化是核心流程完成后直接返回,无需等待非核心流程结果,完全解耦
- 水平扩展vs垂直扩展:水平扩展是增加机器数量,线性提升性能,理论无天花板;垂直扩展是升级单机配置,性能增长有天花板,成本指数级上升
- 无状态vs有状态:无状态服务不存储请求上下文,任何实例处理结果一致,可无限扩容;有状态服务依赖本地存储的上下文,请求只能分发到特定实例,无法水平扩展
结尾
高并发架构的核心,不是追求多么复杂的技术,而是基于业务的实际场景,将流量削峰、异步化、水平扩展这三大核心方案组合起来,形成一套完整的、层层设防的防护体系。流量削峰从源头管控流量冲击,异步化解耦业务流程、提升系统吞吐量,水平扩展打破单机瓶颈、实现系统能力的线性增长,三者互为补充,协同生效,最终让系统在百万级QPS的流量冲击下,依然保持稳定可用。