秒杀问题

今天去面试,面试官问了一个秒杀问题。我当时脑子一片空白,纯靠条件反射说了一个傻逼的答案。 在回程的路上,我骑着电驴还在想着这个事情,思路突然就打开了·····我真傻,真的,真傻。 现在先把能想到的东西记录下。

问题描述

秒杀问题常见于电商平台到点抢购东西,也有其他场景,比如抢票占座等等。从单个请求看整个流程的话, 就是客户发起请求,之后服务端处理并持久化请求,这么一个简单过程。秒杀有个特点就是短时间内有大量的请求进来, 如果处理不好,会造成不好的后果。

整体而言,这是一个开放性问题。即使没有相关工作经验,也是有东西可以扯扯的。下面我就凭着感觉先扯一扯。

问题分析

在真正施工之前,我们作为开发,需要跟产品详细了解下需求,并做数据预估,之后才能制定方案。

整个问题的解法跟预期用户规模有很大关系。比如:

  • 预期有几百个人去秒杀,那很可能不需要做任何开发工作就可以抗住压力。当然,稳妥起见,还是要做一下压测,去了解一下当前系统能够承受的压力边界在哪里。
  • 预期有几千上万人去秒杀,那我们时是否可以变通一下业务流程?比如,把秒杀改成预约。改成预约之后,整个事情就被转化成了一个批处理问题。
  • 如果不能变通,或者预期有更多的人去秒杀,我们就只能硬着头皮上了。

从单个请求上来说,就是客户从客户端发起请求,服务端处理请求并持久化结果,这么一个过程。有一些环节,我们可以做一些工作来抗住压力,这些环节包括:

  1. 客户端发送请求到服务端的环节
  2. 网关层路由请求到指定服务器的环节
  3. 秒杀服务器处理请求的环节
  4. 查询数据和持久化数据的环节

现在我们要

硬着头皮上

首先,我们需要

明确一些底线要求

比如:

  1. 秒杀期间不能影响其他业务进行。比如:
    • 不能把常规的业务服务搞挂
    • 不能把过多的事务直接打到数据库,导致数据库被搞挂掉
  2. 秒杀不能把用户的钱搞没了,还是没地方查的那种。

我们需要协商,划定红线。如果没人协商,就自己调研一下有哪些红线。

了解当前系统的压力上限

动手之前,需要做几轮压测了解系统的瓶颈在什么地方。比如:

  1. 当前的数据库配置,可以支持多少 tps 而不影响其他业务进行
  2. 是否可以增加服务使用的 cpu、内存数量,增加服务实例的数量,提高 tps,能提高到什么程度

根据压测结果,我们发现只需要增加服务使用的机器配置,或者增加服务的数量,就可以抗住压力,那就简单地临时加机器就行。能用钱解决的事情,就用钱解决。 只靠简单的加机器不能够抗住压力,就像需要做额外的开发准备。

了解用户的操作路径

  1. 秒杀发生在那些页面?
  2. 这些页面有哪些操作?
  3. 特别是,用户秒杀失败后,可能会如何重试?

根据用户的操作路径,我们可以预测

  • 哪些接口会是热点接口
  • 哪些数据可以用缓存临时应付用户,以便在不损失用户体验的情况下,缓解系统压力
  • 哪些数据必须动态更新,有没有变通的方式来缓解读取动态数据导致的压力
  • 用户失败后会重试,是否可以在客户端引导用户增加重试的时间间隔来缓解系统压力

之后,就可以操刀了。

大致方案

客户端发送请求到服务端的环节

  1. 是否可以随机丢掉部分用户的请求,来缓解服务器的秒杀请求的压力。比如前 20 秒有 20% 的概率丢掉请求,之后不再丢请求。当然,具体比例最后由产品决定,或者后端研发计算后决定。
  2. 用户可能提前进入页面等待秒杀。前端是否可以缓存一些数据,以减少不太重要的资源带来的数据访问压力。
  3. 用户免不了重试。是否可以通过提供良好的用户体验,在用户重试时,适当拉长重试时间间隔,来缓解服务器压力,同时环节用户焦虑情绪。
  4. 是否可以要求用户在指定客户端秒杀,以增加秒杀过程的可控性。

网关层路由请求到指定服务器的环节

  1. 确保网关可以抗住压力。
  2. 网关层通过配置路由规则,将请求路由到指定的服务器。

秒杀服务器处理请求的环节

  1. 确定是否提供专门的服务器来处理秒杀。如果需要,就申请专用于秒杀的服务器。然后在网关层把秒杀请求路由到秒杀服务器。以确保秒杀服务器被打挂之后不影响其他业务。

  2. 确定是否要随机丢掉部分的请求,因为丢掉部分请求后,客户会重试。秒杀,就总有人抢不到。所以可以尝试丢掉一定比例的请求。最好还是前端来做,前端做的话,甚至能缓解网关层压力。

  3. 确定是把请求送入队列,还是使用令牌桶来限制 tps。

    • 如果选择把请求送入队列,好处是先到先得,坏处是排队时间可能较长,用户提交请求后需要等待,用户可能会频繁刷新查询结果的接口。
    • 如果选择使用令牌桶,好处用户一旦秒杀成功,就可以很快得到结果,坏处是没有秒杀成功的用户会不断重试。

    具体方案需要根据预期用户数量,库存数量来决定。比如,我们有 1 万件库存,系统可以承受 1000 tps,那么使用队列就足够,用户最多只需要等 10 秒钟就可以知道结果。 如果有 10 万件库存,请求进入队列后可能需要等好几分钟,这个时候就需要做权衡了。令牌桶的话,建议使用 in-memory 令牌桶,而非分布式令牌桶。 每个服务实例中的令牌桶发放令牌的速度,可以根据预先分配的服务实例数来决定。

查询数据和持久化数据的环节

  1. 确定是否需要单独的数据库用来应付秒杀。如果需要,就申请专用于秒杀的数据库实例。以确保数据库被打挂之后不影响其他业务。
  2. 是否需要用缓存来大致计算库存,以避免这种统计性质的查询打到数据库上。
  3. 如何避免超售问题,或者说是否允许一定的超售。可能需要设计适当的存储结构和锁策略。比如,每个用户一次只能秒杀一件,和一次可以秒杀多件,是两种不同复杂度的问题。

另外

  • 系统设计的时候,如果可以,就尽量拉着同事多来几轮评审。
  • 一定要压测,验证系统真的可以抗住预期的压力。