面向热门演唱会开售场景的高并发抢票系统。基于黑马点评(hm-dianping)的单体架构起步,把“优惠券秒杀”改造成演出票务:票档库存、实名一证一票、虚拟排队、锁座超时释放、候补与选座。
“安可(Encore)”是演唱会结束后观众请求返场的呼声,也是这个系统想解决的问题:几万人同时点“立即购买”,系统既不能超卖、不能崩,也要尽量公平。
原版秒杀只处理“一个数字库存 + 一人一单”。演出票务带来的新问题,原方案都覆盖不了:
| 演出业务的新约束 | 原版没有的设计问题 | 本项目的方案 |
|---|---|---|
| 开售瞬间几十倍于库存的请求 | 流量无差别打到扣减逻辑 | 等候室 + 按剩余库存放行购买令牌,削峰在前、扣减在后 |
| 实名制,每个证件每场限 1 张 | “一人一单”变成“一证一票”,且退票后允许重新购买 | 证件号 HMAC 指纹,Redis 原子校验,MySQL 条件唯一索引兜底 |
| 下单后 15 分钟不付款要释放 | 订单状态机与超时、支付回调并发 | RocketMQ 定时消息 + 扫表兜底,状态 CAS 更新,释放幂等 |
| 有人退票或超时释放的票 | 回流库存被脚本“捡漏” | 回流库存优先分配给候补队列 |
| 非热门场次允许选座 | 库存从“数量”变成“具体座位” | 座位 BitMap + Lua 多座原子锁定 |
| Redis 扣减与 MySQL 落库在两个系统 | 跨系统一致性 | RocketMQ 事务消息,回查 Redis 扣减凭证 + 定时对账 |
sequenceDiagram
participant U as 用户
participant A as encore-app
participant R as Redis
participant MQ as RocketMQ
participant DB as MySQL
U->>A: 进入等候室(开售前随机、开售后按到达顺序)
A->>R: 加入排队 ZSET
Note over A,R: 放行任务按“剩余库存 × 系数”发购买令牌
U->>A: 持令牌提交订单(票档、观演人)
A->>MQ: 发送事务半消息
MQ-->>A: 执行本地事务
A->>R: Lua:校验令牌/限购/一证一票 → 扣库存 → 写扣减凭证
A-->>MQ: COMMIT / ROLLBACK(宕机则由 Broker 回查凭证)
A-->>U: 返回 orderNo,前端轮询结果
MQ->>A: 消费下单消息
A->>DB: 插入订单与票(唯一索引幂等)+ 条件更新已售数
A->>MQ: 15 分钟定时消息(超时取消)
U->>A: 模拟支付 → 回调 CAS 状态 → 出票
| 层面 | 选型 |
|---|---|
| 语言 / 框架 | JDK 21,Spring Boot 3.5,MyBatis-Plus |
| 前端 | Vue 3,TypeScript,Vite,Tailwind CSS v4,Pinia |
| 存储 | MySQL 8,Redis 7(Redisson) |
| 消息 | RocketMQ 5.x(事务消息、定时消息) |
| 缓存 | Caffeine + Redis 两级缓存,Canal 订阅 binlog 失效(可选) |
| 观测 | Micrometer + Prometheus + Grafana |
| 压测 | JMeter |
| 部署 | Docker Compose;压测时 Nginx 后挂 2 个应用实例 |
| 黑马点评模块 | 在 Encore 中的去向 |
|---|---|
| 短信登录 + Redis Token | 保留 |
| 商户查询缓存(穿透/击穿/雪崩) | 改为演出详情缓存,升级为 Caffeine + Redis 两级缓存,并补上多实例本地缓存失效 |
| 全局唯一 ID | 保留,用于订单号 |
| 优惠券秒杀 + Stream 异步下单 | 重写为票档抢票:等候室、事务消息、订单状态机、候补 |
| Redisson 分布式锁 | 保留,用于放行任务、对账任务的单实例执行 |
| 点赞 / 关注 Feed | 改为关注艺人、新演出推送(推拉结合,P2) |
| GEO 附近商户 | 改为同城场馆 / 附近演出 |
| BitMap 签到 | 改为座位图(每个座位 1 bit) |
| HyperLogLog UV | 改为演出页 UV / “想看”人数 |
| 文档 | 内容 |
|---|---|
| 系统设计 | 领域模型、数据表、Redis 键、核心链路、一致性与异常处理、设计取舍 |
| 压测与正确性验证 | 压测场景、数据准备、指标、对账 SQL、故障演练、报告模板 |
| 开发路线 | 里程碑、每阶段验收标准、优先级 |
| 接口约定 | 前后端接口、状态与错误码 |
| 联调与部署验收 | 真实浏览器流程、双实例验证、复跑方式与边界 |
| 交接记录 | 启动命令、设计差异、环境问题与后续待办 |
| 演出与图片管理 | 新增演出、草稿发布、上下架、海报裁剪及图片上传 |
| Flat UI 组件规范 | 浅色扁平设计、组件使用规则、可复用提示词与浏览器验收 |
| 质量检查与修复 | 候补取消竞争、前端轮询、后台草稿保护与后续优化 |
需要 Docker Desktop(Linux 容器)、Docker Compose v2 和 Node.js 22.12+。后端在 Docker 内使用 Maven / JDK 21 构建,本机不需要安装 Maven 或升级 JDK。
在仓库根目录执行(PowerShell / Bash 均可逐行运行):
npm --prefix frontend ci
npm --prefix frontend run build
docker compose --profile app up -d --build首次启动需下载镜像与 Maven 依赖。Compose 命令返回后,等待两个应用的 /actuator/health 均返回 UP,再访问前端。
| 入口 | 地址 |
|---|---|
| 前端与 Nginx API | http://localhost:8000(API 前缀 /api) |
| 应用实例健康检查 | http://localhost:8081/actuator/health 、 http://localhost:8082/actuator/health |
| 接口文档 | http://localhost:8081/doc.html |
| Grafana | http://localhost:3000(admin / encore) |
| Prometheus | http://localhost:9090 |
演示管理员手机号 13800000000,普通演示账号 13900000000;页面获取验证码后会自动填入。新测试手机号会注册账号,购票前在“我的”完成模拟实名。验证码、实名和支付都是本地演示,不发送短信、不产生真实扣款。
MySQL 容器映射到 3307,Redis 映射到 6380,避免影响宿主机的 3306 / 6379 服务。这里的数据库刷盘参数、开发密钥和验证码回显仅适合开发环境,生产设置见交接记录。
已有 encore-dev 时先用 docker stop encore-dev 停掉第三个应用实例。停止完整环境但保留数据卷:
docker compose --profile app stop前端开发可复用 Compose 的 app1(PowerShell):
$env:ENCORE_API_TARGET = 'http://localhost:8081'
npm --prefix frontend run dev -- --host 127.0.0.1访问终端打印的 http://127.0.0.1:5173;本机另有项目占用 localhost:5173 的 IPv6 地址,二者可能不是同一个服务。
前端轮询回归:npm --prefix frontend test(Node.js 22.12+)。
在完整部署运行后,从仓库根目录执行:
node scripts/smoke-test.mjs http://localhost:8000/api
node scripts/deployment-test.mjs两个脚本均会写入本地测试数据,不应与压测并行。冒烟覆盖 28 项业务检查;部署脚本覆盖 20 项检查,包括跨实例登录/退出、Pub/Sub 缓存失效、Nginx 分流、定时放行锁、对账和 Prometheus 抓取。副作用和具体前置条件见 验收记录。
前端现已统一为浅色 Flat Design;管理员可从后台的“界面规范”入口查看按钮、表单、标签、弹窗等共享组件。
2026-10-21 新增后台 演出管理:支持草稿、场次与票档、复制场次、预览发布、上下架,以及海报裁剪、详情图排序和座位分区图。管理员登录后进入“运营后台 → 演出管理 → 新增演出”。新增功能 API 验收 32 项、原冒烟 28 项、部署回归 20 项通过;使用说明与边界。
截至 2026-10-19,已实现 M0–M3、M5、M6,M4 的指标/对账/Grafana,以及 M7 的两级缓存与 Pub/Sub 失效。前端在真实后端上已走通登录、实名、排队、下单、模拟支付、退票、选座、候补和运营后台。
两个应用实例 + Nginx 的完整 Compose 部署已构建并验证:经 Nginx 的冒烟测试 28/28、部署检查 20/20,通过时五项正确性违规数与 17 个票档的对账差值均为 0。此前的并发验收为库存 100、请求 2000、恰好售出 100 张;本轮未重跑该并发验收。
M4 的 JMeter S1–S5 对照压测及完整故障演练仍待完成;Canal、关注/Feed/GEO 和入队验证码为后续可选项。这里的功能验收结果不代表生产吞吐或故障恢复能力已经通过验收。