1. 项目背景与核心需求
作为一名长期从事Java企业级开发的工程师,我最近指导了几位计算机专业毕业生完成他们的毕业设计——基于SpringBoot的航空机票预定管理系统。这个项目看似简单,但实际涵盖了企业级应用开发的多个关键技术点。今天我就从实战角度,详细拆解这个系统的设计思路和实现细节。
航空票务系统本质上是一个典型的高并发事务处理系统,需要解决的核心问题包括:
- 实时票务库存管理(避免超卖)
- 多条件组合查询(航班、时间、价格等)
- 支付事务一致性
- 用户行为日志记录
传统教学项目往往只关注CRUD基础操作,而真实的票务系统需要考虑更多工程实践问题。我们这次设计的系统在满足基础功能的同时,特别强化了以下特性:
- 采用Redis缓存热点航班数据
- 实现分布式锁处理并发订票
- 引入Elasticsearch提升查询效率
- 使用Spring Cloud Stream处理异步消息
2. 技术选型与架构设计
2.1 技术栈决策依据
选择SpringBoot作为基础框架主要基于以下考量:
- 自动配置:简化了传统SSM框架的大量XML配置
- 内嵌容器:无需额外部署Tomcat,开发测试更便捷
- Starter依赖:一键引入常用功能模块(如redis、mybatis等)
- Actuator监控:方便后期性能调优
mermaid复制graph TD
A[前端] -->|HTTP/JSON| B(SpringBoot)
B --> C[MySQL]
B --> D[Redis]
B --> E[Elasticsearch]
C --> F[持久化存储]
D --> G[缓存层]
E --> H[搜索服务]
实际架构中我们采用分层设计:
- 表现层:Thymeleaf + Bootstrap
- 业务层:Spring MVC + Spring Transaction
- 数据层:MyBatis-Plus + Druid
- 缓存层:Redis + Redisson
- 搜索层:Elasticsearch + IK分词器
2.2 数据库设计要点
票务系统的核心表关系如下:
sql复制CREATE TABLE `flight` (
`id` bigint NOT NULL AUTO_INCREMENT,
`flight_no` varchar(20) NOT NULL COMMENT '航班号',
`departure` varchar(50) NOT NULL,
`destination` varchar(50) NOT NULL,
`departure_time` datetime NOT NULL,
`arrival_time` datetime NOT NULL,
`total_seats` int DEFAULT 0,
`available_seats` int DEFAULT 0,
`price` decimal(10,2) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_flight_no` (`flight_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `ticket_order` (
`id` bigint NOT NULL AUTO_INCREMENT,
`order_no` varchar(32) NOT NULL,
`user_id` bigint NOT NULL,
`flight_id` bigint NOT NULL,
`status` tinyint DEFAULT 0 COMMENT '0-待支付 1-已支付 2-已取消',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_order_no` (`order_no`),
KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
关键设计考虑:
- 航班表设置座位数双重校验(total_seats和available_seats)
- 订单表使用状态机模式管理订单生命周期
- 所有时间字段统一使用UTC时间存储
- 建立合适的索引提升查询效率
3. 核心功能实现细节
3.1 航班查询优化方案
原始方案直接查询MySQL导致性能瓶颈,我们改进后的实现:
java复制@Service
@RequiredArgsConstructor
public class FlightSearchServiceImpl implements FlightSearchService {
private final FlightMapper flightMapper;
private final RedisTemplate<String, Object> redisTemplate;
private final ElasticsearchRestTemplate esTemplate;
@Override
public Page<FlightVO> searchFlights(FlightQueryDTO query, Pageable pageable) {
// 1. 先查Redis热点缓存
String cacheKey = buildCacheKey(query);
Object cached = redisTemplate.opsForValue().get(cacheKey);
if (cached != null) {
return (Page<FlightVO>) cached;
}
// 2. 没有缓存则查询ES
NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder()
.withQuery(QueryBuilders.boolQuery()
.must(QueryBuilders.termQuery("departure", query.getDeparture()))
.must(QueryBuilders.termQuery("destination", query.getDestination()))
.must(QueryBuilders.rangeQuery("departure_time")
.gte(query.getDepartureDate().atStartOfDay())
.lt(query.getDepartureDate().plusDays(1).atStartOfDay())))
.withPageable(pageable);
SearchHits<FlightES> hits = esTemplate.search(builder.build(), FlightES.class);
// 3. 转换结果并缓存
Page<FlightVO> result = convertToPage(hits, pageable);
redisTemplate.opsForValue().set(cacheKey, result, 5, TimeUnit.MINUTES);
return result;
}
}
3.2 高并发订票解决方案
票务系统最关键的并发控制方案:
java复制public class TicketOrderService {
private final RedissonClient redissonClient;
@Transactional(rollbackFor = Exception.class)
public OrderResult createOrder(OrderRequest request) {
// 分布式锁防止超卖
RLock lock = redissonClient.getLock("flight:" + request.getFlightId());
try {
boolean locked = lock.tryLock(3, 10, TimeUnit.SECONDS);
if (!locked) {
throw new BusinessException("系统繁忙,请稍后重试");
}
// 1. 校验库存
Flight flight = flightMapper.selectByIdForUpdate(request.getFlightId());
if (flight.getAvailableSeats() <= 0) {
throw new BusinessException("座位已售罄");
}
// 2. 扣减库存
int updated = flightMapper.reduceInventory(request.getFlightId());
if (updated == 0) {
throw new BusinessException("库存不足");
}
// 3. 创建订单
TicketOrder order = buildOrder(request);
orderMapper.insert(order);
// 4. 发送支付超时消息
sendDelayMessage(order.getOrderNo());
return convertToResult(order);
} finally {
lock.unlock();
}
}
}
4. 典型问题与调优经验
4.1 缓存一致性解决方案
我们遇到的核心问题是:管理员修改航班信息后,缓存未能及时更新。最终采用的解决方案:
- 使用双重删除策略:
java复制@Transactional
public void updateFlight(FlightUpdateDTO dto) {
// 1. 更新数据库
flightMapper.updateById(convertToEntity(dto));
// 2. 删除缓存
String cacheKey = "flight:" + dto.getId();
redisTemplate.delete(cacheKey);
// 3. 异步再次删除(防止第一步删除失败)
eventPublisher.publishEvent(new CacheEvictEvent(cacheKey));
}
- 配合消息队列实现最终一致性:
java复制@EventListener
public void handleCacheEvict(CacheEvictEvent event) {
try {
redisTemplate.delete(event.getKey());
} catch (Exception e) {
// 记录日志并重试
retryTemplate.execute(context -> {
redisTemplate.delete(event.getKey());
return null;
});
}
}
4.2 性能调优关键指标
通过JMeter压测发现的性能瓶颈及解决方案:
| 场景 | QPS(优化前) | 瓶颈点 | 优化方案 | QPS(优化后) |
|---|---|---|---|---|
| 航班查询 | 128 | MySQL全表扫描 | 增加ES索引 | 2100 |
| 下单流程 | 56 | 数据库行锁竞争 | 引入Redis分布式锁 | 480 |
| 支付回调 | 78 | 同步写日志 | 改为异步队列处理 | 650 |
调优过程中总结的经验:
- MySQL连接池配置不是越大越好(我们最终设置druid.maxActive=50)
- Redis集群模式下避免使用keys命令(改用scan迭代)
- ES分片数建议设置为节点数的1.5倍
- 线程池配置需要根据IO/CPU密集型区分
5. 项目部署与监控
5.1 容器化部署方案
我们使用Docker Compose编排服务:
yaml复制version: '3'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
volumes:
- ./mysql/data:/var/lib/mysql
- ./mysql/conf:/etc/mysql/conf.d
ports:
- "3306:3306"
redis:
image: redis:6.2
command: redis-server --appendonly yes
volumes:
- ./redis/data:/data
ports:
- "6379:6379"
app:
build: .
depends_on:
- mysql
- redis
environment:
SPRING_PROFILES_ACTIVE: prod
ports:
- "8080:8080"
5.2 监控配置要点
- Spring Boot Actuator配置:
properties复制management.endpoints.web.exposure.include=health,info,metrics,prometheus
management.metrics.export.prometheus.enabled=true
- Grafana监控看板关键指标:
- 应用层:JVM内存、GC次数、线程状态
- 数据层:MySQL连接数、慢查询数
- 缓存层:Redis内存使用率、命中率
- 业务层:订单创建成功率、平均响应时间
6. 毕业设计扩展建议
如果想进一步提升项目竞争力,可以考虑:
- 引入微服务架构:
- 将用户服务、航班服务、订单服务拆分为独立模块
- 使用Spring Cloud Alibaba实现服务治理
- 通过Seata处理分布式事务
- 增加智能推荐功能:
- 基于用户历史订单实现协同过滤推荐
- 使用Spark MLlib训练推荐模型
- 实时推荐使用Flink流处理
- 强化安全防护:
- 接口幂等性设计
- 敏感数据加密存储
- 定期安全扫描(使用OWASP ZAP)
这个项目从技术选型到最终实现,完整走过了企业级Java开发的典型流程。在实现基础功能的同时,适当引入Redis、ES等中间件,既保证了项目的可行性,又体现了技术深度。对于计算机专业毕业生来说,掌握这种"基础功能+特色优化"的项目设计思路,能显著提升毕业设计的含金量。