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

  • 相关阅读:
    lua的数组下标是从1开始的
    DestroyImmediate的一些坑
    c#的IDisposable
    unity工具开发(转)
    winform控件命名规范对照表
    C#调用Exe程序示例
    System.Diagnostics.Process.Start的妙用
    C#中AppDomain.CurrentDomain.BaseDirectory及各种路径获取方法
    C# WindowsAPI
    TabPage判断重复添加Page
  • 原文地址:https://www.cnblogs.com/WuYiStudio/p/11012648.html
Copyright © 2011-2022 走看看