思维升级-如何设计一个秒杀系统?

技术面试中,高级开发职称以上必问,如果你没亲身设计过,那你需要了解一下设计中的细节。

一、场景分析:

1.限流:鉴于只有少部分用户能秒杀成功,所以要限制大部分流量,只允许少部分流量进入后端服务;

2.削峰:对于秒杀系统瞬时会涌入大量用户,所以在抢购一开始会有很高的瞬间峰值。高峰值流量是压垮系统很重要的原因,
所以如何把瞬间的高流量变成一段时间平稳的流量也是设计系统的重要思路。实现削峰的常用方法就是有效利用缓存和消息
中间件。

3.异步处理:秒杀系统是一个高并发系统,采用异步处理模式可以极大提高系统并发量,其实异步处理就是一种削峰实现。

4.内存缓存:秒杀系统最大的瓶颈一般是数据库读写,由于数据库读写属于磁盘IO,性能很低,如果能把部分数据或业务逻辑转移内存缓存,会极大提升效率。

5.可拓展:如果我们项支持更多用户,更大并发,最好就将系统设计成弹性可扩展的,如果流量来了,扩展机器就好,像淘宝京东等双十一活动时会增加大量机器应对交易高峰。

二、设计思路:

1.将请求拦截在系统上游,降低下游压力:
    秒杀系统特点是并发量极大,单实际秒杀成功的请求数量很少,所以可以在前端拦截掉一部分重复请求

2.充分利用缓存
3.消息队列
    削峰,拦截大量并发请求,后端从消息队列中主动拉取请求消息进行处理

前端方案
1.页面静态化:将活动页面上的所有可以静态的元素全部静态化,并减少动态元素,通过CDN抗峰值
2.禁止重复提交:用户提交后按钮置灰
3.用户限流:在某一段时间内只允许用户提交一次请求,例如IP限流方案

后端方案
网关:限制UserId访问频率,针对某些技术上的恶意攻击和插件,针对同一个UserId限制访问频率
服务层:
    1.消息队列缓存请求:数据库订阅消息减库存,操作成功的请求返回秒杀成功,失败的返回秒杀结束。
    2.利用缓存应对读请求:类似于12306,对于大部分的查询请求,可以利用缓存分担数据库压力
    3.利用缓存应对写请求:将库存信息转移到Redis,所有减库存操作都在redis中进行,在通过后台将redis数据同步到数据库中

数据库层
    一般请求到达数据库层时,都是符合数据库能力方位内的访问请求,一般不处理

设计过程中尽量不要有单点,单点是系统架构的大忌,单点意味着没有被封,风险不可控,设计分布式系统最重要的原则就是消除单点。
避免单点的关键点是避免将服务的状态和机器绑定,即服务无状态化,这样服务就可以在机器中随意移动。

三、热点数据处理(这一点非常重要)

2/8原则:数据的访问存在热点,20%的数据占用80%的访问流量,这20%的数据就是热点数据。
热点分为热点操作和热点数据。
热点操作:大量的刷新页面,大量的添加购物车,双十一零点大量的下单等。
热点数据:用户的热点请求对应的数据,也分为静态热点数据和动态热点数据。

处理热点数据的几种思路:一是优化,二是限制,三是隔离。
优化:缓存热点数据,如果做了动静分离,可以长期缓存静态数据。可以采用LRU淘汰算法替换热点数据。
限制:更多是一种保护机制,限制办法比如:对被访问商品的ID做一致性hash,然后根据hash做分桶,每个分桶设置一个处理队列,这样可以把热点商品
    限制在一个请求队列里,防止因某些热点商品占用太多的服务器资源,而使得其他请求始终得不到服务器处理资源。
隔离:隔离热点数据,不要让1%的请求影响到另外的99%,隔离后也方便对1%的请求做针对性的优化。
    业务隔离:把秒杀做成一种营销果冻,卖家要参加秒杀就需要单独报名,从技术上来说,卖家报名后对我们来说就有了已知热点,因此可以提前预热。
    系统隔离:运行时隔离,可以通过分组部署的方式和另外99%分开,秒杀可以申请单独的域名,目的也是让请求落到不同的集群中。
    数据隔离:秒杀所调用的数据大部分是热点数据,比如会启用单独的Cahce集群或者MySql数据库来放热点数据,目的也是不想0.01%的数据有机会影响99.99%数据。

流量削峰:本质上就是更多的延缓用户请求的发出,以便减少和过滤掉一些无效请求。
思路:排队、答题、分层过滤。这几种方式都是无损方案,不会损失用户的发出请求。

排队:用消息队列来缓冲瞬时流量,把同步的直接调用转换成异步的间接推送
答题:增加购买的复杂度,有两个好处,一是防止秒杀作弊器,二是延缓请求
分层过滤:采用漏斗式设计处理请求,在不同的层次尽可能过滤掉无效请求,让漏斗最末端的才是有效请求。

如何发现瓶颈
当QPS达到极限时,你的服务器CPU使用率是不是超过了95%,如果没有超过,表示CPU还有提升空间,要么是有锁限制,要么是有过多的本地I/O等待发生。
最常用的就是JProfiler和Yourkit这两个工具,他们可以列出整个请求中每个函数的CPU执行时间,可以发现哪个函数消耗的CPU时间最多,针对性的做优化。

减库存设计的核心逻辑
1.下单减库存。即买家下单后,在商品的总库存中减去购买数量,最简单也控制最精准的一种,直接通过数据库的事物机制控制商品库存,一定不会出现超卖。但是无法确定
    有些人下完单可能不会付款。
2.付款减库存。即买件下完单后确认付款完成才真正减库存,否则库存一致保留给其他买家。但是并发较高时,可能出现买家下单无法付款的情况。
3.预扣库存。实现方式相对复杂,买家下单后,库存为其保留如十分钟,超出时间未支付库存自动释放,付款前系统会校验订单的库存是否还有保留,如果没有保留,
    则尝试预扣,如果库存不足预扣失败,则不允许继续付款,如果预扣成功,则完成付款并实际的减库存。

大型秒杀中如何减库存:
业务系统中最常见的是预扣库存方案
由于参加秒杀的商品,一般都是“抢到就是赚到”,所以成功下单后却不付款的情况比较少,再加上卖家对秒杀商品的库存有严格限制,所以秒杀商品采用“下单减库存”更加合理。
另外,理论上由于“下单减库存”比“预扣库存”以及涉及第三方支付的“付款减库存”在逻辑上更为简单,所以性能上更占优势。

秒杀减库存的极致优化:

秒杀商品和普通商品的减库存还是有些差异的,例如商品数量比较少,交易时间段也比较短,因此这里有一个大胆的假设,即能否把秒杀商品减库存直接放到缓存系统中实现,也就是直接在缓存中减库存或者在一个带有持久化功能的缓存系统(如 Redis)中完成呢?

如果你的秒杀商品的减库存逻辑非常单一,比如没有复杂的 SKU 库存和总库存这种联动关系的话,我觉得完全可以。但是如果有比较复杂的减库存逻辑,或者需要使用事务,你还是必须在数据库中完成减库存。

如何设计兜底方案:
1. 架构阶段:架构阶段主要考虑系统的可扩展性和容错性,避免单点
2. 编码阶段:编码保证代码的健壮性,例如涉及远程调用问题时,要设置合理的超时退出机制,防止被其他系统拖垮,也要对调用的返回结果集有预期,防止返回的结果超出程序处理范围,最常见的做法就是对错误异常进行捕获,对无法预料的错误要有默认处理结果。
3. 测试阶段:测试主要是保证测试用例的覆盖度,保证最坏情况发生时,我们也有相应的处理流程。
4. 发布阶段:发布时也有一些地方需要注意,因为发布时最容易出现错误,因此要有紧急的回滚机制。
5. 运行阶段:运行时是系统的常态,系统大部分时间都会处于运行态,运行态最重要的是对系统的监控要准确及时,发现问题能够准确报警并且报警数据要准确详细,以便于排查问题。
6. 故障发生:故障发生时首先最重要的就是及时止损,例如由于程序问题导致商品价格错误,那就要及时下架商品或者关闭购买链接,防止造成重大资产损失。然后就是要能够及时恢复服务,并定位原因解决问题。

那么针对秒杀系统在遇到大流量时,应该从哪些方面来保障系统的稳定运行?

所以更多的是看如何针对运行阶段进行处理,这就引出了接下来的内容:降级、限流和拒绝服务。

降级
所谓“降级”,就是当系统的容量达到一定程度时,限制或者关闭系统的某些非核心功能,从而把有限的资源保留给更核心的业务。它是一个有目的、有计划的执行过程,所以对降级我们一般需要有一套预案来配合执行。如果我们把它系统化,就可以通过预案系统和开关系统来实现降级。

降级方案可以这样设计:当秒杀流量达到 5w/s 时,把成交记录的获取从展示 20 条降级到只展示 5 条。“从 20 改到 5”这个操作由一个开关来实现,也就是设置一个能够从开关系统动态获取的系统参数。

限流
如果说降级是牺牲了一部分次要的功能和用户的体验效果,那么限流就是更极端的一种保护措施了。限流就是当系统容量达到瓶颈时,我们需要通过限制一部分流量来保护系统,并做到既可以人工执行开关,也支持自动化保护的措施。

拒绝服务
如果限流还不能解决问题,最后一招就是直接拒绝服务了。

当系统负载达到一定阈值时,例如 CPU 使用率达到 90% 或者系统 load 值达到 2*CPU 核数时,系统直接拒绝所有请求,这种方式是最暴力但也最有效的系统保护方式。例如秒杀系统,我们在如下几个环节设计过载保护:

在最前端的 Nginx 上设置过载保护,当机器负载达到某个值时直接拒绝 HTTP 请求并返回 503 错误码,在 Java 层同样也可以设计过载保护。

拒绝服务可以说是一种不得已的兜底方案,用以防止最坏情况发生,防止因把服务器压跨而长时间彻底无法提供服务。像这种系统过载保护虽然在过载时无法提供服务,但是系统仍然可以运作,当负载下降时又很容易恢复,所以每个系统和每个环节都应该设置这个兜底方案,对系统做最坏情况下的保护。

你可能感兴趣的:(并发专栏,1024程序员节,java,学习)