设计Ticket Master (附解答)

专栏 | 九章算法
网址 | www.jiuzhang.com程序员

学员面经题web

一个相似ticket master的网站。说某个时间段开放某明星演唱会订票,大概会同时有500K QPS 的访问量,一共只有50K张票。订票的过程是用户打开订票网页(不用考虑认证等问题),填一个text box说要订几张票,而后click一个button就打开一个page,那个page会不停的spin直到系统可以预留那几张票,若是预留成功,用户会有几分钟时间填写用户信息已经完成支付,若是到期未支付,这些票就自动被系统收回了。每张票都是同样的,没有位置信息什么的。
求教怎么design这个系统,我一开始看到500K QPS就有点慌乱了。面试

九章讲师解答 SOLUTIONSredis

订票网站一直都是世界难题。12306应该比这个还恐怖。
不要被 500k QPS吓到。500k也好,5k也好,500也好,分析的方法和思路都是同样的。算法

这个题的关键首先是给出一个可行解(不管任何系统设计题的关键都是如此),一个核心要实现的功能是,票的预留与回收。在设计可行解的时候,能够先将500k qps抛之脑后。假设如今只有10个用户来买票。优化的事情,放到Evolve的那一步。数据库

按照咱们的SNAKE分析法来:服务器

Scenario 设计些啥:微信

  • 用户提交订票请求
  • 客户端等待订票
  • 预留票,用户完成支付
  • 票过时,回收票
  • 限制一场演唱会的票数。

Needs 设计得多牛?并发

  • 500k QPS,面试官已经给出
  • 响应时间——是在用户点击的一瞬间就要完成预约么?不是,可让用户等个几分钟。也就是说,500K的请求,能够在若干分钟内完成就行了。所以所谓的 500k QPS,并非Average QPS,只是说峰值是 500k QPS。而你要作的事情并非在1秒以内完成500k的预约,而是把确认你收到了购票申请就行了。

Application 应用与服务分布式

  • ReservationService —— 用户提交一个预约请求,查询本身的预约状态
  • TicketService —— 系统帮一个预约完成预约,生成具体的票

Kilobyte 数据如何存储与访问

1.ReservationService ——用户提交了一个订票申请以后,把一条预约的数据写到数据库里。因此须要一个Reservation的table。大概包含的columns有:

id(primary_key)
created_at(timestamp)
concert_id(foreign key)
user_id(foreign key)
tickets_count(int)
status(int)

简单的说就是谁在什么时刻预约了哪一个演唱会,预约了几张,当前预约状态是什么(等待,成功,失败)。

2. TicketService —— 系统从数据库中按照顺序选出预约,完成预约,预约成功的,生成对应的Ticket。表结构以下:

id (primary key)
created_at (timestamp)
user_id (fk)
concert_id (fk)
reservation_id (fk)
status (int) // 是否退票之类的

另外,咱们固然还须要一个Concert的table,主要记录总共有多少票:

id (primary key)
title (string)
description (text)
start_at (timestamp)
tickets_amount (int)
remain_tickets_amount (int)
...

总结一下具体的一个Work Solution 的流程以下:

  1. 用户提交一个预约,ReservationService 收到预约,存在数据库里,status=pending
  2. 用户提交预约以后,跳转到一个等待订票结果的界面,该界面每隔5-10秒钟像服务器发送一个请求查询当前的预约状态
  3. TicketService是一个单独执行的程序,你能够认为是一个死循环,不断检查数据库里是否有pending状态的票,取出一批票,好比1k张,而后顺利处理,建立对应的Tickets,修改对应的Reservation的status。

Evolve

分析一下上述的每一个操做在500k qps的状况下会发生什么,以及该如何解决。

1. 用户提交一个预约,ReservationService 收到预约,存在数据库里,status=pending

也就是说,在一秒钟以内,咱们要同时处理500k的预约请求,首先web server一台确定搞不定,须要增长到大概500台,每台web server一秒钟同时处理1k的请求仍是能够的。数据库若是只有一台的话,也很难承受这样大的请求。而且SQL和NoSQL这种数据库处理这个问题也会很是吃力。能够选用Redis这种既是内存级访问速度,又能够作持久化的key-value数据库。而且Redis自带一个队列的功能,很是适合咱们订票的这个模型。Redis的存取效率大概是每秒钟几十k,那么也就是咱们要大概20台Redis应该就能够了。咱们能够按照 user_id 做为 shard key,分配到各个redis上。

2. 用户提交预约以后,跳转到一个等待订票结果的界面,该界面每隔5-10秒钟像服务器发送一个请求查询当前的预约状态

使用了redis的队列以后,如何查询一个预约信息是否在队列里呢?方法是reservation的基本信息除了放到队列里,还须要同时继续存一份在redis里。队列里能够只放reservation_id。此时reservation_id能够用user_id+concert_id+timestamp来表示。

3. TicketService是一个单独执行的程序,你能够认为是一个死循环,不断检查数据库里是否有pending状态的票,取出一批票,好比1k张,而后顺利处理,建立对应的Tickets,修改对应的Reservation的status。

为每一个Redis的数据库后面添加一个TicketService的程序(在某台机器上跑着),每一个TicketService负责一个Redis数据库。该程序每次从Redis的队列中读出最多1k的数据,而后计算一下有须要多少张票,好比2k,而后访问Concert的数据库。问Concert要2k的票,若是还剩有那么多,那么就remain_tickets_amount - 2k,若是不够的话,就返回还有多少张票,并把remain_tickets_acount 清零。这个过程要对数据库进行加锁,能够用数据库本身带的锁,也能够用zookeeper之类的分布式锁。由于如今是1k为一组进行处理,因此这个过程不会很慢,存Concert的数据库也不须要不少,一台就够了。由于就算是500k的话,分红500组,也就是500个queries峰值,数据库处理起来绰绰有余额。
假如获得了2k张票的额度以后,就顺序处理这1k个reservation,而后对每一个reservation生成对应的tickets,并在redis中标记reservation的状态,这里的话,tickets的table大概就会产生2k条的insert,因此tickets的数据库须要大概可以承受 20 x 1k = 20k 的并发写。这个的话,大概 20 台 SQL数据库也就搞定了。

从头理一下

开放订票,500k的请求从世界各地涌来
经过 Load Balancer 纷发给500台 Web Server 。每台Web Server大概一秒钟处理1k的请求
Web Server 将1k的请求,按照 user_id 进行 shard,丢给对应的 redis 服务器里的队列,并把 Reservation 信息也丢给 Redis存储。
此时,20台 Redis,每台 Redis 约收到 25k 的 排队订票记录
每台 Redis 背后对应一个 TicketService 的程序,不断的查看 Redis 里的队列是否有订票记录,若是有的话,一次拿出1k个订票记录进行处理,问Concert 要额度,而后把1k的reservation对应的建立出2k左右的tickets出来(假如一个reservation有2张票平均)。假如这个部分的处理能力是1k/s的话,那么这个过程完成须要25秒。也就是说,对于用户来讲,最慢大概25秒以后,就知道本身有没有订上票了,平均等待时间应该低于10秒,由于当concert的票卖完了的时候,就无需生成1-2k条新的tickets,那么这个时候速度会快不少。
存储tickets的数据库须要多台,由于须要处理的请求大概是20k的qps,因此大概20台左右的Ticket数据库。

超时的票回收

增长一个RecycleService。这个RecycleService 不断访问 Tickets 的数据库,看看有没有超时的票,若是超时了,那么就回收,而且去Concert的数据库里把remain_tickets_acount 增长。

总结如何攻破 500k QPS的核心点

核心点就是,500k QPS 我只要作到收,不须要作处处理,那么500台web服务器+20台Redis就能够了。
处理的的时候,分红1k一组进行处理,让用户多等个几秒钟,问题不大。用户等10秒钟的话,咱们须要的服务器数目就下降10-20倍,这是个tradeoff,须要好好权衡的。

一些可能的疑惑和能够继续进化的地方

问:500台Web服务器不少,并且除了订票的那几秒种,大部分的时候都是闲置浪费的,怎么办?
答:用AWS的弹性计算服务,为每场演唱会的火爆指数进行评估,而后预先开好机器,用完以后就能够销毁掉。

问:为何不直接用Redis也来存储全部的数据信息?
答:由于是针对通同一个Concert的预约,你们须要访问同一条数据(remain_tickets_acount),shard是无论用的,Redis也承受不住500k QPS 对同一条数据进行读写,而且还要加锁之类的保证一致性。因此这个对 remain_tickets_acount 的值进行修改,建立对应的 tickets 的过程,是不能在用户请求的时候,实时完成的,须要延迟进行。

问:redis又用来作队列,又用来作Reservation 表的存储,是否有点乱?
答:是的,因此一个更好的办法是,只把redis当作队列来用 和 Reservation 信息的Cache来用。当一个Reservation 被处理的时候,再到SQL数据库里生成对应的持久化记录。这样的好处是,Redis 这种结构其实不是很擅长作持久化数据的存储,咱们通常都仍是拿来当队列和cache用得比较多。



欢迎关注个人微信公众号:九章算法(ninechapter)。
精英程序员交流社区,按期发布面试题、面试技巧、求职信息等

九章算法,IT教育领域的深耕者
相关文章
相关标签/搜索