zoukankan      html  css  js  c++  java
  • 秒杀系统设计与实现

    问题描述

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

    过去都说台上十分钟,台下十年功,而秒杀系统更有意思,瞬时的流量峰值可能就三两分钟,但你却必须为此做大量的准备工作。容量评估是否做好了,带宽是否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

  • 相关阅读:
    《Programming WPF》翻译 第8章 1.动画基础
    一些被遗忘的设计模式
    《Programming WPF》翻译 第4章 数据绑定
    《Programming WPF》翻译 第3章 控件
    《Programming WPF》翻译 第5章 样式和控件模板
    《Programming WPF》翻译 第7章 绘图
    《Programming WPF》翻译 第9章 自定义控件
    《Programming WPF》翻译 第7章 绘图 (2)
    《Programming WPF》翻译 第8章 前言
    关于Debug和Release之本质区别
  • 原文地址:https://www.cnblogs.com/WuYiStudio/p/11012648.html
Copyright © 2011-2022 走看看