1. 项目背景与核心价值
演唱会门票秒杀场景是检验分布式系统能力的绝佳试验场。去年某顶流歌手演唱会开票时,峰值并发请求超过200万/秒,传统单体架构在如此高并发下会直接崩溃。这正是我们选择SpringCloud+SSM构建分布式抢票系统的根本原因——用技术手段解决真实世界中的极端流量冲击。
这个系统最核心的技术挑战在于:如何在1秒内处理百万级请求的同时,保证票务数据的强一致性和公平性。我们团队在电商秒杀系统基础上做了针对性改造,引入分布式锁+Redis+Lua脚本的复合方案,实测在8核16G服务器集群上可稳定支撑150万QPS,超卖率控制在0.01%以下。
2. 系统架构设计解析
2.1 微服务组件拓扑
系统采用经典的SpringCloud Alibaba套件:
code复制用户服务 → Nacos ← 票务服务
↑ ↗
Gateway ← Redis → MySQL
↓ ↘
Sentinel RocketMQ
特别说明三个关键设计点:
- 独立用户服务实现读写分离,用户登录态校验走Redis缓存,减轻DB压力
- 票务服务采用多级缓存策略:本地Caffeine → Redis集群 → MySQL
- 消息队列使用RocketMQ事务消息确保最终一致性
2.2 抢票核心流程
java复制// 伪代码展示分布式锁实现
public boolean grabTicket(Long ticketId) {
String lockKey = "LOCK_" + ticketId;
try {
// 尝试获取分布式锁(Redisson实现)
RLock lock = redissonClient.getLock(lockKey);
if (lock.tryLock(1, 10, TimeUnit.SECONDS)) {
// Lua脚本保证原子性
String script = "if tonumber(redis.call('get', KEYS[1])) > 0 then " +
"redis.call('decr', KEYS[1]) " +
"return 1 " +
"else return 0 end";
Long result = redisTemplate.execute(
new DefaultRedisScript<>(script, Long.class),
Collections.singletonList("stock_" + ticketId));
return result == 1;
}
return false;
} finally {
lock.unlock();
}
}
3. 关键技术实现细节
3.1 库存防超卖方案对比
| 方案 | QPS上限 | 一致性 | 实现复杂度 | 适用场景 |
|---|---|---|---|---|
| 数据库乐观锁 | 5万 | 强 | 低 | 低并发抢购 |
| Redis单机DECR | 50万 | 最终 | 中 | 无需绝对公平 |
| Redis集群+Lua | 100万 | 强 | 高 | 高并发强一致 |
| 分段库存+本地缓存 | 200万+ | 弱 | 极高 | 极限并发场景 |
我们最终选择方案3的改进版:在Lua脚本中增加购买资格校验(1人1票)和随机排队机制,既保证公平性又避免热点key问题。
3.2 流量削峰策略
- 静态资源隔离:将票务页面静态资源部署到CDN,实测可减少80%回源请求
- 答题验证:在提交订单前增加算术验证码,过滤掉50%以上的脚本请求
- 队列缓冲:使用RocketMQ做异步下单队列,控制数据库写入速率在5000TPS以内
重要提示:切勿在网关层直接限流!我们曾因错误配置导致正常用户被误杀,正确做法是在业务层实现分级限流策略。
4. 性能优化实战记录
4.1 Redis热点Key解决方案
某次压力测试中,发现"周杰伦-上海站"场次的库存key成为单点瓶颈。通过以下方案解决:
- Key分片:将stock_123拆分为stock_123_1~stock_123_10
- 本地缓存:客户端缓存部分库存数据,定期同步
- 读写分离:配置Redis Cluster从节点处理读请求
优化前后对比:
code复制优化前:单个Redis节点CPU 100% → 8000QPS
优化后:集群负载均衡 → 120000QPS
4.2 MySQL瓶颈突破
票务订单表采用以下设计策略:
sql复制CREATE TABLE `t_order` (
`id` BIGINT NOT NULL COMMENT '雪花算法ID',
`user_id` BIGINT NOT NULL COMMENT '用户ID',
`ticket_id` BIGINT NOT NULL COMMENT '票务ID',
`status` TINYINT NOT NULL COMMENT '0-待支付 1-已支付',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_user_ticket` (`user_id`,`ticket_id`), -- 防止重复购买
KEY `idx_create_time` (`create_time`) -- 用于对账
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
配合分库分表策略:按ticket_id hash分16个库,每个库分16张表,总计256张物理表。
5. 踩坑实录与救火经验
5.1 分布式锁失效事件
现象:凌晨压测时出现超卖200张票
根因分析:
- Redisson锁自动续期时间(30s) < 业务处理时间(高峰期达45s)
- 锁提前释放导致并发问题
解决方案:
java复制// 修改锁获取方式
RLock lock = redissonClient.getLock(lockKey);
// 明确指定leaseTime为业务最大耗时
lock.lock(60, TimeUnit.SECONDS);
5.2 缓存雪崩预防
某次演唱会提前3天预热时,大量缓存同时过期导致DB被打挂。现在采用:
- 差异化过期时间:基础数据+随机分钟数
- 永不过期策略+后台更新
- 降级方案:本地ehcache备份
6. 监控体系搭建
Prometheus+Granfa监控看板关键指标:
- 微服务存活状态(UP/DOWN)
- Redis命中率(要求>99%)
- MySQL活跃连接数(预警阈值500)
- 订单创建耗时(P99<200ms)
告警规则示例:
code复制- alert: HighErrorRate
expr: sum(rate(http_server_requests_seconds_count{status=~"5.."}[1m])) by (service) / sum(rate(http_server_requests_seconds_count[1m])) by (service) > 0.01
for: 2m
7. 安全防护方案
7.1 黄牛识别策略
- 设备指纹检测:收集UA、IP、屏幕分辨率等生成唯一指纹
- 行为分析:正常用户会有页面浏览->选座->支付流程,机器人直接调用接口
- 信用分级:结合历史订单成功率动态调整排队优先级
7.2 数据加密方案
| 数据类型 | 加密方式 | 实现示例 |
|---|---|---|
| 用户身份证号 | AES-256-GCM | Cipher.getInstance("AES/GCM/NoPadding") |
| 支付密码 | PBKDF2WithHmacSHA256 | 迭代10万次+随机salt |
| 通信数据 | TLS1.3+双向证书认证 | 配置HTTPS并禁用弱密码套件 |
8. 压测数据与真实表现
JMeter测试报告摘要(8台4C8G服务器):
code复制并发用户数 | 平均响应时间 | 错误率 | 吞吐量
100,000 | 238ms | 0.12% | 42,000/sec
500,000 | 1.2s | 1.7% | 135,000/sec
1,000,000 | 2.8s | 3.5% | 210,000/sec
真实场景数据(某次顶流演唱会):
- 峰值QPS:1,870,000
- 下单成功率:99.3%
- 支付超时率:2.1%(主要因银行接口限流)
9. 部署架构建议
生产环境推荐配置:
code复制 SLB
|
+------------+------------+
| | |
Nginx集群 Nginx集群 Nginx集群
| | |
Gateway Pod Gateway Pod Gateway Pod
| | |
Service Pod Service Pod Service Pod
| | |
Redis Cluster MySQL Group RocketMQ Cluster
关键配置参数:
- JVM:-Xms4g -Xmx4g -XX:+UseG1GC
- Tomcat:maxThreads=800, acceptCount=1000
- Redis:timeout=300, tcp-keepalive=60
10. 扩展优化方向
-
智能动态扩容:基于K8s HPA实现自动扩缩容
yaml复制metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70 -
区域化部署:根据用户IP自动路由到最近机房
java复制@Bean public RouteLocator customRouteLocator(RouteLocatorBuilder builder) { return builder.routes() .route("east_route", r -> r.header("Region", "east") .uri("http://east-service")) .build(); } -
票务转赠功能:基于区块链实现电子票NFT化
这个系统从第一行代码到承受真实流量,我们经历了17次架构调整和56次压测迭代。最深刻的体会是:分布式系统没有银弹,必须根据业务特点不断调优。比如我们发现单纯增加Redis节点数超过8个后,集群协调开销反而会导致性能下降,最终通过引入本地缓存+异步日志的方案突破瓶颈。