ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于Java的航空票务系统设计与高并发优化实践

基于Java的航空票务系统设计与高并发优化实践

1. 项目概述:航空出行服务平台的Java实现

航空票务管理系统作为现代出行服务的重要基础设施,正在经历从传统柜台服务向移动化、云端化的转型。这个毕业设计项目采用Java技术栈构建了一个名为"云上航空"的航空出行服务平台,实现了机票查询、预订、支付、订单管理等核心功能模块。平台采用B/S架构,前端基于Android开发,后端使用Spring Boot框架,数据库选用MySQL,是一套完整的航空票务解决方案。

我在实际开发过程中发现,航空票务系统与传统电商系统存在显著差异:一方面需要处理实时变动的航班数据和座位库存,另一方面要应对高并发的查询请求和严格的交易一致性要求。这个项目很好地模拟了真实航空公司的业务场景,对Java后端开发能力的锻炼非常全面。

2. 系统架构设计

2.1 技术选型与架构设计

后端技术栈选择基于以下考虑:

  • Spring Boot 2.7.x:提供快速启动和自动配置,简化了传统Spring应用的初始搭建流程
  • MyBatis-Plus 3.5.x:增强的ORM框架,内置通用Mapper和分页插件,减少样板代码
  • Redis 6.x:缓存热点数据和分布式锁实现,提升系统响应速度
  • RabbitMQ 3.9.x:异步处理订单创建和通知,削峰填谷
  • Alipay SDK:集成主流支付渠道,确保交易安全

前端采用Android原生开发,主要考虑因素:

  • 更好的性能表现和用户体验
  • 完整的设备API访问能力
  • Material Design设计规范的天然支持

系统采用分层架构设计:

表现层(Android) → 业务逻辑层(Spring Boot) → 数据访问层(MyBatis) → 存储层(MySQL) ↑ 缓存层(Redis) 消息队列(RabbitMQ)

2.2 数据库设计要点

航空票务系统的数据库设计有几个关键难点:

  1. 航班数据模型复杂,涉及航空公司、机型、航线、班期等多维关联
  2. 座位库存需要精确控制,避免超售
  3. 价格策略灵活多变,可能包含基础票价、燃油附加费、机场建设费等

核心表结构设计示例:

CREATE TABLE `flight` ( `id` bigint NOT NULL AUTO_INCREMENT, `flight_no` varchar(10) NOT NULL COMMENT '航班号', `departure_airport` varchar(3) NOT NULL COMMENT '出发机场IATA代码', `arrival_airport` varchar(3) NOT NULL COMMENT '到达机场IATA代码', `departure_time` datetime NOT NULL COMMENT '计划起飞时间', `arrival_time` datetime NOT NULL COMMENT '计划到达时间', `aircraft_id` int NOT NULL COMMENT '机型ID', `total_seats` int NOT NULL COMMENT '总座位数', `available_seats` int NOT NULL COMMENT '可用座位数', `base_price` decimal(10,2) NOT NULL COMMENT '基础票价', PRIMARY KEY (`id`), UNIQUE KEY `idx_flight_no_date` (`flight_no`,`departure_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

注意事项:航班表必须建立航班号+起飞时间的联合唯一索引,避免重复航班数据。可用座位数需要额外设计乐观锁机制,防止并发订票时的数据竞争。

3. 核心功能实现

3.1 航班查询与筛选功能

航班查询是系统最高频的操作,需要特别优化:

  1. 采用多条件组合查询,支持按日期、出发/到达城市、航空公司等筛选
  2. 结果排序支持按价格、起飞时间、飞行时长等维度
  3. 分页加载避免一次性返回过多数据

后端接口示例:

@GetMapping("/flights") public PageResult<FlightVO> searchFlights( @RequestParam String departureCity, @RequestParam String arrivalCity, @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate departureDate, @RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer size, @RequestParam(required = false) String sortBy) { // 构建查询条件 LambdaQueryWrapper<Flight> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(Flight::getDepartureAirport, airportService.getAirportCode(departureCity)); queryWrapper.eq(Flight::getArrivalAirport, airportService.getAirportCode(arrivalCity)); queryWrapper.between(Flight::getDepartureTime, departureDate.atStartOfDay(), departureDate.plusDays(1).atStartOfDay()); // 处理排序 if ("price".equals(sortBy)) { queryWrapper.orderByAsc(Flight::getBasePrice); } else { queryWrapper.orderByAsc(Flight::getDepartureTime); } // 执行分页查询 Page<Flight> pageInfo = new Page<>(page, size); IPage<Flight> flightPage = flightMapper.selectPage(pageInfo, queryWrapper); // 转换为VO并返回 return PageResult.success(flightPage.convert(this::convertToVO)); }

性能优化措施:

  • 使用Redis缓存热门航线查询结果,设置5分钟过期时间
  • 数据库表建立合适的索引,特别是出发/到达机场和起飞时间字段
  • 采用延迟加载策略,初始只返回基本信息,详情数据按需加载

3.2 机票预订与库存控制

机票预订是系统的核心交易功能,需要处理的关键问题:

  1. 座位库存的原子性操作
  2. 防止重复预订
  3. 处理并发冲突

采用"查询+预占+确认"的三阶段模式:

  1. 查询可用航班和座位
  2. 预占座位(生成临时订单)
  3. 支付成功后确认订单

库存预占的伪代码实现:

public boolean reserveSeats(Long flightId, int seats) { // 使用航班ID作为分布式锁key String lockKey = "flight:lock:" + flightId; try { // 尝试获取分布式锁 boolean locked = redisTemplate.opsForValue() .setIfAbsent(lockKey, "1", 30, TimeUnit.SECONDS); if (!locked) { throw new BusinessException("系统繁忙,请稍后再试"); } // 检查剩余座位 Flight flight = flightMapper.selectById(flightId); if (flight.getAvailableSeats() < seats) { throw new BusinessException("剩余座位不足"); } // 预占座位(乐观锁) int updated = flightMapper.updateAvailableSeats( flightId, flight.getAvailableSeats() - seats, flight.getAvailableSeats()); if (updated == 0) { throw new BusinessException("座位已被其他用户预订"); } return true; } finally { // 释放锁 redisTemplate.delete(lockKey); } }

重要提示:实际项目中需要考虑锁的粒度、超时时间以及锁释放的可靠性,避免死锁和长时间阻塞。可以采用Redisson等成熟框架简化分布式锁的实现。

4. 支付与订单处理

4.1 支付流程设计

航空票务系统的支付流程有特殊要求:

  1. 支付时效性:通常要求30分钟内完成支付,否则自动取消
  2. 多支付方式:需要支持信用卡、第三方支付等多种渠道
  3. 退款复杂:涉及自愿退票和非自愿退票不同规则

支付状态机设计:

待支付 → 支付中 → 支付成功 → 出票完成 ↓ ↓ 超时取消 支付失败

支付核心逻辑实现:

@Transactional public PaymentResult processPayment(Long orderId, PaymentRequest request) { // 校验订单状态 Order order = orderMapper.selectById(orderId); if (order == null || !OrderStatus.PENDING_PAYMENT.equals(order.getStatus())) { throw new BusinessException("订单状态异常"); } // 更新为支付中状态 order.setStatus(OrderStatus.PAYING); orderMapper.updateById(order); try { // 调用支付渠道 PaymentResponse response = paymentService.execute(request); if (response.isSuccess()) { // 支付成功处理 order.setStatus(OrderStatus.PAID); order.setPaymentTime(LocalDateTime.now()); order.setTransactionId(response.getTransactionId()); orderMapper.updateById(order); // 异步出票 ticketIssueService.asyncIssueTicket(orderId); return PaymentResult.success(response); } else { // 支付失败处理 order.setStatus(OrderStatus.PAYMENT_FAILED); orderMapper.updateById(order); return PaymentResult.fail(response.getErrorMsg()); } } catch (Exception e) { // 异常处理 order.setStatus(OrderStatus.PAYMENT_EXCEPTION); orderMapper.updateById(order); throw new BusinessException("支付处理异常", e); } }

4.2 订单状态管理

航空订单状态流转复杂,典型状态包括:

  • 待支付
  • 支付中
  • 已支付
  • 出票中
  • 已出票
  • 已取消
  • 已改签
  • 已退款

采用状态模式实现订单状态管理:

public interface OrderState { void pay(OrderContext context); void cancel(OrderContext context); void refund(OrderContext context); void change(OrderContext context); } @Component @Scope("prototype") public class PendingPaymentState implements OrderState { @Override public void pay(OrderContext context) { // 转换为支付中状态 context.getOrder().setStatus(OrderStatus.PAYING); orderMapper.updateById(context.getOrder()); context.setState(applicationContext.getBean(PayingState.class)); // 发起实际支付 paymentService.startPayment(context.getOrder()); } @Override public void cancel(OrderContext context) { // 超时取消逻辑 context.getOrder().setStatus(OrderStatus.CANCELLED); orderMapper.updateById(context.getOrder()); context.setState(applicationContext.getBean(CancelledState.class)); // 释放座位库存 inventoryService.releaseSeats(context.getOrder()); } }

5. 系统优化与扩展

5.1 性能优化实践

在高并发场景下,我们实施了以下优化措施:

  1. 多级缓存策略:

    • 本地缓存(Caffeine):缓存静态数据如城市、机场信息
    • Redis缓存:缓存热门航班查询结果,设置5分钟TTL
    • 数据库缓存:合理配置MySQL查询缓存
  2. 查询优化:

    @Cacheable(value = "flights", key = "#departure+'-'+#arrival+'-'+#date") public List<FlightVO> searchFlights(String departure, String arrival, LocalDate date) { // 复杂查询逻辑 }
  3. 异步处理:

    • 使用RabbitMQ实现订单创建异步化
    • 短信/邮件通知采用消息队列削峰
    • 出票操作后台异步执行

5.2 安全防护措施

航空系统对安全性要求极高,我们实现了:

  1. 数据加密:

    • 敏感字段如身份证号、手机号数据库加密存储
    • 使用HTTPS传输所有数据
    • 支付请求签名验证
  2. 防护机制:

    • 防SQL注入:MyBatis使用参数化查询
    • 防XSS:前端过滤特殊字符,后端再次校验
    • 防CSRF:重要操作需验证token
  3. 限流措施:

    @RateLimiter(value = 100, key = "'search:' + #departure + #arrival") @GetMapping("/search") public Result searchFlights(...) { // 查询逻辑 }

6. 开发经验与心得

在实际开发过程中,我总结了以下几点重要经验:

  1. 航班数据建模是基础也是难点,需要提前与业务方确认清楚各种边界情况,如:

    • 经停航班如何处理
    • 代码共享航班如何显示
    • 航班延误状态更新机制
  2. 库存控制必须考虑分布式环境下的原子操作,我们最终采用了:

    • Redis分布式锁保证互斥
    • 数据库乐观锁保证数据一致性
    • 预占库存+定时释放机制
  3. 支付流程要特别注意:

    • 支付结果异步通知的处理
    • 支付状态与订单状态的最终一致性
    • 支付超时与库存释放的协调
  4. 性能优化要有的放矢:

    • 先通过压测找出瓶颈点
    • 优先优化高频查询接口
    • 缓存策略要考虑数据一致性

这个项目让我深入理解了复杂业务系统的开发流程,特别是在处理分布式事务、高并发场景时的各种技术选型和权衡。最大的收获是学会了如何将一个复杂的业务领域逐步拆解为可实现的模块,并通过合适的技术方案解决实际问题。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进