1. 航空订票系统架构设计
航空订票系统本质上是一个典型的高并发事务处理系统,我在实际项目中采用分层架构设计。最底层是MySQL数据库层,中间是Java业务逻辑层,最上层是Web展示层。这种分层设计最大的好处是各层职责明确,修改任何一层都不会影响其他层。
数据库层我选择了MySQL 8.0,主要看中它的ACID特性和事务处理能力。在表结构设计上,我特别注意避免数据冗余。比如航班表和机票表是分开的,通过外键关联。这样当航班信息变更时,只需要修改一处即可。
业务逻辑层采用Spring Boot框架,它内置的Tomcat容器能轻松应对数百并发请求。我特别使用了Spring的事务管理,确保订票过程中的多个数据库操作要么全部成功,要么全部回滚。比如用户支付成功后,系统需要同时减少座位数和生成订单,这两个操作必须在一个事务中完成。
Web层采用Thymeleaf模板引擎,相比JSP更轻量级。前端页面做了静态资源缓存,减少服务器压力。对于高并发的航班查询请求,我加入了Redis缓存,将热门航线信息缓存起来,实测查询性能提升了5倍以上。
2. 核心数据库表设计
2.1 航班信息表设计
航班表(flight)是整个系统的核心,字段设计直接影响查询效率。我采用以下关键字段:
CREATE TABLE `flight` ( `id` bigint NOT NULL AUTO_INCREMENT, `flight_no` varchar(20) NOT NULL COMMENT '航班号', `departure_city` varchar(50) NOT NULL, `arrival_city` varchar(50) NOT NULL, `departure_time` datetime NOT NULL, `arrival_time` datetime NOT NULL, `aircraft_type` varchar(30) DEFAULT NULL, `total_seats` int DEFAULT '0' COMMENT '总座位数', `available_seats` int DEFAULT '0' COMMENT '可用座位数', `price` decimal(10,2) DEFAULT NULL, PRIMARY KEY (`id`), KEY `idx_city_time` (`departure_city`,`arrival_city`,`departure_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;特别注意创建了联合索引idx_city_time,这对提高按城市和时间的查询速度非常关键。实测在有索引的情况下,百万级数据量的查询能在50ms内完成。
2.2 订单表设计
订单表(order)需要记录完整的交易信息,我采用状态字段来跟踪订单生命周期:
CREATE TABLE `order` ( `id` bigint NOT NULL AUTO_INCREMENT, `order_no` varchar(32) NOT NULL COMMENT '订单编号', `user_id` bigint NOT NULL, `flight_id` bigint NOT NULL, `passenger_name` varchar(100) NOT NULL, `id_card_no` varchar(18) NOT NULL, `seat_type` tinyint DEFAULT '1' COMMENT '1经济舱 2商务舱', `amount` decimal(10,2) NOT NULL, `status` tinyint DEFAULT '0' COMMENT '0待支付 1已支付 2已取消', `create_time` datetime NOT NULL, `update_time` datetime NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_order_no` (`order_no`), KEY `idx_user` (`user_id`), KEY `idx_flight` (`flight_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;订单号使用UUID生成确保唯一性。status字段的设计让系统可以轻松扩展新的订单状态,比如退款中、已完成等。
3. 关键业务逻辑实现
3.1 航班查询功能
航班查询是最频繁的操作,我采用缓存+数据库的查询策略:
public List<Flight> searchFlights(String departure, String arrival, Date date) { String cacheKey = "flight:" + departure + ":" + arrival + ":" + new SimpleDateFormat("yyyyMMdd").format(date); // 先查Redis缓存 String cached = redisTemplate.opsForValue().get(cacheKey); if (cached != null) { return JSON.parseArray(cached, Flight.class); } // 缓存没有则查数据库 List<Flight> flights = flightMapper.selectByCitiesAndDate(departure, arrival, date); // 设置缓存,过期时间30分钟 if (!flights.isEmpty()) { redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(flights), 30, TimeUnit.MINUTES); } return flights; }对于热门航线,这种设计能减少90%以上的数据库查询。缓存过期时间设置为30分钟,既保证了数据新鲜度,又避免了频繁查询数据库。
3.2 订票事务处理
订票是典型的分布式事务场景,我采用本地事务+消息队列的方案:
@Transactional public Order createOrder(Long flightId, Long userId, PassengerInfo passenger) { // 1. 查询航班信息 Flight flight = flightMapper.selectById(flightId); if (flight == null) { throw new BizException("航班不存在"); } // 2. 检查余票 if (flight.getAvailableSeats() <= 0) { throw new BizException("航班已售罄"); } // 3. 扣减库存 int updated = flightMapper.reduceSeat(flightId); if (updated == 0) { throw new BizException("座位已被其他用户锁定"); } // 4. 生成订单 Order order = new Order(); order.setOrderNo(generateOrderNo()); order.setFlightId(flightId); order.setUserId(userId); order.setPassengerName(passenger.getName()); order.setIdCardNo(passenger.getIdCard()); order.setAmount(flight.getPrice()); order.setStatus(OrderStatus.UNPAID.getCode()); order.setCreateTime(new Date()); order.setUpdateTime(new Date()); orderMapper.insert(order); // 5. 发送支付超时消息 sendPaymentTimeoutMessage(order.getOrderNo()); return order; }这个实现有几个关键点:
- 使用@Transactional确保减库存和生成订单的原子性
- 使用乐观锁防止超卖
- 订单生成后发送延迟消息,30分钟后检查支付状态
4. 并发控制与性能优化
4.1 解决超卖问题
航空订票最怕的就是超卖,我采用三种措施防止超卖:
- 数据库乐观锁:
@Update("UPDATE flight SET available_seats = available_seats - 1 WHERE id = #{id} AND available_seats > 0") int reduceSeat(Long id);- Redis分布式锁:
public boolean lockSeat(Long flightId) { String lockKey = "seat_lock:" + flightId; return redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS); }- 异步库存校验: 订单创建后,会有定时任务检查实际库存与订单是否匹配,发现不一致会自动取消订单并通知用户。
4.2 性能优化实践
在高并发场景下,我做了以下优化:
- 使用连接池:配置HikariCP连接池,最大连接数设置为100
spring.datasource.hikari.maximum-pool-size=100 spring.datasource.hikari.connection-timeout=30000慢查询优化:通过EXPLAIN分析慢查询,添加适当索引
读写分离:查询走从库,写入走主库
分库分表:当订单表超过500万时,按用户ID哈希分表
5. 安全设计与异常处理
5.1 支付安全设计
支付环节我采用以下安全措施:
- 所有敏感信息如身份证号在数据库加密存储
public String encryptIdCard(String idCard) { return EncryptUtil.aesEncrypt(idCard, secretKey); }- 支付请求签名验证
public boolean verifySign(PaymentRequest request) { String raw = request.getOrderNo() + request.getAmount() + salt; return DigestUtils.md5Hex(raw).equals(request.getSign()); }- 支付结果异步通知校验
5.2 异常处理策略
系统定义了一套完整的异常处理机制:
@ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(BizException.class) @ResponseBody public Result handleBizException(BizException e) { return Result.fail(e.getCode(), e.getMessage()); } @ExceptionHandler(Exception.class) @ResponseBody public Result handleException(Exception e) { log.error("系统异常", e); return Result.fail(500, "系统繁忙,请稍后再试"); } }对于预期内的业务异常如"座位已售罄",返回明确的错误提示;对于系统异常,记录详细日志并返回友好提示。
6. 系统扩展与演进
随着业务增长,系统架构也在不断演进:
服务拆分:将原本的单体应用拆分为航班服务、订单服务、支付服务等
引入消息队列:使用RocketMQ处理异步任务如发送行程单
分布式事务:采用Seata处理跨服务的分布式事务
弹性设计:增加熔断降级机制,当航班查询服务不可用时返回缓存数据
这些架构演进保证了系统能够支撑从日订单几百到上万的平滑过渡。