1. 项目背景与核心挑战
演唱会抢票系统是典型的高并发、分布式场景的"压力测试场"。去年某顶流歌手演唱会开票时,峰值并发请求超过200万/秒,这对系统架构提出了近乎苛刻的要求:
- 瞬时流量冲击:开票瞬间流量是日常的1000倍以上
- 资源竞争激烈:热门场次座位数可能只有几万,但抢票用户达数百万
- 数据一致性要求:超卖会导致重大公关危机
- 黄牛防御需求:需要识别并拦截自动化脚本
传统单体架构在这种场景下会立即崩溃。我们采用SpringCloud微服务架构,通过以下技术栈实现弹性扩展:
- 服务注册与发现:Eureka + Ribbon
- 流量控制:SpringCloud Gateway + Sentinel
- 分布式锁:Redisson + Redis
- 事务管理:Seata
- 监控体系:Prometheus + Grafana
关键设计原则:宁可拒绝99%的请求,也要保证1%成功请求的绝对正确性
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心架构设计
2.1 微服务拆分策略
系统按业务边界拆分为六个微服务:
| 服务名称 | 实例数 | QPS | 核心依赖 |
|---|---|---|---|
| 用户服务 | 3 | 5000 | Redis,JWT |
| 票务服务 | 10 | 20000 | MySQL,Redisson |
| 订单服务 | 8 | 15000 | Seata,RocketMQ |
| 支付服务 | 5 | 8000 | Alipay SDK |
| 风控服务 | 3 | 3000 | TensorFlow Lite |
| 通知服务 | 2 | 1000 | WebSocket,SMS Gateway |
2.2 高并发场景下的数据一致性方案
采用最终一致性替代强一致性,通过事件驱动架构实现:
- 库存预扣减:Redis原子操作DECR
- 创建订单状态为"处理中"
- 支付成功后发送MQ消息
- 订单服务消费消息更新状态
- 定时任务补偿异常订单
java复制// Redisson分布式锁示例
RLock lock = redissonClient.getLock("ticket:" + showId);
try {
if(lock.tryLock(1, 10, TimeUnit.SECONDS)) {
// 核心业务逻辑
int remain = redisTemplate.opsForValue().decrement("stock:" + showId);
if(remain >= 0) {
createOrderAsync(userId, showId);
}
}
} finally {
lock.unlock();
}
3. 关键技术实现细节
3.1 秒杀流量削峰方案
三级流量过滤体系:
-
前端层:
- 按钮倒计时+随机延迟
- 图形验证码+行为验证
-
网关层:
yaml复制spring: cloud: gateway: routes: - id: ticket-route uri: lb://ticket-service predicates: - Path=/api/ticket/** filters: - name: RequestRateLimiter args: redis-rate-limiter.replenishRate: 1000 redis-rate-limiter.burstCapacity: 2000 -
服务层:
- 令牌桶算法控制服务入口流量
- 线程池隔离避免级联雪崩
3.2 分布式事务处理
采用Seata的AT模式解决跨服务事务:
- 全局事务ID贯穿所有微服务
- 各服务本地事务注册到TC(Transaction Coordinator)
- 二阶段提交/回滚机制
- 事务补偿定时任务
实测数据:在1000TPS压力下,事务成功率从85%提升到99.2%
4. 性能优化实战记录
4.1 Redis热点数据处理
针对热门场次的库存key,采用:
- 本地缓存+Redis多级缓存
- Key分片:将stock:123拆分为stock:123_shard[0-9]
- Lua脚本保证原子性:
lua复制local stock = tonumber(redis.call('GET', KEYS[1]))
if stock > 0 then
redis.call('DECR', KEYS[1])
return 1
end
return 0
4.2 MySQL优化方案
- 垂直分库:用户库/票务库/订单库分离
- 水平分表:订单表按用户ID哈希分16张表
- 索引优化:建立组合索引 (show_id, status, create_time)
- 连接池配置:
properties复制spring.datasource.hikari.maximum-pool-size=20 spring.datasource.hikari.minimum-idle=5 spring.datasource.hikari.idle-timeout=30000
5. 部署架构与监控体系
5.1 Kubernetes集群部署方案
生产环境采用3主节点+10工作节点的K8s集群:
-
Pod资源限制:
yaml复制resources: limits: cpu: "2" memory: 4Gi requests: cpu: "0.5" memory: 1Gi -
HPA自动扩缩容配置:
yaml复制metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70
5.2 全链路监控方案
-
Prometheus采集指标:
- 应用指标:JVM,Tomcat,自定义业务指标
- 中间件:Redis,MySQL,RocketMQ
- 系统指标:CPU,内存,网络
-
Grafana监控看板:
- 实时QPS/成功率看板
- 分布式事务状态看板
- 资源水位预警看板
-
日志收集:
- ELK收集全链路日志
- 关键业务日志染色追踪
6. 踩坑与解决方案
6.1 分布式锁失效场景
问题现象:
- 高并发下出现超卖
- Redisson锁偶尔失效
根因分析:
- 网络延迟导致锁过期
- 业务处理时间超过锁有效期
- Redis主从切换时的脑裂问题
解决方案:
- 使用Redisson的看门狗机制自动续期
- 业务代码优化缩短临界区
- 采用RedLock算法(需5个Redis实例)
java复制// 改进后的锁用法
RLock lock = redissonClient.getLock("ticket_lock:" + showId);
try {
// 等待时间1秒,锁有效期30秒
if(lock.tryLock(1, 30, TimeUnit.SECONDS)) {
// 业务逻辑执行时间控制在20秒内
}
} finally {
if(lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
6.2 缓存与数据库一致性问题
典型场景:
- 库存扣减成功但缓存未更新
- 缓存击穿导致数据库压力飙升
最终方案:
- 采用Cache Aside Pattern
- 设置随机过期时间避免缓存雪崩
- 布隆过滤器预防缓存穿透
java复制// 缓存处理示例
public Ticket getTicket(Long showId) {
String cacheKey = "ticket:" + showId;
Ticket ticket = redisTemplate.opsForValue().get(cacheKey);
if(ticket == null) {
synchronized(this) {
ticket = redisTemplate.opsForValue().get(cacheKey);
if(ticket == null) {
ticket = ticketMapper.selectById(showId);
// 设置随机过期时间(5-10分钟)
int expireTime = 300 + new Random().nextInt(300);
redisTemplate.opsForValue().set(
cacheKey, ticket, expireTime, TimeUnit.SECONDS);
}
}
}
return ticket;
}
在实际压测中,这套系统成功支撑了50万QPS的流量冲击,平均响应时间控制在200ms以内,订单处理成功率达到99.97%。最大的经验教训是:分布式环境下,任何单点假设都可能成为系统崩溃的导火索,必须通过混沌工程主动验证系统韧性。
