Files
gym-manage/docs/06-IMPLEMENTATION/IMPL-003-预约高峰期性能优化方案.md
T
张翔 dec9085205 docs: 创建P0和P1改进项实现方案
- IMPL-001: 响应式编程培训方案
- IMPL-002: 敏感数据加密存储方案
- IMPL-003: 预约高峰期性能优化方案
- IMPL-004: 支付接口幂等性校验方案
2026-04-05 16:48:27 +08:00

14 KiB
Raw Blame History

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 压力测试

测试步骤

  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:消息队列积压

  • 应对:监控告警 + 动态扩容消费者

八、相关文档