跟着信息技术和互联网的开展,票务体系也在不断晋级,比方实现了移动付出、电子收据、实时数据剖析等先进功用。此外,许多票务体系还引入了人工智能和大数据技术,用于精准营销、个性化推荐和风险办理。

但是,票务体系也存在一些应战,如怎么维护用户隐私,怎么防止票务欺诈,以及怎么供给更好的用户体验等。因而,票务体系的开发和运营需求考虑到这些问题,并继续改善和晋级。

优秀设计背后的思考:票务系统的架构分析

项目简介:大麦网是中国的抢先在线票务渠道,供给多样化的活动票务,如音乐会、戏剧和体育赛事等。主要功用包括活动查找、在线购票、电子票务、实时座位挑选、退换票服务以及付出接口。其智能推荐体系能够依据用户兴趣推送相关活动,为用户供给方便、快捷的一站式购票体验。

类似的产品有:猫眼娱乐、永乐票务、bookmyshow.com、ticketmaster.com

难度级别:困难

1、什么是在线电影票预定体系

电影票预定体系为其客户供给在线购买影院座位的才能。电子票务体系答应客户阅读当前正在上映的电影,并在任何地方任何时候预定座位。

2、体系的需求和目标

咱们的票务预定服务应满意以下需求:

功用需求

  1. 咱们的票务预定服务应能列出其联盟影院所在的不同城市。
  2. 用户挑选城市后,服务应显现该特定城市现已上映的电影。
  3. 用户挑选电影后,服务应显现正在放映该电影的影院及其可用的放映时刻。
  4. 用户应能挑选在特定影院的一场放映并预定他们的票。
  5. 服务应能向用户展示影院大厅的座位布局。用户应能依据他们的喜好挑选多个座位。
  6. 用户应能从已预定的座位中区分出可用的座位。
  7. 用户应能在付款以完结预定之前,将座位保存五分钟。
  8. 假如有或许座位会变得可用,例如,当其他用户的保存到期时,用户应能等候。
  9. 等候的客户应以公正的、先到先得的方法服务。

非功用性需求

  • 体系需求具有高度并发性。在任何特定时刻点,都会有多个对同一座位的预定恳求。服务应能高雅且公正地处理这一状况。
  • 服务的核心是票务预定,也就意味着涉及到财务买卖。这意味着体系应具有安全性,并且数据库应遵守ACID(原子性、一致性、阻隔性、持久性)准则。

3、一些规划考虑

  • 为了简便,咱们假定咱们的服务不需求任何用户认证。
  • 体系将不处理部分票务订单。用户要么取得他们想要的一切票,要么一张也得不到。 体系有必要公正。
  • 为了阻止体系被滥用,咱们能够限制用户一次预定不超越十个座位。
  • 咱们能够假定在抢手/备受期待的电影上映时,流量会激增,座位会很快被预定完。
  • 体系应具有可扩展性和高可用性,以应对流量激增。

4、容量估量

流量估量:咱们假定咱们的服务每月有30亿次页面阅读,每月售出1000万张电影票。

存储估量:假定咱们有500个城市,均匀每个城市有10家影院。假如每个影院有2000个座位,均匀每天有两场放映。

咱们假定每个座位预定需求50字节(ID、NumberOfSeats、ShowID、MovieID、SeatNumbers、SeatStatus、Timestamp 等)存储在数据库中。咱们还需求存储关于电影和影院的信息;咱们假定它会需求50字节。所以,要存储一切城市的一切影院的一切放映的一切数据一天:

500个城市 * 10家影院 * 2000个座位 * 2场放映 * (50+50) 字节 = 2GB / 天

要存储五年的这些数据,咱们大约需求3.6TB。

5、体系API

咱们能够有SOAP或REST API来公开咱们服务的功用。以下或许是查找电影放映和预定座位的API的界说。

SearchMovies(api_dev_key, keyword, city, lat_long, radius, start_datetime, end_datetime, postal_code, includeSpellcheck, results_per_page, sorting_order)

参数: api_dev_key (string):注册账户的API开发者密钥。这将用于包括限制用户基于其分配的配额等在内的事情。 keyword (string):要查找的关键词。 city (string):用于挑选电影的城市。 lat_long (string):用于挑选的纬度和经度。 radius (number):咱们想要查找活动的区域的半径。 start_datetime (string):用开端日期时刻挑选电影。 end_datetime (string):用完毕日期时刻挑选电影。 postal_code (string):用邮政编码/邮编挑选电影。 includeSpellcheck (Enum: “yes” or “no”):是否在呼应中包括拼写查看建议。 results_per_page (number):每页回来的成果数。最大为30。 sorting_order (string):查找成果的排序次序。一些可答应的值:’name,asc’,’name,desc’,’date,asc’,’date,desc’,’distance,asc’,’name,date,asc’,’name,date,desc’,’date,name,asc’,’date,name,desc’。

回来:(JSON) 以下是电影及其放映的示例列表:

{
    "MovieID": 1,
    "ShowID": 1,
    "Title": "Cars 2",
    "Description": "About cars",
    "Duration": 120,
    "Genre": "Animation",
    "Language": "English",
    "ReleaseDate": "8th Oct. 2014",
    "Country": USA,
    "StartTime": "14:00",
    "EndTime": "16:00",
    "Seats": 
    [
    {  
        "Type": "Regular"
        "Price": 14.99
        "Status: "Almost Full"
    },
    {  
        "Type": "Premium"
        "Price": 24.99
        "Status: "Available"
    }
    ]
},
{
    "MovieID": 1,
    "ShowID": 2,
    "Title": "Cars 2",
    "Description": "About cars",
    "Duration": 120,
    "Genre": "Animation",
    "Language": "English",
    "ReleaseDate": "8th Oct. 2014",
    "Country": USA,
    "StartTime": "16:30",
    "EndTime": "18:30",
    "Seats": 
    [
        {  
        "Type": "Regular"
        "Price": 14.99
        "Status: "Full"
    },
        {  
        "Type": "Premium"
        "Price": 24.99
        "Status: "Almost Full"
    }
    ]
}
ReserveSeats(api_dev_key, session_id, movie_id, show_id, seats_to_reserve[])

参数

api_dev_key (string):与上面相同

session_id (string):用户的会话ID,用于盯梢此预定。一旦预定时刻到期,将运用此ID在服务器上删除用户的预定。

movie_id (string):预定的电影。

show_id (string):预定的放映。

seats_to_reserve (number):包括要预定的座位ID的数组。

回来:(JSON)

回来预定的状况,其间包括以下之一:1) “预定成功” 2) “预定失利 – 放映已满”,3) “预定失利 – 请重试,因为其他用户正在保存预定座位”。

6、数据库规划

以下是咱们即将存储的数据的一些调查:

  1. 每个城市能够有多个影院。
  2. 每个影院将有多个影厅。
  3. 每部电影将有多场放映,每场放映将有屡次预定。
  4. 一个用户能够有屡次预定。

优秀设计背后的思考:票务系统的架构分析

7、顶层规划

在顶层面上,咱们的web服务器将办理用户的会话,应用服务器将处理一切的票务办理,将数据存储在数据库中,以及与缓存服务器一起处理预定。

优秀设计背后的思考:票务系统的架构分析

8、组件规划

首要,咱们试着建立服务,假定它是由一个单一的服务器供给的。

票务预定流程:以下将是典型的票务预定流程:

  1. 用户查找一部电影。
  2. 用户挑选一部电影。
  3. 向用户显现该电影的可用场次。
  4. 用户挑选一场放映。
  5. 用户挑选要预定的座位数量。
  6. 假如需求的座位数可用,用户将看到一个剧院的地图以挑选座位。假如不是,用户将进入下面的“过程8”。
  7. 一旦用户挑选了座位,体系将尝试预定这些选定的座位。
  8. 假如无法预定座位,咱们有以下选项:
  • 放映已满;向用户显现错误音讯。
  • 用户想预定的座位现已没有了,但是还有其他座位可用,所以用户被带回到剧院地图页面以挑选不同的座位。
  • 没有可预定的座位,但一切座位都还没有被预定,因为有些座位被其他用户在预定池中保存并且还没有预定。用户将被带到一个等候页面,在那里他们能够等候直到需求的座位从预定池中开释。这个等候或许会导致以下选项:
  • 假如需求的座位数变得可用,用户将被带到剧院地图页面,他们能够挑选座位。
  • 在等候过程中,假如一切座位都被预定了,或许预定池中的座位数少于用户计划预定的座位数,用户将被显现错误音讯。
  • 用户撤销等候,回来到电影查找页面。
  • 最多,用户能够等候一个小时,之后用户的会话将过期,用户将被带回到电影查找页面。
  1. 假如成功预定了座位,用户有五分钟的时刻付出预定。付款后,预定标记为完结。假如用户不能在五分钟内付出,他们一切的预定座位都将被开释,以供其他用户运用。

优秀设计背后的思考:票务系统的架构分析

服务器怎么盯梢一切没有预定的活动预定?服务器又怎么盯梢一切等候的客户? 咱们需求两个守护服务,一个用来盯梢一切活动的预定并从体系中移除任何过期的预定;咱们称之为ActiveReservationService。另一个服务将盯梢一切等候的用户恳求,一旦需求的座位数变得可用,它将告知(等候时刻最长的)用户挑选座位;咱们称之为WaitingUserService。

A. ActiveReservationsService(活动预定服务)

咱们能够在内存中保存一个与Linked HashMap或TreeMap类似的数据结构来存储一场“表演”的一切预定,除了在数据库中保存一切数据。咱们需求一种Linked HashMap类型的数据结构,它答应咱们在预定完结时跳转到任何预定以移除它。此外,因为咱们将有与每个预定关联的到期时刻,HashMap的头部将一直指向最旧的预定记载,以便在到达超不时过期预定。

为了存储每场表演的每个预定,咱们能够有一个HashTable,其间’key’是’ShowID’,’value’是包括’BookingID’和创立’Timestamp’的Linked HashMap。

在数据库中,咱们将在’Booking’表中存储预定,到期时刻将在Timestamp列中。’Status’字段将有一个值为’Reserved (1)’的值,一旦预定完结,体系将更新’Status’为’Booked (2)’并从相关表演的Linked HashMap中删除预定记载。当预定过期时,咱们能够从Booking表中移除它,或许将其标记为’Expired (3)’,除此之外还要从内存中移除。

ActiveReservationsService也将与外部金融服务一起处理用户付出。每逢预定完结或预定过期时,WaitingUsersService都会收到一个信号,以便能够为任何等候的客户供给服务。

优秀设计背后的思考:票务系统的架构分析

B. WaitingUsersService(等候用户服务)

就像ActiveReservationsService一样,咱们能够将一个表演的一切等候用户存储在Linked HashMap或TreeMap的内存中。咱们需求一个类似于Linked HashMap的数据结构,以便咱们能够在用户撤销恳求时跳转到任何用户以从HashMap中移除他们。此外,因为咱们是以先到先得的方法服务,Linked HashMap的头部总是指向等候时刻最长的用户,因而每逢座位变得可用时,咱们都能够以公正的方法为用户供给服务。

咱们将有一个HashTable用来存储每个Show的一切等候用户。’key’将是’ShowID’,’value’将是包括’UserIDs’和他们的等候开端时刻的Linked HashMap。

客户端能够运用Long Polling来坚持自己的预定状况更新。每逢座位变得可用时,服务器能够运用这个恳求来告知用户。

预定过期

在服务器上,ActiveReservationsService盯梢活动预定的过期时刻(基于预定时刻)。因为客户端将显现一个计时器(用于过期时刻),这或许与服务器略微不同步,咱们能够在服务器上增加五秒钟的缓冲区以防止破碎的体验,从而保证客户端在服务器超时后永不超时,防止成功购买。

9、并发性

怎么处理并发性,以便没有两个用户能够预定同一座位。咱们能够在SQL数据库中运用业务来防止任何抵触。例如,假如咱们运用的是SQL服务器,咱们能够运用业务阻隔级别来锁定行,然后再更新它们。下面是样本代码:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRANSACTION;
    -- Suppose we intend to reserve three seats (IDs: 54, 55, 56) for ShowID=99 
    Select * From Show_Seat where ShowID=99 && ShowSeatID in (54, 55, 56) && Status=0 -- free 
    -- if the number of rows returned by the above statement is three, we can update to 
    -- return success otherwise return failure to the user.
    update Show_Seat ...
    update Booking ...
COMMIT TRANSACTION;

‘Serializable’ 是最高的阻隔级别,能够保证免受脏读、不可重复读和幻读的影响。这里要注意一点;在一个业务中,假如咱们读取了行,咱们会在这些行上加写锁,以防止它们被任何其他人更新。

一旦上述数据库业务成功,咱们就能够开端在ActiveReservationService中盯梢预定状况。

10、容错性

当ActiveReservationsService或WaitingUsersService溃散时会产生什么? 每逢ActiveReservationsService溃散时,咱们能够从‘Booking’表中读取一切的活动预定。请记住,直到预定完结,咱们都将“Status”列坚持为“Reserved (1)”。另一个挑选是拥有主-次装备,这样,当主服务溃散时,次服务能够接管。咱们没有将等候的用户存储在数据库中,所以,当WaitingUsersService溃散时,除非咱们有主次设置,否则咱们没有任何方法康复那些数据。

相同,咱们会为数据库设置主次装备,以使其具有容错性。

11、数据分区

数据库分区:假如咱们按‘MovieID’进行分区,那么一部电影的一切场次都会在同一个服务器上。关于抢手电影来说,这或许会给那台服务器带来很多负载。更好的方法是依据ShowID进行分区;这样,负载就能够分散到不同的服务器上。

ActiveReservationService和WaitingUserService分区:咱们的Web服务器将办理一切活动用户的会话,并处理与用户的一切通讯。咱们能够运用一致性哈希算法来依据‘ShowID’为ActiveReservationService和WaitingUserService分配应用服务器。这样,特定场次的一切预定和等候用户将由某一组服务器处理。假定为了负载平衡,咱们的”一致性哈希”为任何场次分配了三个服务器,那么每逢一个预定过期时,持有该预定的服务器将执行以下操作:

  1. 更新数据库以移除预定(或标记为过期)并更新‘Show_Seats’表中座位的状况。
  2. 从Linked HashMap中移除预定。
  3. 告知用户他们的预定已过期。
  4. 向一切持有该场次等候用户的WaitingUserService服务器播送音讯,以找出等候时刻最长的用户。一致性哈希计划将告知咱们哪些服务器持有这些用户。
  5. 假如所需的座位现已变为可用,就向持有最长等候用户的WaitingUserService服务器发送音讯以处理他们的恳求。

每逢一个预定成功时,将产生以下事情:

  1. 持有该预定的服务器向一切持有该场次等候用户的服务器发送音讯,以便这些服务器能够使一切需求的座位数多于可用座位数的等候用户过期。
  2. 收到上述音讯后,一切持有等候用户的服务器将查询数据库,以查找现在有多少个闲暇座位。此处的数据库缓存将大大有助于只运转一次这个查询。
  3. 使一切希望预定的座位数多于可用座位数的等候用户过期。为此,WaitingUserService有必要遍历一切等候用户的Linked HashMap。