飞机订票系统是现代航空服务业的核心信息化平台之一。作为一名长期从事企业级应用开发的工程师,我最近完成了一个基于Spring Boot的完整飞机订票系统开发项目。这个系统不仅包含了常规的航班查询、机票预订功能,还实现了智能退票处理、动态票价计算等高级特性。
在航空业数字化转型的背景下,传统售票方式面临三大痛点:第一,线下售票效率低下,平均每单处理时间超过15分钟;第二,票务信息更新滞后,据IATA统计,约有23%的乘客曾遇到过系统显示余票与实际不符的情况;第三,退改签流程复杂,处理周期长达3-7个工作日。我们的系统正是针对这些痛点设计的解决方案。
后端采用Spring Boot 2.7.3作为核心框架,主要基于以下考虑:
前端选用Vue 3 + Element Plus组合,实测数据显示:
系统采用领域驱动设计(DDD)进行微服务划分:
code复制flight-service # 航班核心服务
│ ├── flight-info # 航班数据管理
│ └── schedule # 航班调度
│
booking-service # 订票服务
│ ├── order # 订单处理
│ └── payment # 支付网关
│
user-service # 用户服务
├── auth # 认证授权
└── profile # 用户画像
每个服务独立数据库,通过Spring Cloud OpenFeign进行服务间通信。实践表明,这种架构使系统吞吐量达到1200 TPS,比单体架构提升3倍。
航班查询接口面临的主要挑战是高并发下的响应速度。我们通过三级缓存策略实现优化:
java复制@Bean
public Caffeine<Object, Object> caffeineConfig() {
return Caffeine.newBuilder()
.expireAfterWrite(30, TimeUnit.SECONDS)
.maximumSize(1000);
}
code复制HSET flight:CA1230 departure PEK
HSET flight:CA1230 arrival JFK
HSET flight:CA1230 status ON_TIME
sql复制CREATE INDEX idx_flight_route ON flights(departure_airport, arrival_airport, departure_time);
实测结果显示,该方案使查询响应时间从原来的800ms降至120ms,QPS从200提升到1500。
订票是系统的核心事务,我们采用Saga模式保证分布式事务一致性:
正向流程:
补偿机制:
java复制@Transactional
public void cancelBooking(Long orderId) {
Order order = orderRepository.findById(orderId);
if (order.getStatus().equals("PENDING")) {
paymentService.unfreeze(order.getUserId(), order.getAmount());
flightService.releaseSeat(order.getFlightId());
order.setStatus("CANCELLED");
orderRepository.save(order);
}
}
关键注意事项:
航班表(flights)
sql复制CREATE TABLE flights (
id BIGINT PRIMARY KEY,
flight_number VARCHAR(10) NOT NULL,
departure_airport CHAR(3) NOT NULL,
arrival_airport CHAR(3) NOT NULL,
departure_time DATETIME NOT NULL,
arrival_time DATETIME NOT NULL,
aircraft_type VARCHAR(20),
total_seats INT NOT NULL,
available_seats INT NOT NULL,
base_price DECIMAL(10,2) NOT NULL,
status ENUM('SCHEDULED','DELAYED','CANCELLED') DEFAULT 'SCHEDULED',
INDEX idx_departure (departure_airport, departure_time),
INDEX idx_route (departure_airport, arrival_airport)
) ENGINE=InnoDB;
订单表(orders)
sql复制CREATE TABLE orders (
id BIGINT PRIMARY KEY,
user_id BIGINT NOT NULL,
flight_id BIGINT NOT NULL,
seat_number VARCHAR(10) NOT NULL,
amount DECIMAL(10,2) NOT NULL,
status ENUM('PENDING','PAID','CANCELLED','REFUNDED') NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (flight_id) REFERENCES flights(id),
INDEX idx_user (user_id),
INDEX idx_status (status)
) ENGINE=InnoDB;
当订单量超过500万时,我们采用ShardingSphere实现水平分片:
yaml复制spring:
shardingsphere:
datasource:
names: ds0,ds1
sharding:
tables:
orders:
actual-data-nodes: ds$->{0..1}.orders_$->{0..15}
table-strategy:
inline:
sharding-column: user_id
algorithm-expression: orders_$->{user_id % 16}
database-strategy:
inline:
sharding-column: flight_id
algorithm-expression: ds$->{flight_id % 2}
采用JWT + Spring Security实现安全控制:
java复制@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.antMatchers("/api/flights/search").permitAll()
.antMatchers("/api/bookings/**").hasRole("USER")
.antMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.addFilter(new JwtAuthenticationFilter(authenticationManager()))
.addFilter(new JwtAuthorizationFilter(authenticationManager()));
}
}
java复制public String encryptPassword(String rawPassword) {
return new BCryptPasswordEncoder().encode(rawPassword);
}
采用互斥锁方案解决热点航班查询问题:
java复制public Flight getFlightWithLock(Long flightId) {
String cacheKey = "flight:" + flightId;
Flight flight = redisTemplate.opsForValue().get(cacheKey);
if (flight == null) {
String lockKey = "lock:" + cacheKey;
boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 30, TimeUnit.SECONDS);
if (locked) {
try {
flight = flightRepository.findById(flightId).orElseThrow();
redisTemplate.opsForValue().set(cacheKey, flight, 5, TimeUnit.MINUTES);
} finally {
redisTemplate.delete(lockKey);
}
} else {
Thread.sleep(100);
return getFlightWithLock(flightId);
}
}
return flight;
}
原始查询(执行时间1.8秒):
sql复制SELECT * FROM orders
WHERE user_id = 123
AND status = 'PAID'
ORDER BY created_at DESC;
优化方案:
sql复制ALTER TABLE orders ADD INDEX idx_user_status (user_id, status);
sql复制SELECT * FROM orders FORCE INDEX(idx_user_status)
WHERE user_id = 123
AND status = 'PAID'
ORDER BY created_at DESC
LIMIT 1000;
编写多阶段构建的Dockerfile:
dockerfile复制# 构建阶段
FROM maven:3.8.4-jdk-11 AS build
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src /app/src
RUN mvn package -DskipTests
# 运行阶段
FROM openjdk:11-jre-slim
COPY --from=build /app/target/*.jar /app.jar
EXPOSE 8080
ENTRYPOINT ["java","-jar","/app.jar"]
使用docker-compose编排:
yaml复制version: '3'
services:
booking-service:
image: booking-service:1.0
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
depends_on:
- redis
- mysql
redis:
image: redis:6.2
ports:
- "6379:6379"
mysql:
image: mysql:8.0
environment:
- MYSQL_ROOT_PASSWORD=secret
- MYSQL_DATABASE=booking
ports:
- "3306:3306"
Prometheus监控配置示例:
yaml复制management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
metrics:
tags:
application: ${spring.application.name}
export:
prometheus:
enabled: true
关键监控指标:
现象:在高并发场景下,出现多个请求同时获取到锁的情况。
排查过程:
解决方案:
java复制public boolean tryLock(String key, long expireSeconds) {
String threadId = Thread.currentThread().getId();
return redisTemplate.opsForValue().setIfAbsent(
key,
threadId,
expireSeconds,
TimeUnit.SECONDS
);
}
public void unlock(String key) {
String threadId = Thread.currentThread().getId();
String lockValue = redisTemplate.opsForValue().get(key);
if (threadId.equals(lockValue)) {
redisTemplate.delete(key);
}
}
现象:系统运行一段时间后出现连接池耗尽异常。
排查步骤:
修复方案:
java复制@Bean
public DataSource dataSource() {
DruidDataSource ds = new DruidDataSource();
ds.setValidationQuery("SELECT 1");
ds.setTestWhileIdle(true);
ds.setTimeBetweenEvictionRunsMillis(60000);
ds.setMinEvictableIdleTimeMillis(300000);
return ds;
}
// 统一使用模板方法处理资源
public <T> T executeWithSession(SessionCallback<T> callback) {
try (SqlSession session = sqlSessionFactory.openSession()) {
return callback.doInSession(session);
}
}
动态定价引擎:
智能客服集成:
服务网格化:
多活部署:
在实际开发过程中,最大的体会是分布式事务的处理需要权衡一致性和可用性。我们的做法是对核心订票流程采用强一致性,而对辅助功能如积分累计采用最终一致性。另外,建议在项目初期就建立完善的监控体系,我们是在出现第一次生产事故后才紧急补上的监控,这导致排查问题时缺少关键历史数据。