docs: 创建P0和P1改进项实现方案
- IMPL-001: 响应式编程培训方案 - IMPL-002: 敏感数据加密存储方案 - IMPL-003: 预约高峰期性能优化方案 - IMPL-004: 支付接口幂等性校验方案
This commit is contained in:
@@ -0,0 +1,654 @@
|
||||
# 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**:
|
||||
|
||||
```java
|
||||
@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**:
|
||||
|
||||
```java
|
||||
@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**:
|
||||
|
||||
```java
|
||||
@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**:
|
||||
|
||||
```java
|
||||
@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**:
|
||||
|
||||
```java
|
||||
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**:
|
||||
|
||||
```java
|
||||
@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注解**:
|
||||
|
||||
```java
|
||||
@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**:
|
||||
|
||||
```java
|
||||
@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**:
|
||||
|
||||
```java
|
||||
@Service
|
||||
public class ReservationProducer {
|
||||
|
||||
@Autowired
|
||||
private RabbitTemplate rabbitTemplate;
|
||||
|
||||
public void sendReservation(ReservationMessage message) {
|
||||
rabbitTemplate.convertAndSend(
|
||||
RabbitMQConfig.EXCHANGE,
|
||||
RabbitMQConfig.ROUTING_KEY,
|
||||
message
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**ReservationConsumer**:
|
||||
|
||||
```java
|
||||
@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:预约高峰期模拟**
|
||||
|
||||
```scala
|
||||
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:缓存命中率测试**
|
||||
|
||||
```java
|
||||
@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 压力测试
|
||||
|
||||
#### 测试步骤
|
||||
|
||||
1. 逐步增加并发数(500 → 1000 → 2000 → 3000)
|
||||
2. 监控系统资源(CPU、内存、网络)
|
||||
3. 记录性能指标(QPS、响应时间、成功率)
|
||||
4. 识别系统极限
|
||||
|
||||
#### 性能指标
|
||||
|
||||
| 并发数 | 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 稳定性测试
|
||||
|
||||
#### 测试步骤
|
||||
|
||||
1. 持续运行2小时
|
||||
2. 监控内存泄漏
|
||||
3. 监控GC频率
|
||||
4. 记录系统稳定性指标
|
||||
|
||||
#### 稳定性指标
|
||||
|
||||
- 内存占用稳定
|
||||
- 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:消息队列积压**
|
||||
- 应对:监控告警 + 动态扩容消费者
|
||||
|
||||
---
|
||||
|
||||
## 八、相关文档
|
||||
|
||||
- [改进路线图](../05-PLANS/改进路线图.md)
|
||||
- [EVAL-002-性能与可扩展性评估报告](../03-EVALUATION/EVAL-002-性能与可扩展性评估报告.md)
|
||||
- [ADR-002-响应式编程选型](../02-ARCHITECTURE/架构决策记录/ADR-002-响应式编程选型.md)
|
||||
Reference in New Issue
Block a user