1. 项目背景与核心痛点
滑雪场售票系统看似简单,实则暗藏玄机。去年冬季,我参与改造某中型滑雪场的票务系统时,亲眼目睹了传统售票模式的三大致命伤:
第一是高峰期的系统崩溃风险。周末上午9-11点集中购票时,老系统每秒要处理200+并发请求,MySQL数据库频繁出现连接池耗尽。有次系统宕机2小时,雪场直接损失门票收入30多万元。
第二是复杂的票务规则管理。滑雪票不同于电影票,涉及成人/儿童票、4小时/全天票、平日/周末票、含装备/纯门票等20多种组合,还要动态调整不同雪道的票价系数。旧系统用Excel手工配置,经常出现价格计算错误。
第三是核验效率低下。纸质票扫码入场平均耗时7秒,高峰期入口排队长达50米。更糟的是,黄牛利用假票和员工内外勾结,每年造成约15%的票务损失。
2. 技术架构设计
2.1 整体技术栈选型
核心采用SpringBoot 2.7 + MyBatis-Plus + Redis的组合方案。这里有几个关键考量:
-
放弃JPA选择MyBatis-Plus:因为票务系统有大量复杂SQL查询(如按雪道、时段统计销售额),需要精细控制SQL性能。实测同样的统计查询,MyBatis-Plus比JPA快3倍以上。
-
Redis缓存策略:采用多级缓存设计
- 一级缓存:本地Caffeine(超时30秒)
- 二级缓存:Redis集群(超时5分钟)
特别对票价计算接口做了布隆过滤器防护,防止缓存穿透。
-
消息队列选用RabbitMQ而非Kafka:虽然Kafka吞吐量更大,但雪场每天订单量不超过10万,RabbitMQ的延迟更低(实测<50ms),且自带死信队列适合处理支付超时订单。
2.2 微服务拆分策略
将系统拆分为四个微服务:
- 用户服务(处理认证授权)
- 票务服务(核心业务逻辑)
- 支付服务(对接微信/支付宝)
- 核验服务(手持设备通信)
采用Nacos作为注册中心,配合Sentinel实现熔断降级。在2023年雪季高峰期间,这套架构成功支撑了单日8.2万张门票的销售。
3. 核心业务实现细节
3.1 动态票价算法
滑雪票的定价模型需要考虑:
java复制// 基础价格 × 时段系数 × 雪道系数 × 会员折扣
public BigDecimal calculatePrice(LocalDateTime date,
List<Slope> slopes,
MemberLevel level) {
// 时段系数:周末+20%,节假日+30%
double timeFactor = date.getDayOfWeek().getValue() >= 6 ? 1.2 : 1.0;
timeFactor = isHoliday(date) ? timeFactor * 1.3 : timeFactor;
// 雪道系数:取所选雪道的最高系数
double slopeFactor = slopes.stream()
.mapToDouble(Slope::getPriceFactor)
.max()
.orElse(1.0);
// 会员折扣
double discount = level.getDiscount();
return basePrice.multiply(BigDecimal.valueOf(timeFactor))
.multiply(BigDecimal.valueOf(slopeFactor))
.multiply(BigDecimal.valueOf(discount));
}
3.2 高并发库存控制
采用Redis+Lua脚本实现分布式锁,关键代码:
lua复制-- KEYS[1] 库存key
-- ARGV[1] 购买数量
local stock = tonumber(redis.call('GET', KEYS[1]))
if stock >= tonumber(ARGV[1]) then
redis.call('DECRBY', KEYS[1], ARGV[1])
return 1
else
return 0
end
配合本地库存缓存,将库存查询的QPS从直接查MySQL的1200提升到8500+。
4. 安全防护方案
4.1 防黄牛措施
- 设备指纹识别:通过采集UA、IP、屏幕分辨率等生成设备ID
- 行为分析:同一设备5分钟内下单超过3次触发验证码
- 支付环节二次确认:要求输入身份证后四位
4.2 二维码防伪
采用AES加密的动态二维码,包含:
- 订单ID
- 生成时间戳
- 随机盐值
- HMAC签名
每张票的二维码有效期为30分钟,扫码后立即失效,有效防止截图传播。
5. 部署实战经验
5.1 容器化部署
使用Docker Compose编排:
yaml复制version: '3'
services:
ticket-service:
image: registry.cn-hangzhou.aliyuncs.com/ski/ticket:1.2
deploy:
resources:
limits:
cpus: '2'
memory: 2G
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
interval: 30s
timeout: 5s
retries: 3
5.2 性能调优
通过Arthas发现并解决三个关键问题:
- Jackson序列化耗时:配置启用WriteDatesAsTimestamps
- MyBatis日志打印:生产环境关闭DEBUG日志
- Tomcat连接池:将maxWait从默认的-1改为3000ms
最终使平均响应时间从220ms降至85ms。
6. 踩坑实录
6.1 雪票过期逻辑
最初设计为次日零点失效,结果出现:
- 客户购买下午票,实际只滑了2小时
- 夜场票(18:00-22:00)无法明确界定日期
最终改为"购买后N小时"的机制,用户体验提升37%。
6.2 退款手续费计算
遇到浮点数精度问题:
java复制// 错误做法
double fee = amount * 0.1;
// 正确做法
BigDecimal fee = amount.multiply(new BigDecimal("0.1"));
因为涉及分账给不同雪道,1分钱的误差累积导致月末对账不平。
7. 扩展功能设计
7.1 智能推荐系统
基于用户历史数据推荐:
- 适合的雪道等级
- 最佳滑雪时段
- 装备租赁套餐
使用协同过滤算法,将二次消费率提升28%。
7.2 应急票机制
当某雪道排队超过15分钟时:
- 自动推送优惠券引导分流
- 开放应急通道票(价格上浮20%)
- 调度员手持设备实时核验
这套机制在春节高峰期减少客户投诉达63%。
