02如何设计一个秒杀系统???

设计一个秒杀系统

3.3发现热点数据

如何发现这些秒杀商品,或者更准确的说,如何发现热点商品?

参加秒杀的商品就是秒杀商品,关键是系统怎么知道那些商品参加了秒杀活动呢,所以需要提前来区分普通商品和热点商品。

静态热点数据可以通过商业手段,让卖家通过报名的方式提前筛选出来,实现方式通过一个运营系统,把参加活动的商品数据进行打标,预处理提前缓存。这种方式会增加卖家使用成本,而且实时性较差,不太灵活。

另外一种方式,可以通过技术手段提前预测,例如对买家每天访问的商品进行大数据计算,然后统计处TOP N的商品,我们可以认为这些TOP N的商品就是热点商品,发现动态数据。

无论那种方式,实时性较差,如何设计出系统能在秒级内自然发现热点商品?(动态热点发现)

1、构建一个异步系统,他可以收集交易链路上,各个环节中的中间件产品的热点key,如Nginx。缓存。RPC服务框架等这些中间件(一些中间件产品本身已经有热点统计模块)

2、建立一个热点上报和可以按照要求订阅的热点服务的下发规范,主要目的是通过交易链路上各个系统(包括详情,购物车,交易,优惠,库存,物流等)访问的时间差,把上游已经发现的热点透传给下游系统,提前做好保护。比如,对于大促高峰期,详情系统是最早知道的,在统一接入层上Nginx模块统计的热点URL.

3、将上游系统收集的热点数据发送到热点服务台,然后下游系统(如交易系统)就会知道那些商品会被频繁调用,做热点保护。

用户访问商品时,经过的路径很多,我们主要是依赖前面的导购页面(包括首页,搜索页面,商品详情,购物车等)提前识别那些商品的访问量高。通过这些系统中间件来手机热点数据,并记录到日志中。

上图动态热点发现系统

4、热点服务后台抓取热点数据日志最好异步方式,因此“异步”一方面便于保障通用性,另一方面又不影响业务系统和中间件产品的主流程。

5、热点服务发现和中间自身的热点保护模块并存,每个中间件和应用还需要保护自己,热点服务台提供热点数据的收集和订阅服务,便于各个系统热点数据透明出来。

6、热点发现要做到接近实时(3s内完成热点数据的发现),因此只有做到接近实时,动态发现才有意义,才能实时地对下游系统提供保护。

3.3.1处理了热点数据

处理热点数据通常有几种思路:

一、优化        二、限制        三、隔离

优化热点数据最有效的办法就是缓存热点数据,如果热点数据做了动静分离,那么可以长期缓存静态数据。但是,缓存热点数据更多的是“临时”缓存,即不管静态数据还是动态数据,都用一个队列短暂缓存数秒钟,由于队列长度有限,采用LRU淘汰算法替换。

限制:更多的是一种保护机制,方法很多,例如:对呗访问商品的ID做一致性Hash,然后根据Hash做分桶,每个分桶设置一个处理队列,这样可以把热点商品限制在一个请求队列里,防止因为某些商品占用太多服务器资源,而使其他请求始终得不到服务器处理资源。

隔离:秒杀系统设计的第一个原则就是将这种热点数据隔离出来,不要让1%的请求影响到99%,隔离出来后方便对着1%的请求做针对性的优化,具体到“秒杀”业务,分为几个层次的隔离:

1.业务隔离:把秒杀做成一种营销活动,卖家要参加秒杀需要单独报名,从技术上说,卖家报名对我们来说就有了已知热点,因此可以提前预热。

2.系统隔离:运行时的隔离,通过分组部署的方式和另外99%分开,秒杀可以申请单独的域名,目的时i让请求落到不同的集群中。

3.数据隔离:秒杀所调用的数据大部分都是热点数据,比如会启用但粗的Cache集群或者Mysql数据库来访热点数据,目的是不想0.01%的数据有机会影响99.99%数据。        

3.4流量削峰应该怎么做

秒杀系统监控流量图,你会发现是一条直线,就在秒杀的那一刻就一直是一条直线,因为秒杀请求在时间上高度集中于某一特定时间点,就会导致一个特别高的流量峰值,对资源的消耗是瞬时的。

但是对于秒杀的场景来说,最终能抢到商品的人数是固定的,也就是说100人和1000人发起请求的结果都是一样的,并发越高,无效请求也就越多。

但是从业务上来说,秒杀活动是希望更多的人来参与的,也就是开始之前希望有更多的人来刷网页,但是真正的开始下单时,秒杀请求并不是越多越好,因此可以设计一些规则,让并发请求更多的延缓,甚至可以过滤掉一些无效请求。

3.4.1为什么要削峰

峰值会带来哪些坏处?

服务器的处理资源是恒定的,你用或者不用,处理能力一样,出现峰值的话,很容易导致忙不过来,闲的时候却又没什么要处理。由于要保证服务质量,我们很多处理资源只能按照忙的时候来预估,从而导致资源的浪费。

好比因为存在早高峰和晚高峰,所以有了错峰出行的方案。削峰的存在,一是可以让服务端处理变得更加平稳,二是可以节省服务器资源成本,减少和过滤无效请求,他遵从“请求数要尽量少”原则

流量削峰的思路:排队,答题,分层过滤。几种方式都是无损(即不会损坏用户发出的请求)的实现方案,当然还有些有损的方案:限流,机器负载等强措施,达到削峰保护的目的。

3.4.1排队

要对流量进行削峰,最容易想到的解决方案就是用消息队列来缓冲瞬时流量,把同步的直接调用转换为异步间接推送,中间通过一个队列在一段承接瞬时的流量=洪峰,在另一端平滑将消息已送过去,在这里,消息队列就像“水库”一样,拦蓄上游的洪水,削减进入下游河道的洪峰流量,从而达到见面洪水灾害目的。

消息队列缓冲瞬时流量方案:

但是,如果流量峰值持续一段时间达到了消息队列的处理上限,消息队列同样也会被压垮。

类似的排队方式还有很多:

1.利用线程池加锁等待的排队方式

2.先进先出,先进后出等常用的内存排队算法的实现方式

3.把请求序列化到文件中,然后在顺序的读取文件(如基于MYSQL binlog的同步机制)来恢复请求方式

这些方式都有一个共同的特征:就是把“一步操作”变成“两步操作”,增加的一步操作起到缓冲的作用

3.4.2答题

早起秒杀只是纯粹的刷新页面和点击购买按钮,后来才增加了答题功能,那么为什么增加答题功能呢?主要是为了增加购买的复杂度,从未达到两个目的。

第一个:防止部分卖家使用秒杀器在参加秒杀时作弊。

第二个:延缓请求,起到流量削峰的作用。这个重要的功能就是把峰值的下单请求分片拉长,从以前的1s之内延长到2s~10s。这样一来,请求峰值基于时间分片了,这个时间的分片对于服务器处理并打非常重要,会大大减轻压力。由于请求具有先后顺序,靠后的请求到来时自然也就没有库存了,因此到不了最后的下单步骤。这种设计思路很普遍,例如支付宝的“咻一咻”、微信的“摇一摇”

都是类似方式。

秒杀答题的设计思路:

整个答题逻辑主要分为3个部分:

1.题库生成模块

这个部分主要就是生成一个个问题和答案,其实题目和答案本身并不需要很复杂,重要的是能够防止由机器来算出结果,即防止秒杀器来答题。

2.题库的推送模块

用于秒杀答题前,把题目提前推送给详情系统和交易系统,题目的推送主要是为了保证每次用户请求的题目的唯一的,目的也是防止答题作弊。

3.题目的图片生成模块

用于把题目生成为图片格式,并且在图片里增加一些干扰因素,这也同样是为了防止机器直接答题,它要求只有忍才能理解题目本身的含义,需要注意,由于答题时网络比较拥挤,我们应该把题目的图片提前推送到CDN上,并进行预热,不然的话当用户真正请求题目时,图片可能加载较慢,影响答题。

答题的逻辑很好理解:当用户提交的答案和题目对应的答案比较时,通过了就继续下一步下单逻辑,否则失败,我们可以把问题和答案用下面这样的key来进行MD5加密:

问题key:userID+itenID+question_ID+time+PK

答案key:userID+itemID+answer+PK

逻辑图:

除了验证问题答案外,还包括用户本身身份的验证,例如:是否已经登录,用户的cookie是否完成,用户是否重复频繁提交等,

除了正确性验证,我们还可以对提交答案时间做限制,例如:从开始答题到接受答案要超过1s,因因为小于1s是人为操作的可能性很小,这样可以防止机器答题。

3.4.3分层过滤

排队和答题是少发请求和请求缓冲,而针对秒杀场景还有一个方法,就是对请求进行分层过滤,从而过滤掉一些无效请求,分层过滤其实就是采用“漏斗”式设计,来处理请求。

请求分别经过CDN、前台读系统(商品详情系统)、后台系统(交易系统)和数据库几层:

大部分数据和流量在用户浏览器或者CDN上获取,这一层可以拦截大部分数据的读取。

经过第二层(前台系统)时数据(包括强一致性的数据)尽量得走Cache,过滤一些无效请求。

再到第三层后台系统,主要做数据的二次校验,对系统做好保护和限流,这样数据量和请求就进一步减少。

最后在数据层完成数据的强一致性校验。

这样像漏斗一样,尽量把数据量和请求量一层一层的过滤和减少了。

分层过滤的核心思想是:在不同的层次尽可能过滤掉无效请求,让“漏斗”最末端的才是有效请求。

而要达到这种效果,我们必须对数据做分层校验。

分层校验基本原则:

1.将动态请求的读取和缓存(Cache)在Web端,过滤掉无效的数据读;

2.对读数据不做强一致校验,减少因为一致性校验产生瓶颈的问题。

3.对写数据进行基于时间的合理分片,过滤掉过期失效请求。

4.对写请求做限流保护,将超出熊承载能力的请求过滤掉。

5.对写数据进行强一致性校验,只保留最后有效的数据。

3.5影响性能的因素有那些?又能如何提高系统性能?

设备像CPU、磁盘IOPS即每秒进行读写操作的次数

今天讨论的是系统服务端性能,QPS(Query Per Second,每秒请求数)来衡量,还有响应时间,可以理解为服务器处理响应的耗时。

正常情况下响应时间(RT)越短,一秒钟处理的请求数(QPS)自然也就会越多,这在单线程处理情况下看起来是线性关系,即我们只要把每个请求的响应时间讲到最低,那么性能就会最高。

如果通过多线程,来处理请求,理论上就会变成:总QPS=(1000ms/响应时间)*线程数量。

这样影响性能的一个是响应服务耗时,一个是处理请求线程数。

响应时间和 QPS 有啥关系:

 对于大部分的 Web 系统而言,响应时间一般都是由 CPU 执行时间和线程等待时间(比 如 RPC、IO 等待、Sleep、Wait 等)组成,即服务器在处理一个请求时,一部分是 CPU 本身在做运算,还有一部分是在各种等待。 理解了服务器处理请求的逻辑,估计你会说为什么我们不去减少这种等待时间。很遗憾, 根据我们实际的测试发现,减少线程等待时间对提升性能的影响没有我们想象得那么 大,它并不是线性的提升关系,这点在很多代理服务器(Proxy)上可以做验证。 如果代理服务器本身没有 CPU 消耗,我们在每次给代理服务器代理的请求加个延时, 即增加响应时间,但是这对代理服务器本身的吞吐量并没有多大的影响,因为代理服务 器本身的资源并没有被消耗,可以通过增加代理服务器的处理线程数,来弥补响应时间 对代理服务器的 QPS 的影响。 其实,真正对性能有影响的是 CPU 的执行时间。这也很好理解,因为 CPU 的执行真正 消耗了服务器的资源。经过实际的测试,如果减少 CPU 一半的执行时间,就可以增加 一倍的 QPS。 也就是说,我们应该致力于减少 CPU 的执行时间。

线程数对 QPS 的影响。

单看“总 QPS”的计算公式,你会觉得线程数越多 QPS 也就会越高,但这会一直正确 吗?显然不是,线程数不是越多越好,因为线程本身也消耗资源,也受到其他因素的制 约。例如,线程越多系统的线程切换成本就会越高,而且每个线程也都会耗费一定内存。 那么,设置什么样的线程数最合理呢?其实很多多线程的场景都有一个默认配置,即 “线程数 = 2 * CPU 核数 + 1”。除去这个配置,还有一个根据最佳实践得出来的公 式: 线程数 = [(线程等待时间 + 线程 CPU 时间) / 线程 CPU 时间] × CPU 数量 当然,最好的办法是通过性能测试来发现最佳的线程数。 换句话说,要提升性能我们就要减少 CPU 的执行时间,另外就是要设置一个合理的并 发线程数,通过这两方面来显著提升服务器的性能。

3.6如何优化系统

对 Java 系统来说,可以优化的地方很多,这里我重点说一下比较有效的几种手段,

它们是:减少编码、减少序列化、Java 极致优化、并发读优化。接下来,我 们分别来看一下。

1. 减少编码 Java 的编码运行比较慢,这是 Java 的一大硬伤。在很多场景下,只要涉及字符串的操 作(如输入输出操作、I/O 操作)都比较耗 CPU 资源,不管它是磁盘 I/O 还是网络 I/O, 因为都需要将字符转换成字节,而这个转换必须编码。

每个字符的编码都需要查表,而这种查表的操作非常耗资源,所以减少字符到字节或者 相反的转换、减少字符编码会非常有成效。减少编码就可以大大提升性能。

2. 减少序列化 序列化也是 Java 性能的一大天敌,减少 Java 中的序列化操作也能大大提升性能。又因 为序列化往往是和编码同时发生的,所以减少序列化也就减少了编码。

序列化大部分是在 RPC 中发生的,因此避免或者减少 RPC 就可以减少序列化,当然当 前的序列化协议也已经做了很多优化来提升性能。有一种新的方案,就是可以将多个关 联性比较强的应用进行“合并部署”,而减少不同应用之间的 RPC 也可以减少序列化 的消耗。 所谓“合并部署”,就是把两个原本在不同机器上的不同应用合并部署到一台机器上, 当然不仅仅是部署在一台机器上,还要在同一个 Tomcat 容器中,且不能走本机的 Socket,这样才能避免序列化的产生。

3. Java 极致优化

Java 和通用的 Web 服务器(如 Nginx 或 Apache 服务器)相比,在处理大并发的 HTTP 请求时要弱一点,所以一般我们都会对大流量的 Web 系统做静态化改造,让大部分请 求和数据直接在 Nginx 服务器或者 Web 代理服务器(如 Varnish、Squid 等)上直接 返回(这样可以减少数据的序列化与反序列化),而 Java 层只需处理少量数据的动态 请求

 4. 并发读优化 也许有读者会觉得这个问题很容易解决,无非就是放到 Tair 缓存里面。集中式缓存为 了保证命中率一般都会采用一致性 Hash,所以同一个 key 会落到同一台机器上。虽然 单台缓存机器也能支撑 30w/s 的请求,但还是远不足以应对像“大秒”这种级别的热 点商品。那么,该如何彻底解决单点的瓶颈呢? 答案是采用应用层的 LocalCache,即在秒杀系统的单机上缓存商品相关的数据。 那么,又如何缓存(Cache)数据呢?你需要划分成动态数据和静态数据分别进行处理:

 像商品中的“标题”和“描述”这些本身不变的数据,会在秒杀开始之前全量推送 到秒杀机器上,并一直缓存到秒杀结束; 

像库存这类动态数据,会采用“被动失效”的方式缓存一定时间(一般是数秒), 失效后再去缓存拉取最新的数据。

像库存这种频繁更新的数据,一旦数据不一致,会不会导致超卖? 这就要用到前面介绍的读数据的分层校验原则了,读的场景可以允许一定的脏数据,因 为这里的误判只会导致少量原本无库存的下单请求被误认为有库存,可以等到真正写数 据时再保证最终的一致性,通过在数据的高可用性和一致性之间的平衡,来解决高并发 的数据读取问题

3.7秒杀系统“减库存”设计的核心逻辑

如果要设计一套秒杀系统,那我想你的老板肯定会先对你说:千万不要超卖,这是大前提。

如果你第一次接触秒杀,那你可能还不太理解,库存 100 件就卖 100 件,在数据库里 减到 0 就好了啊,这有什么麻烦的?是的,理论上是这样,但是具体到业务场景中,“减 库存”就不是这么简单了。 例如,我们平常购物都是这样,看到喜欢的商品然后下单,但并不是每个下单请求你都 最后付款了。你说系统是用户下单了就算这个商品卖出去了,还是等到用户真正付款了 才算卖出了呢? 我们可以先根据减库存是发生在下单阶段还是付款阶段,把减库存做一下划分。

3.7.1减库存有哪几种方式

在正常的电商平台购物场景中,用户的实际购买过程一般分为两步:下单和付款。你想 买一台 iPhone 手机,在商品页面点了“立即购买”按钮,核对信息之后点击“提交订 单”,这一步称为下单操作。下单之后,你只有真正完成付款操作才能算真正购买,也 就是俗话说的“落袋为安”。

那如果你是架构师,你会在哪个环节完成减库存的操作呢?总结来说,减库存操作一般 有如下几个方式:

下单减库存,即当买家下单后,在商品的总库存中减去买家购买数量。下单减库存 是最简单的减库存方式,也是控制最精确的一种,下单时直接通过数据库的事务机 制控制商品库存,这样一定不会出现超卖的情况。但是你要知道,有些人下完单可 能并不会付款。

付款减库存,即买家下单后,并不立即减库存,而是等到有用户付款后才真正减库 存,否则库存一直保留给其他买家。但因为付款时才减库存,如果并发比较高,有 可能出现买家下单后付不了款的情况,因为可能商品已经被其他人买走了。

预扣库存,这种方式相对复杂一些,买家下单后,库存为其保留一定的时间(如 10 分钟),超过这个时间,库存将会自动释放,释放后其他买家就可以继续购买。 在买家付款前,系统会校验该订单的库存是否还有保留:如果没有保留,则再次尝 试预扣;如果库存不足(也就是预扣失败)则不允许继续付款;如果预扣成功,则 完成付款并实际地减去库存。

未完......

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值