dec9085205
- IMPL-001: 响应式编程培训方案 - IMPL-002: 敏感数据加密存储方案 - IMPL-003: 预约高峰期性能优化方案 - IMPL-004: 支付接口幂等性校验方案
14 KiB
14 KiB
IMPL-003: 预约高峰期性能优化方案
文档编号: GYM-IMPL-003
版本: v1.0
日期: 2026-04-05
作者: 张翔
状态: 正式发布
文档修订历史
| 版本 | 日期 | 作者 | 修订内容 |
|---|---|---|---|
| v1.0 | 2026-04-05 | 张翔 | 创建预约高峰期性能优化方案 |
一、需求分析
1.1 问题背景
预约高峰期QPS仅500-1000,距离目标2000+差距较大,影响用户体验和业务转化。
1.2 性能问题分析
当前性能指标
| 指标 | 目标值 | 实际值 | 差距 |
|---|---|---|---|
| QPS | 2000+ | 500-1000 | 4倍 |
| 响应时间(P99) | ≤200ms | 600-1000ms | 5倍 |
| 成功率 | ≥99% | 95-97% | 2-4% |
性能瓶颈识别
瓶颈1:数据库查询慢
- 缺少索引
- 全表扫描
- 慢SQL
瓶颈2:无缓存机制
- 每次查询都访问数据库
- 热点数据未缓存
- 缓存命中率低
瓶颈3:同步阻塞
- 预约高峰期并发处理能力不足
- 线程池满载
- 响应时间过长
瓶颈4:缺少消息队列
- 无法削峰填谷
- 流量直接冲击数据库
- 系统稳定性差
1.3 成功标准
- QPS≥2000
- 响应时间(P99)≤200ms
- 成功率≥99%
- 缓存命中率≥80%
- 数据库主从延迟≤1秒
- 消息队列无积压
二、技术方案设计
2.1 优化策略组合
策略1:引入Redis缓存
缓存对象:
| 缓存对象 | TTL | 缓存键格式 | 说明 |
|---|---|---|---|
| 课程信息 | 1小时 | course:{id} | 课程详情 |
| 会员信息 | 30分钟 | member:{id} | 会员信息 |
| 教练信息 | 1小时 | coach:{id} | 教练信息 |
| 预约名额 | 5分钟 | reservation:quota:{courseId}:{date} | 剩余名额 |
缓存策略:
Cache-Aside模式:
1. 读取时先查缓存
2. 缓存未命中则查数据库
3. 查询结果写入缓存
4. 写入时更新缓存
缓存架构:
应用层 → Redis缓存 → 数据库
↓
本地缓存(可选)
策略2:数据库读写分离
架构设计:
应用层
↓
ShardingSphere-JDBC(路由层)
↓ ↓
主库(写) 从库(读)
实现方案:
主库:处理写入操作
- INSERT
- UPDATE
- DELETE
从库:处理读取操作
- SELECT
主从同步:半同步模式
- 主库写入后等待至少一个从库确认
- 保证数据一致性
策略3:引入消息队列削峰
消息队列选型:RabbitMQ
削峰方案:
预约请求流程:
1. 用户发起预约请求
2. 请求写入消息队列
3. 立即返回"预约中"状态
4. 后台消费者异步处理
5. 处理完成后通知用户
消息队列架构:
生产者 → Exchange → Queue → 消费者
↓
死信队列(失败重试)
流量控制:
- 消费速率:2000 TPS
- 队列容量:10000条消息
- 超出容量:拒绝请求
2.2 技术选型
| 组件 | 技术选型 | 版本 | 说明 |
|---|---|---|---|
| 缓存 | Redis | 6.2+ | 高性能缓存 |
| 数据库中间件 | ShardingSphere-JDBC | 5.3+ | 读写分离路由 |
| 消息队列 | RabbitMQ | 3.9+ | 削峰填谷 |
三、代码结构设计
3.1 缓存层设计
包结构
com.gym.manage.cache/
├── config/
│ ├── RedisConfig.java # Redis配置
│ └── CacheConfig.java # 缓存配置
├── service/
│ ├── CacheService.java # 缓存服务接口
│ └── CacheServiceImpl.java # 缓存服务实现
└── aspect/
└── CacheAspect.java # 缓存切面
核心类设计
RedisConfig:
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(
RedisConnectionFactory factory
) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// 使用Jackson序列化
Jackson2JsonRedisSerializer<Object> serializer =
new Jackson2JsonRedisSerializer<>(Object.class);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(serializer);
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(serializer);
return template;
}
}
CacheService:
@Service
public class CacheServiceImpl implements CacheService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Override
public <T> T get(String key, Class<T> type) {
Object value = redisTemplate.opsForValue().get(key);
return value != null ? (T) value : null;
}
@Override
public void set(String key, Object value, Duration ttl) {
redisTemplate.opsForValue().set(key, value, ttl);
}
@Override
public void delete(String key) {
redisTemplate.delete(key);
}
@Override
public boolean hasKey(String key) {
return Boolean.TRUE.equals(redisTemplate.hasKey(key));
}
}
CacheAspect:
@Aspect
@Component
public class CacheAspect {
@Autowired
private CacheService cacheService;
@Around("@annotation(cacheable)")
public Object around(ProceedingJoinPoint pjp, Cacheable cacheable)
throws Throwable {
String key = generateKey(cacheable.key(), pjp.getArgs());
// 先查缓存
Object cached = cacheService.get(key, cacheable.type());
if (cached != null) {
return cached;
}
// 缓存未命中,执行方法
Object result = pjp.proceed();
// 写入缓存
if (result != null) {
cacheService.set(key, result, Duration.ofMinutes(cacheable.ttl()));
}
return result;
}
private String generateKey(String pattern, Object[] args) {
// 生成缓存键
return String.format(pattern, args);
}
}
3.2 数据库层设计
包结构
com.gym.manage.datasource/
├── config/
│ ├── MasterSlaveConfig.java # 主从配置
│ └── ShardingConfig.java # 分片配置
├── routing/
│ ├── DynamicDataSource.java # 动态数据源
│ └── DataSourceRouter.java # 数据源路由
└── annotation/
└── ReadOnly.java # 只读注解
核心类设计
MasterSlaveConfig:
@Configuration
public class MasterSlaveConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DataSource slaveDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public DynamicDataSource dynamicDataSource(
@Qualifier("masterDataSource") DataSource master,
@Qualifier("slaveDataSource") DataSource slave
) {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("master", master);
targetDataSources.put("slave", slave);
DynamicDataSource dynamicDataSource = new DynamicDataSource();
dynamicDataSource.setDefaultTargetDataSource(master);
dynamicDataSource.setTargetDataSources(targetDataSources);
return dynamicDataSource;
}
}
DynamicDataSource:
public class DynamicDataSource extends AbstractRoutingDataSource {
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
@Override
protected Object determineCurrentLookupKey() {
return CONTEXT_HOLDER.get();
}
public static void setMaster() {
CONTEXT_HOLDER.set("master");
}
public static void setSlave() {
CONTEXT_HOLDER.set("slave");
}
public static void clear() {
CONTEXT_HOLDER.remove();
}
}
DataSourceRouter:
@Aspect
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class DataSourceRouter {
@Around("@annotation(readOnly)")
public Object route(ProceedingJoinPoint pjp, ReadOnly readOnly)
throws Throwable {
try {
DynamicDataSource.setSlave();
return pjp.proceed();
} finally {
DynamicDataSource.clear();
}
}
}
ReadOnly注解:
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ReadOnly {
}
3.3 消息队列层设计
包结构
com.gym.manage.mq/
├── config/
│ └── RabbitMQConfig.java # RabbitMQ配置
├── producer/
│ └── ReservationProducer.java # 预约生产者
├── consumer/
│ └── ReservationConsumer.java # 预约消费者
└── message/
└── ReservationMessage.java # 预约消息
核心类设计
RabbitMQConfig:
@Configuration
public class RabbitMQConfig {
public static final String EXCHANGE = "reservation.exchange";
public static final String QUEUE = "reservation.queue";
public static final String ROUTING_KEY = "reservation.create";
@Bean
public DirectExchange exchange() {
return new DirectExchange(EXCHANGE);
}
@Bean
public Queue queue() {
return QueueBuilder.durable(QUEUE)
.withArgument("x-message-ttl", 60000) // 消息TTL: 1分钟
.withArgument("x-dead-letter-exchange", "reservation.dlx")
.build();
}
@Bean
public Binding binding() {
return BindingBuilder.bind(queue())
.to(exchange())
.with(ROUTING_KEY);
}
}
ReservationProducer:
@Service
public class ReservationProducer {
@Autowired
private RabbitTemplate rabbitTemplate;
public void sendReservation(ReservationMessage message) {
rabbitTemplate.convertAndSend(
RabbitMQConfig.EXCHANGE,
RabbitMQConfig.ROUTING_KEY,
message
);
}
}
ReservationConsumer:
@Service
public class ReservationConsumer {
@Autowired
private ReservationService reservationService;
@RabbitListener(queues = RabbitMQConfig.QUEUE)
public void handleReservation(ReservationMessage message) {
try {
// 处理预约
reservationService.processReservation(message);
} catch (Exception e) {
// 异常处理,消息进入死信队列
throw new AmqpRejectAndDontRequeueException(e);
}
}
}
四、测试方案
4.1 性能测试
测试工具
- JMeter:压力测试
- Gatling:性能测试
- Prometheus + Grafana:监控
测试场景
场景1:预约高峰期模拟
scenario("预约高峰期")
.exec(http("预约课程")
.post("/api/reservations")
.body(StringBody("""{"courseId":1,"memberId":1}"""))
.check(status.is(200))
)
.inject(
rampUsersPerSec(100) to 2000 during (300 seconds)
)
.protocols(httpProtocol)
场景2:缓存命中率测试
@Test
public void testCacheHitRate() {
int totalRequests = 1000;
int cacheHits = 0;
for (int i = 0; i < totalRequests; i++) {
Course course = courseService.getCourseById(1L);
if (cacheService.hasKey("course:1")) {
cacheHits++;
}
}
double hitRate = (double) cacheHits / totalRequests;
System.out.println("缓存命中率: " + (hitRate * 100) + "%");
assertTrue(hitRate >= 0.8, "缓存命中率应≥80%");
}
4.2 压力测试
测试步骤
- 逐步增加并发数(500 → 1000 → 2000 → 3000)
- 监控系统资源(CPU、内存、网络)
- 记录性能指标(QPS、响应时间、成功率)
- 识别系统极限
性能指标
| 并发数 | QPS | 响应时间(P99) | 成功率 | CPU利用率 | 内存利用率 |
|---|---|---|---|---|---|
| 500 | 500 | 100ms | 99% | 30% | 50% |
| 1000 | 1000 | 150ms | 99% | 50% | 60% |
| 2000 | 2000 | 200ms | 99% | 70% | 70% |
| 3000 | 2500 | 300ms | 95% | 90% | 80% |
4.3 稳定性测试
测试步骤
- 持续运行2小时
- 监控内存泄漏
- 监控GC频率
- 记录系统稳定性指标
稳定性指标
- 内存占用稳定
- GC频率正常
- 无内存泄漏
- 无死锁
五、实施步骤
| 步骤 | 任务 | 负责人 | 完成时间 | 验收标准 |
|---|---|---|---|---|
| 1 | Redis环境搭建 | 运维工程师 | 1天 | Redis服务正常运行 |
| 2 | 实现缓存服务 | 后端开发 | 2天 | 单元测试通过 |
| 3 | 数据库主从配置 | 运维工程师 | 1天 | 主从同步正常 |
| 4 | 实现读写分离 | 后端开发 | 3天 | 集成测试通过 |
| 5 | RabbitMQ环境搭建 | 运维工程师 | 1天 | RabbitMQ服务正常运行 |
| 6 | 实现消息队列 | 后端开发 | 2天 | 单元测试通过 |
| 7 | 性能测试 | 测试工程师 | 2天 | 性能指标达标 |
| 8 | 灰度发布 | 运维工程师 | 1天 | 灰度发布成功 |
六、验收标准
6.1 性能验收
- QPS≥2000
- 响应时间(P99)≤200ms
- 成功率≥99%
6.2 缓存验收
- 缓存命中率≥80%
- 缓存穿透防护有效
- 缓存雪崩防护有效
6.3 数据库验收
- 数据库主从延迟≤1秒
- 读写分离正常
- 数据一致性保证
6.4 消息队列验收
- 消息队列无积压
- 消息不丢失
- 消费速率达标
七、风险与应对
7.1 风险识别
风险1:缓存穿透
- 应对:布隆过滤器 + 空值缓存
风险2:缓存雪崩
- 应对:随机过期时间 + 多级缓存
风险3:主从延迟
- 应对:关键业务读主库 + 半同步复制
风险4:消息队列积压
- 应对:监控告警 + 动态扩容消费者