秒杀系统设计与实现

问题描述

如何设计并实现一个秒杀/抢购系统

过去都说台上十分钟,台下十年功,而秒杀系统更有意思,瞬时的流量峰值可能就三两分钟,但你却必须为此做大量的准备工作。容量评估是否做好了,带宽是否ready,前后端截流是否完备,是否需要队列化请求等等。

设计难点

瞬时峰值

瞬时峰值会挑战服务器带宽

秒杀的一瞬间,带宽可能是平常时的几倍几十倍,一瞬间带宽可能就跑满了。

瞬时峰值会挑战应用服务器资源

几十倍的流量,如果后端架构没有足额的设计。会在极短的时间内雪崩,秒杀类的业务,活动结束的时候流量又会断崖式的下跌,没有前期良好的设计,几乎不可能在出现峰值的短短三两分钟里给出有效的应急方案。另外,如果服务间没有良好的隔离,也会影响其他业务服务的运作。

瞬时峰值会挑战DB负荷能力

如果大量的请求落到DB,海量的请求,读写一份库存数据,读写冲突,会出现大量的锁与等待,接下来就是龟速响应跟崩溃了。

思路

越早拦截,成本越低,吞吐量越大

简单抽象,常见的应用架构是这样的

接口应用层(APP/浏览器等)-> 服务层 -> 存储层

核心思路是要把请求到达存储层之前尽可能多地拦截掉,越多的请求走到后面,架构与硬件成本就越高。搭建百万并发又支持事务的DB集群,可比一个百万级并发读的静态HTTP服务集群难多了。

如何拦截

接口应用层(APP/浏览器等客户端)

按钮置灰,防止重复点击

//####
// 防止一个页面中重复点击
//####
if button_is_clicked
    return
    
button_is_clicked = yes
post_data()

//####
// 校验是否5秒内点击过,
// 防止多个页面重复点击
//####
if clicked_within_5_seconds
    return

//跨页面存储在本地,如cookie
clicked_within_5_seconds = yes
post_data()

错峰提交

抢购开始后,为避免大量流量极短时间内涌入服务端,可以在客户端要求用户执行一些操作后才能点击购按钮,分流压力。譬如,

  1. 计算一个简单的数学题
  2. 输入验证码
  3. 输入一串中文
  4. 回答一个调皮的问题

异步显示抢购结果

抢购与抢购结果的显示,产品上应避免设计进一个同步流程里,这样一方面可以为服务端赢得喘息的机会,也可以第一时间响应信息给用户,提高体验。

同步流程:

点击抢购 -> 同步等待服务端结果 -> 显示结果


异步流程:

点击抢购 -> 服务端响应201 -> 显示抢购中的页面 

-> 【若干秒】后异步拉取抢购结果 -> 显示结果
此处的【若干秒】也有很多想象空间。

譬如说,50%的用户是5秒后到服务端拉取结果,

50%的用户是10秒后。这样也同样实现了错峰。

服务层

基于user_id去重,防止刷量

前端保护是非常重要的一环,可以有效拦截普通用户,但也是最不可控的一环,有许多可以绕过的方法。需要服务端根据一个唯一标识再进行一次单用户去重拦截。伪代码与客户端类似

//####
// 校验该用户是否5秒内点击过
//####
if user_clicked_within_5_seconds
    return

限制并发连接数

以IP为条件限制并发连接数,会出现一定的误杀概率。一般会冗余一定的量,在误杀与有效拦截间取一个平衡。

使用MQ,拦截到DB的量

  1. 维护一个请求计数,只通过比实际库存量稍大的请求到MQ里,其余请求响应已抢完

  2. 使用若干个worker更新库存抢购


//请求数小于库存量130%
if mq_count < quantity * 1.3
    push_to_mq()
    mq_count++
    return 201

response('抢光啦')

//worker更新库存
while mq_has_data
    //乐观并发锁
    update_quantity_where_version_is_1()

缓存的使用

扛多读少写的并发,缓存的合理使用尤为关键。

1,客户端缓存:静态资源放到CDN里,回源请求要尽可能少。

2,服务端缓存

(1)热点读取的数据,需要预热好放到redis/memcache,甚至本地缓存中

(2)超量的请求响应,避免在运行时拼装,可以建立一套网关机制,直接响应本地缓存。(如:Nginx-Lua)

常见问题

如何点亮抢购

1,客户端计时,时间到之前置灰按钮

优点:实现简单

缺点:客户限制很容易被绕过

2,服务端维护一个计时服务器,当计时完成,推送结果到各个服务器,秒杀开始。

优点:实现简单,worker只需要监听推送结果即可。

缺点:计时服务器有单点问题,且推送到各个服务器时间上有先后,容易出现有些请求落下来抢购开始,有些没有开始。瞬间压力会撑爆抢购开始的机子上。

3,Redis中存储一个ttl为抢购开始时间的key,各个服务器通过校验key是否过期,来判别活动开始。

推荐使用:一个高可用的Redis集群,能轻松扛过10万+的并发读

如何托底

最外层的LB,如果是基于硬件的。需要有一个崩溃阈值,一旦超量,要么直接抛弃连接。要么路由到一个CDN里的文件,提示“抢光啦”等。

超卖问题

加锁,通过前面的拦截,DB层的量已经所剩无几。果断加乐观并发锁。

带宽/服务器扩容

活动前需要进行容量评估,秒杀系统的部署也需要独立于其他的应用服务器。类似阿里云/腾讯云的按量付费服务器是个不错的选择,活动结束后再把数据同步回自己的服务器。

肉鸡问题

这是最让人头疼的问题,职业羊毛一般有大量的账号。往往从各个维度上看,都是正常的用户,防不胜防。但依然有一些方式可以防范

1,IP风险评估

2,实名认证

3,根据账号过往的交易进行风险评估,过滤高风险的账户

4,如果依赖于第三方平台,可以使用他们系统本身的风控功能,比如:腾讯的天御 https://cloud.tencent.com/document/api/295/1774

猜你喜欢

转载自www.cnblogs.com/WuYiStudio/p/11012648.html