一、业务场景与核心挑战
1. 高并发压力
春运期间,12306 系统每秒可能会收到数百万次的请求,这对系统的并发处理能力提出了极高的要求。2023 年春运期间,12306 系统最高峰日处理售票请求达 15 亿次,平均每秒处理请求超过 17000 次。
2. 票额库存管理
火车票的库存管理需要保证原子性和一致性,防止超卖现象。例如,一张票不能同时售给多个用户。
3. 防止黄牛与爬虫
黄牛党和恶意爬虫会通过自动化工具大量抢票,破坏票务系统的公平性。据统计,12306 系统每天要拦截数千万次的恶意爬虫请求。
4. 预售与候补机制
预售期的设置需要精确控制,候补购票则需要高效的排队和通知机制。目前,12306 的预售期为 15 天,候补购票成功率超过 70%。
5. 分布式事务处理
票务系统涉及多个服务的协同操作,如订单创建、库存扣减、支付处理等,需要保证分布式事务的一致性。
二、技术架构设计
1. 整体架构
12306 抢票系统采用微服务架构,将系统拆分为多个独立的服务,如用户服务、票务服务、订单服务、支付服务等,每个服务可以独立部署和扩展。
系统前端采用前后端分离架构,使用 Vue.js 等框架构建高性能的 Web 界面;后端采用 Spring Cloud 微服务框架,实现服务的注册与发现、负载均衡、配置管理等功能。
2. 高并发处理方案
(1)负载均衡
使用 Nginx 和 LVS 进行四层和七层负载均衡,将请求均匀分发到多个应用服务器。同时,采用 CDN 加速静态资源的访问,减轻服务器压力。
(2)限流与熔断
在网关层使用 Sentinel 或 Hystrix 进行限流和熔断,防止系统被恶意请求或突发流量压垮。例如,对单个用户的请求频率进行限制,对异常服务进行熔断。
// 使用Sentinel进行限流的示例代码
@SentinelResource(value = "getTicket", blockHandler = "blockHandler")
public Ticket getTicket(String trainNumber, String date) {
// 查询车票信息的业务逻辑
}
public Ticket blockHandler(String trainNumber, String date, BlockException ex) {
// 限流或熔断后的处理逻辑
return new Ticket("系统繁忙,请稍后再试");
}
(3)异步处理
对于非核心业务逻辑,如短信通知、日志记录等,采用消息队列(如 RabbitMQ 或 Kafka)进行异步处理,提高系统的吞吐量。
3. 库存管理方案
(1)分布式锁
使用 Redis 分布式锁或 ZooKeeper 实现对票额库存的原子操作,确保同一时间只有一个请求能够修改库存。
java
// 使用Redis分布式锁的示例代码
public boolean deductStock(String trainNumber, String seatType, int count) {
String lockKey = "stock_lock:" + trainNumber + ":" + seatType;
Boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, "locked", 30, TimeUnit.SECONDS);
if (!locked) {
return false; // 获取锁失败
}
try {
// 查询库存
int stock = getStock(trainNumber, seatType);
if (stock >= count) {
// 扣减库存
boolean result = updateStock(trainNumber, seatType, stock - count);
return result;
}
return false;
} finally {
// 释放锁
redisTemplate.delete(lockKey);
}
}
(2)本地缓存与多级缓存
对于热门车次的票额信息,采用本地缓存(如 Caffeine)和分布式缓存(如 Redis)相结合的方式,减少对数据库的访问压力。
4. 防止黄牛与爬虫方案
(1)验证码机制
采用滑动拼图、点击图标等复杂验证码,增加自动化工具识别的难度。12306 的验证码系统采用了深度学习技术,不断优化验证码的难度和用户体验。
(2)用户行为分析
通过分析用户的访问频率、操作模式等行为特征,识别并拦截可疑用户。例如,对短期内频繁查询同一车次的用户进行限流或封禁。
(3)IP 黑名单与代理检测
维护 IP 黑名单,对已知的黄牛 IP 进行封禁。同时,检测用户是否使用代理服务器,对可疑代理进行拦截。
5. 预售与候补机制实现
(1)定时任务
使用 Quartz 或 Spring Task 实现预售车票的定时上架功能。例如,每天早上 6 点准时开放新一天的车票预订。
// 使用Spring Task实现定时任务的示例代码
@Component
public class TicketReleaseTask {
@Scheduled(cron = "0 0 6 * * ?") // 每天早上6点执行
public void releaseTickets() {
// 释放新一天的车票库存
ticketService.releaseTickets();
}
}
(2)候补队列
使用 Redis 的有序集合(Sorted Set)实现候补队列,根据用户提交候补的时间进行排序。当有退票或余票时,按照队列顺序通知用户。
6. 分布式事务处理
采用 TCC(Try-Confirm-Cancel)或 Saga 模式处理分布式事务。例如,在订单创建时,先冻结库存(Try),支付成功后确认订单并扣减库存(Confirm),支付失败则回滚库存(Cancel)。
// TCC模式处理订单创建的示例代码
public class OrderService {
@Transactional
public boolean createOrder(String userId, String trainNumber, String seatType) {
// Try阶段:冻结库存
boolean frozen = stockService.freezeStock(trainNumber, seatType, 1);
if (!frozen) {
return false;
}
try {
// 创建订单
Order order = new Order(userId, trainNumber, seatType);
orderRepository.save(order);
// 调用支付服务进行支付
boolean paid = paymentService.pay(order.getId(), order.getAmount());
if (paid) {
// Confirm阶段:确认订单和扣减库存
orderService.confirmOrder(order.getId());
stockService.confirmStock(trainNumber, seatType, 1);
return true;
} else {
// Cancel阶段:回滚订单和库存
orderService.cancelOrder(order.getId());
stockService.cancelStock(trainNumber, seatType, 1);
return false;
}
} catch (Exception e) {
// Cancel阶段:回滚订单和库存
stockService.cancelStock(trainNumber, seatType, 1);
return false;
}
}
}
三、数据存储设计
1. 关系型数据库
使用 MySQL 存储用户信息、订单信息、车次信息等结构化数据。采用主从复制和分库分表技术提高数据库的读写性能。
2. 非关系型数据库
使用 Redis 存储热点数据,如票额库存、用户会话信息等,提高数据访问速度。使用 MongoDB 存储日志数据和非结构化数据。
3. 缓存策略
采用多级缓存策略,本地缓存(Caffeine)存储最近访问的数据,分布式缓存(Redis)存储全局热点数据。设置合理的缓存过期时间和淘汰策略,保证数据的一致性。
四、高可用与灾备设计
1. 集群部署
所有服务均采用集群部署,避免单点故障。应用服务器、数据库、缓存等组件都部署多个实例,通过负载均衡器进行调度。
2. 熔断与降级
在服务间调用时使用熔断机制,当某个服务出现异常时,自动熔断对该服务的调用,并进行降级处理,保证系统的可用性。
3. 数据备份与恢复
定期对数据库进行全量备份和增量备份,确保数据的安全性。同时,设计完善的灾备方案,当主数据中心出现故障时,能够快速切换到备用数据中心。
五、性能优化与监控
1. 性能优化
- 数据库索引优化:合理设计数据库索引,提高查询效率。
- SQL 优化:避免慢查询,对复杂 SQL 进行性能分析和优化。
- 异步处理:将非核心业务逻辑异步化,提高系统吞吐量。
- 代码优化:减少锁的粒度,避免死锁,优化算法复杂度。
2. 监控系统
建立完善的监控系统,实时监控系统的性能指标、服务状态和业务指标。使用 Prometheus 和 Grafana 搭建监控平台,对系统的 CPU、内存、磁盘 IO、请求响应时间等进行监控和预警。
// 使用Micrometer和Prometheus进行指标监控的示例代码
@Service
public class TicketService {
private final Counter ticketQueryCounter;
private final Timer ticketQueryTimer;
public TicketService(MeterRegistry registry) {
// 计数器:统计车票查询次数
this.ticketQueryCounter = Counter.builder("ticket.query.count")
.description("Number of ticket queries")
.register(registry);
// 计时器:统计车票查询耗时
this.ticketQueryTimer = Timer.builder("ticket.query.time")
.description("Time taken to query tickets")
.register(registry);
}
public List<Ticket> queryTickets(String trainNumber, String date) {
// 记录查询次数
ticketQueryCounter.increment();
// 记录查询耗时
return ticketQueryTimer.record(() -> {
// 实际查询逻辑
return ticketRepository.findByTrainNumberAndDate(trainNumber, date);
});
}
}
六、总结与展望
12306 抢票系统的设计是一个复杂的系统工程,需要综合考虑高并发、数据一致性、安全性、可用性等多个方面的因素。通过采用微服务架构、分布式缓存、异步处理、分布式锁等技术手段,可以有效应对系统面临的各种挑战。