首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >12306偷偷改了?连续座位竟这么抢!

12306偷偷改了?连续座位竟这么抢!

作者头像
灬沙师弟
发布2026-01-05 18:07:46
发布2026-01-05 18:07:46
870
举报
文章被收录于专栏:Java面试教程Java面试教程

出行节奏越来越快

12306作为中国铁路官方购票平台

每天处理着数以亿计的购票请求

但你了解吗

他背后的技术架构远比我们想象的要复杂得多


我也曾参与演唱会票务系统、抢购功能的建设

也会思考国内顶流12306解决方案时怎样实现的

今天,我们探讨12306两个常见但复杂的购票需求:

  1. 两人同行购买连续座位
  2. 以及指定购买不同铺位类型

这两种票务问题

区别于电商系统

传统的电商系统

最小单位是商品 SKU

每个商品有库存

售完即止

而火车票的商品是弹性的

例如 北京 - 广州 京广线

一个座位可以是一张全程票

也可以是多张半程票

比如途经 17个站点

则每个座位会有 C(17, 2) 个组合,即136种商品

座位数就是库存量

所以会带来新的问题

代码语言:javascript
复制
某个区段最多允许出多少张票

某个区段最少允许出多少张票

某个站点上车的最多多少张

座位池与动态分配

我们从最基础的情形开始

如何在保证座位物理连续性的同时

高效地分配座位

12306采用了座位池管理机制

将车厢内的座位按照物理位置进行分组

每个座位池代表车厢内的一段连续座位

代码语言:javascript
复制
1车厢1排 A、B、C 号座位

1车厢1排 D、F 号座位

当用户选择"连续座位"时

系统会查询可用的座位池

确保座位在物理上是连续的


系统在车次开售前

根据车厢布局和座位配置

预先计算出所有可能的连续座位组合

并标记为"可用"或"已售"

当用户请求连续座位时

如选购 2 张连续的二等座

系统首先根据用户指定的座位类型

过滤可用座位池

然后根据座位连续性要求筛选出符合条件的座位池

这种机制确保了用户能够成功购买到连续座位

同时避免了因座位分配不当导致的系统混乱

混合铺位选择

长途旅程带老人和小孩时

卧铺购买会更倾向选择下铺

当用户要求 1 个下铺 + 1个中铺 时

12306需要处理不同座位类型的组合购买

这涉及到更复杂的配额管理和座位分配逻辑


首先在定义了不同的座位类型

例如 上铺、中铺、下铺

为每个车次、每个区段设置座位类型配额

例如北京到石家庄

下铺配额为10张,中铺配额为20张

当购票请求时

会根据座位类型和用户需求

优先分配用户指定的座位类型

当某个座位类型配额即将用完时

系统会自动调整售票策略


在处理连续座位请求时

采用了乐观锁机制

系统首先查询可用的座位池

然后尝试在数据库中标记这些座位为"已预留"

如果更新成功

则返回座位信息

失败则重试或提示用户选择其他座位

代码语言:javascript
复制
public class SeatAllocator {
    // 座位池缓存:key = [车次+区段+座位类型], value = 可用座位池列表
    private final Map<String, List<SeatPool>> seatPoolCache = new ConcurrentHashMap<>();
    
    // 配额管理:key = [车次+区段+座位类型], value = 当前剩余配额
    private final Map<String, Integer> quotaManager = new ConcurrentHashMap<>();
    
    // 预留订单缓存:key = 订单ID, value = 预留座位列表 + 超时时间戳
    private final Map<String, Reservation> reservationCache = new ConcurrentHashMap<>();

    // 1. 初始化座位池(系统启动时预加载)
    public void initSeatPools(String trainId, String segment, String seatType, int totalSeats) {
        // 实际实现:从数据库加载车厢布局,生成连续座位池
        List<SeatPool> pools = generateContinuousPools(segment, seatType, totalSeats);
        seatPoolCache.put(trainId + "_" + segment + "_" + seatType, pools);
        quotaManager.put(trainId + "_" + segment + "_" + seatType, totalSeats);
    }

    // 2. 核心分配方法(处理连续/混合场景)
    public boolean allocateSeats(BookingRequest request) {
        if (request.isContinuous()) {
            return allocateContinuous(request);
        } elseif (request.isMixedType()) {
            return allocateMixed(request);
        } else {
            return allocateStandard(request);
        }
    }

    // 3. 连续座位分配逻辑
    private boolean allocateContinuous(BookingRequest request) {
        String key = request.getTrainId() + "_" + request.getSegment() + "_" + request.getSeatType();
        
        // 1. 获取可用座位池(按物理连续性筛选)
        List<SeatPool> pools = seatPoolCache.getOrDefault(key, Collections.emptyList());
        SeatPool selectedPool = findContinuousPool(pools, request.getSeatCount());
        
        if (selectedPool == null) {
            returnfalse; // 无连续座位
        }

        // 2. 配额原子性检查(防止超卖)
        if (!checkQuotaAndReserve(key, request.getSeatCount())) {
            returnfalse;
        }

        // 3. 分配座位(标记为预留)
        List<Seat> allocatedSeats = selectedPool.allocateSeats(request.getSeatCount());
        reservationCache.put(request.getOrderId(), new Reservation(allocatedSeats, System.currentTimeMillis() + 45 * 60000));
        
        returntrue;
    }

    // 4. 混合铺位分配逻辑(如卧铺+中铺)
    private boolean allocateMixed(BookingRequest request) {
        List<String> seatTypes = request.getSeatTypes(); // [卧铺, 中铺]
        List<Integer> counts = request.getSeatCounts();  // [1, 1]
        
        // 1. 逐类型检查配额并预留
        for (int i = 0; i < seatTypes.size(); i++) {
            String type = seatTypes.get(i);
            String key = request.getTrainId() + "_" + request.getSegment() + "_" + type;
            
            if (!checkQuotaAndReserve(key, counts.get(i))) {
                returnfalse; // 任一类型配额不足
            }
        }

        // 2. 为每种类型分配座位(独立池)
        List<Seat> allSeats = new ArrayList<>();
        for (int i = 0; i < seatTypes.size(); i++) {
            String type = seatTypes.get(i);
            String key = request.getTrainId() + "_" + request.getSegment() + "_" + type;
            SeatPool pool = findAnyPool(seatPoolCache.getOrDefault(key, Collections.emptyList()));
            allSeats.addAll(pool.allocateSeats(counts.get(i)));
        }
        
        // 3. 保存混合分配结果
        reservationCache.put(request.getOrderId(), new Reservation(allSeats, System.currentTimeMillis() + 45 * 60000));
        returntrue;
    }

    // 购票请求格式
    class BookingRequest {
        private String trainId;
        private String segment;
        private String seatType;
        private List<String> seatTypes; // 混合铺位时使用
        private List<Integer> seatCounts; // 每种类型的数量
        private int seatCount;
        private boolean isContinuous;
        private boolean isMixedType;
        private String orderId;
    }
}

以上是伪代码片段

在真实场景下

还有高并发、实时更新、抢票行为等

为用户提供高效、公平的购票体验

其核心的领域模型设计和座位管理技术

都将是支撑12306持续为数亿用户提供可靠服务的关键

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-01-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Java面试教程 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 区别于电商系统
  • 座位池与动态分配
  • 混合铺位选择
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档