关于高铁抢票系统设计的一些个人构思

325 阅读15分钟

针对高铁票相关的核心数据库表设计,我们可以考虑以下几个表:

高铁车次表(train)

  • train_id:高铁车次ID,主键
  • train_number:高铁车次编号,如 G123
  • departure_station:出发站点
  • arrival_station:到达站点
  • departure_time:出发时间
  • arrival_time:到达时间
  • distance:距离

车次途经站点表(train_stop)

  • train_id 车次编号
  • station_id 站点编号
  • arrival_time 到达时间
  • departure_time 出发时间
  • sequence 途经站点序号

其中,train_stop表的sequence字段用于标识车次经过该站点的顺序,可以用于查询经过某个站点的所有车次及其到达时间和出发时间等信息。

 高铁车次座位表(train_seat)

  • train_seat_id:高铁车次座位ID,主键
  • train_id:高铁车次ID,外键
  • seat_type:座位类型,如一等座、二等座、商务座等
  • total_count:总座位数
  • remaining_count:剩余座位数

高铁车票表(train_ticket)

  • train_ticket_id:高铁车票ID,主键
  • train_id:高铁车次ID,外键
  • departure_station:出发站点
  • arrival_station:到达站点
  • departure_time:出发时间
  • arrival_time:到达时间
  • seat_type:座位类型,如一等座、二等座、商务座等
  • ticket_count:购买数量
  • user_id:用户ID

用户表(user)

  • user_id:用户ID,主键
  • username:用户名
  • password:密码
  • email:电子邮件地址
  • phone:手机号码
  1. 订单表:存储用户购票的订单信息,包括订单ID、购票时间、出发站点、到达站点、车次、座位类型、票价等;
  2. 车票库存表:存储车票的库存信息,包括车次、座位类型、剩余票数等。

用户购票成功后,需要生成相应的支付流水,记录用户支付信息以及订单信息,以便后续查询和管理。一般需要设计如下的支付流水表:

pay_order表

字段名类型描述
idbigint主键id
order_novarchar(50)订单编号,唯一
user_idbigint用户id
train_novarchar(50)车次编号
start_stationvarchar(50)起始站点
end_stationvarchar(50)终点站点
start_timedatetime出发时间
passenger_namevarchar(50)乘客姓名
seat_typevarchar(10)座位类型,如二等座、一等座等
seat_novarchar(10)座位号
ticket_pricedecimal(10,2)票价
create_timedatetime创建时间
update_timedatetime更新时间
statusint订单状态,0:未支付,1:已支付,2:已取消,3:已改签
payment_methodint支付方式,0:微信支付,1:支付宝支付等
payment_timedatetime支付时间
payment_channelvarchar(50)支付渠道
payment_amountdecimal(10,2)实际支付金额
payment_currencyvarchar(10)支付币种
payment_statusint支付状态,0:未支付,1:已支付
trade_novarchar(50)第三方支付平台的交易号
pay_order_novarchar(50)自己系统内的支付流水号,唯一
refund_timedatetime退款时间
refund_amountdecimal(10,2)退款金额
refund_statusint退款状态,0:未退款,1:已退款

当用户需要取消车票或者改签车票时,需要修改相应的订单状态以及更新相应的票务信息,一般需要设计如下的表:

ticket_order表

字段名类型描述
idbigint主键id
order_novarchar(50)订单编号,唯一
user_idbigint用户id
train_novarchar(50)车次编号
start_stationvarchar(50)起始站点
end_stationvarchar(50)终点站点
start_timedatetime出发时间
passenger_namevarchar(50)乘客姓名
seat_typevarchar(10)座位类型

在这些表的设计中,高铁车次表和高铁车次座位表是比较基础的,用于存储高铁车次和对应的座位信息;高铁车票表用于存储用户购买的车票信息,包括出发站点、到达站点、出发时间、到达时间、座位类型、购买数量等信息;用户表则是用于存储用户信息的。

用户购票支付流水,可以设计一个名为user_order的表,该表包含以下字段:

  • id:订单ID,主键,自增长。
  • user_id:用户ID,外键,关联到user表的id字段。
  • train_no:车次编号,外键,关联到train表的train_no字段。
  • start_station:起始站点,外键,关联到station表的station_name字段。
  • end_station:终点站点,外键,关联到station表的station_name字段。
  • seat_type:座位类型,外键,关联到seat_type表的type字段。
  • price:订单总价。
  • status:订单状态,0表示未支付,1表示已支付,2表示已取消。
  • create_time:订单创建时间。
  • update_time:订单更新时间。

对于取消车票和车票改签,可以在user_order表中新增两个字段:

  • cancel_time:订单取消时间。
  • exchange_time:订单改签时间。

同时,在user_order表中新增一个refund字段,表示订单是否可以退票。如果订单可以退票,则该字段为1,否则为0。当用户进行退票操作时,系统会检查订单是否可以退票,如果可以,则将status字段更新为2,并在refund字段中标记为1。

对于用户进行车票改签操作,需要将原订单进行退票,并创建一张新订单。具体操作步骤如下:

  1. 检查原订单是否可以退票,如果可以,则将status字段更新为2,并在refund字段中标记为1。
  2. 将原订单的信息拷贝到一张新订单中,同时将status字段设置为0,表示新订单未支付。
  3. 更新新订单的信息,包括车次、座位类型、起始站点、终点站点、价格等信息。
  4. 将新订单信息返回给用户,并提醒用户支付新订单。

以上是用户购票支付流程相关的表设计,包括订单表和车票状态表,以及取消车票和车票改签相关的字段和操作。需要注意的是,以上表结构仅供参考,实际的设计需要考虑业务需求和性能要求,进行必要的优化和调整。

购票业务

用户购票业务还涉及以下几个表:

  1. 用户表:存储用户的基本信息,如用户ID、姓名、手机号、邮箱等;
  2. 订单表:存储用户购票的订单信息,包括订单ID、购票时间、出发站点、到达站点、车次、座位类型、票价等;
  3. 乘客信息表:存储乘客的基本信息,如姓名、身份证号、乘客类型等;
  4. 座位表:存储座位的信息,包括车次、座位类型、座位号等;
  5. 车票库存表:存储车票的库存信息,包括车次、座位类型、剩余票数等。

在用户购票过程中,用户需要先登录系统,系统根据用户的信息生成订单,并为每个乘客分配座位号。生成订单时需要同时更新车票库存表中的数据,以保证不会出现超卖的情况。当用户完成支付后,系统将订单状态更新为已支付,并且将座位状态更新为已占用。

购票计算优先级

在高铁售票系统中,当多个用户同时购买同一趟车次的车票时,系统的计算优先级一般是按照以下顺序来计算:

  1. 时间优先:按照用户提交购票请求的时间顺序进行计算,先提交的请求先计算。
  2. 座位类型优先:对于同一时间提交的购票请求,按照座位类型的优先级进行计算。比如,硬座和硬卧的价格相同,但是硬座的数量更多,那么系统会先计算硬座的票。
  3. 票价优先:对于同一时间提交的购票请求,按照票价高低进行计算。比如,如果某个用户选择的是商务座,那么系统会先计算商务座的票。

需要注意的是,以上的计算优先级只是一个基本原则,实际上在高铁售票系统中,还需要考虑很多其他因素,比如余票数量、已经锁定的座位数等等。因此,在实际的开发中,需要根据具体的业务需求来确定计算优先级。

如何防止超卖

为了避免超卖的情况发生,一般在高并发的情况下,可以采用分布式锁来控制资源的互斥访问。比如可以使用Redis实现分布式锁,对于同一个车次的抢票请求,每次只允许一个请求获取锁,并进行库存扣减和订单创建操作,其他请求需要等待或者直接返回抢票失败的结果。

至于抢票成功后是先扣减库存还是支付成功后扣减库存,一般来说是先扣减库存。因为在高并发的情况下,可能会出现多个用户同时抢到同一张车票的情况,如果先进行支付再扣减库存的话,可能会导致库存不足的情况出现,从而导致超卖的情况。因此,先进行库存扣减,然后再进行支付操作,这样可以避免库存不足的情况。

问题一:如果对于同一个车次的抢票请求,每次只允许一个请求获取锁,锁的粒度是否太大了,毕竟同一车次可能有途径几十个站点,如果以站点为锁粒度的话该如何保证防止超卖呢?

答:如果将锁的粒度降到站点级别,那么在同一车次的多个站点中可能会出现超卖的情况,因为在不同的站点中购买同一车次的车票时需要共享车票库存,如果不加限制的话,就有可能出现超卖的情况。为了避免这种情况,可以在系统中引入一个分布式锁,锁的粒度为整个车次,以保证在同一时刻只有一个请求可以获取到锁,从而避免超卖情况的发生。

对于抢票成功是先扣车票库存还是支付成功才扣减库存的问题,可以采用不同的策略,具体视业务需求而定。如果采用先扣库存再支付的方式,需要考虑如何处理超时未支付的订单对库存的影响。如果采用先支付再扣库存的方式,需要考虑如何处理支付失败的情况,以及如何处理未及时支付的订单对库存的影响。不同的策略都有其优缺点,需要根据业务场景和需求进行选择。

防止超卖解决方案

可以采用分布式缓存系统(如Redis)的原子性操作来替代分布式锁的实现。具体实现方式如下:

  1. 对于每个车次的库存信息,缓存在Redis中,并设置一个过期时间,保证在一定时间内库存信息不会被过期的缓存覆盖。
  2. 对于抢票请求,首先在Redis中获取对应车次的库存信息,然后对库存信息进行扣减,并将扣减后的库存信息更新回Redis中。
  3. 在更新Redis库存信息时,使用Redis提供的原子性操作(如INCRBY、DECRBY等),保证多个线程同时进行库存扣减操作时,不会发生竞态条件,从而避免超卖情况。

通过使用分布式缓存系统的原子性操作,可以大大提高系统的并发处理能力,从而提高接口吞吐量。

如果Redis缓存丢失了,大量请求都直接打到数据库可能会导致数据库被打挂,可以采取以下措施:

  1. 加入限流机制:通过限制每秒钟请求的最大数量,防止请求过载,导致数据库崩溃。可以采用开源限流框架如Guava RateLimiter、Sentinel等。
  2. 引入缓存穿透机制:如果缓存失效后,大量请求都直接查询数据库,可以在应用程序中引入缓存穿透机制,例如在缓存失效后,先查询缓存是否有该数据的标记,如果有,则直接返回空数据;如果没有,则从数据库中查询数据并更新到缓存中。这样即使缓存失效,也能够防止大量请求同时打到数据库。

(可以在查询数据并更新到缓存中时加上分布式锁的操作,以避免多个请求同时更新缓存,造成缓存多次重刷的问题。这个分布式锁可以使用类似于上面提到的分布式锁的实现方式,比如利用 Redis 实现分布式锁。

实现的方式类似于下面这样:

  1. 获取锁,如果获取锁失败则等待一段时间重试,直到获取锁成功。
  2. 查询缓存,如果缓存中存在数据则直接返回。
  3. 如果缓存中不存在数据,则查询数据库并更新到缓存中。
  4. 在更新缓存前再次判断缓存中是否已经存在了数据,如果存在则直接返回。
  5. 如果缓存中不存在数据,则将查询到的数据更新到缓存中。
  6. 释放锁。

这样,在获取锁的过程中可以避免多个请求同时更新缓存的问题,从而减少缓存多次重刷的风险。同时,通过在更新缓存前再次判断缓存中是否已经存在数据,也可以避免重复更新缓存的问题。)

  1. 增加数据库读写分离机制:可以将读请求和写请求分别路由到不同的数据库实例上,这样可以将读请求分摊到多个数据库实例上,减轻单个数据库实例的压力。可以使用MySQL自带的读写分离机制或者使用开源的中间件如MyCat等。
  2. 使用缓存预热机制:在系统启动时,可以将热点数据预先加载到缓存中,这样可以减少缓存失效后的查询请求。可以使用定时任务或者后台线程实现缓存预热机制。
  3. 数据库优化:如果数据库性能较差,可以考虑对数据库进行优化,例如使用索引、分表、分区等技术,减少数据库查询时的瓶颈。可以根据具体的业务需求和数据库情况,选择适合的优化方案。

抢票接口支持高并发访问,需要做好以下几点:

  1. 分布式架构设计:采用分布式架构设计,将抢票请求均匀地分散到不同的服务器上,以减轻单一服务器的压力。同时,通过负载均衡器来平衡各个服务器的负载,确保整个系统的稳定性和高可用性。
  2. 集群部署:将系统部署在多个服务器上,通过负载均衡器实现请求的负载均衡。同时,利用集群的高可用性,保证在某一台服务器出现问题时,其他服务器可以接替其工作,确保系统的稳定性和高可用性。
  3. 缓存技术:利用缓存技术,将热点数据放入缓存中,减少对数据库的访问,提高系统的性能和并发能力。常见的缓存技术包括Redis、Memcached等。
  4. 数据库优化:对数据库进行优化,包括合理的索引设计、SQL优化等,以提高数据库的读写性能和并发能力。
  5. 异步处理:通过将某些操作异步化,可以减轻系统的压力,提高系统的并发能力和响应速度。例如,将支付操作异步化,将抢票成功的通知异步化等。
  6. 限流和熔断:通过限流和熔断等手段,限制系统的并发量,保护系统免受过载和请求崩溃的风险。例如,对用户访问频率进行限制,限制每个用户每秒最多可以请求的次数;设置超时时间,防止请求卡住。
  7. 监控和日志:通过监控和日志系统,实时监控系统的状态和运行情况,及时发现和处理问题,以保障系统的稳定性和可用性。同时,对系统的性能和瓶颈进行分析,进行针对性优化,提高系统的性能和并发能力。

春运期间,高铁购票系统的稳定性和高可用性,可以考虑以下措施:

  1. 使用分布式缓存:将高频访问的数据缓存在分布式缓存中,减少对数据库的访问,提高系统的性能和响应速度。
  2. 使用分布式锁:在抢票场景中,使用分布式锁来保证同一时间只有一个请求可以操作一个资源,避免并发操作导致的数据错误。
  3. 使用分布式事务:在用户购票、支付、退票等操作中,需要使用分布式事务来保证数据的一致性和完整性,避免数据丢失或错误。
  4. 垂直扩展和水平扩展:根据实际情况可以采取垂直扩展和水平扩展的方式来提升系统的性能和扩展性。垂直扩展可以通过增加服务器的硬件资源来提升系统性能,如增加CPU、内存、硬盘等;水平扩展可以通过增加服务器节点来提升系统的扩展性,如增加应用服务器、缓存服务器、数据库服务器等。
  5. 自动化运维:使用自动化工具来管理和维护系统,如自动化部署、自动化监控、自动化报警等,可以提高系统的可靠性和稳定性。
  6. 备份和恢复:定期备份数据并进行数据恢复测试,确保系统出现故障时可以快速恢复数据和系统服务。同时也需要设计高可用的架构,例如通过多个数据中心实现异地备份,确保数据的安全性。
  7. 灰度发布:在系统更新或升级时,可以采用灰度发布的方式,逐步将新版本推送到生产环境,避免一次性更新导致的系统宕机或数据丢失。

综上所述,通过采用上述措施,可以保证高并发情况下系统的稳定性和高可用性,提升系统的性能和扩展性,提高用户的购票体验。

扫码_搜索联合传播样式-标准色版.png