我这边是在京东某部门负责营销活动的开发,大家所知道的秒杀其实就是活动的一种,这些活动有个共同点,就是流量极大.首先每个商品你都要知道他是不是活动商品?属于什么活动商品?我有没有购买资格?活动的开团时间是什么?活动有哪些商品,每个商品的活动叠加结果是什么?还有许多活动有库存的概念,比如秒杀或者限量购,逻辑很复杂,这里里面有太多太多的细节,很多细节可能没法一一说清楚,这里总结些重要的秒杀的问题以及解决方案给大家,
不扯虚的,全部是线上沉淀下来的纯干货
;
这里我以我做的一个最有意思的营销活动,先给大家介绍一下,不涉及技术,只是后面出案例会以此为案例而已,看技术可以跳过
我这个活动叫做《周期循环购
》,这个活动可以选择参与的商品,活动支持设置活动总的有效时间(比如2022-02-02 12:21~2033-12-02 13:22`),另外我们还支持让你选择周一到周日哪几天是开团日(比如周三,周五),每个开团日再进行设置哪几个时间段为开团时段(比如00:00~01:00,12:00:02:00),然后还支持设置商品在每个时段的库存是多少那么其实在这里看,团购的商品对于我来说就是一个产品的概念(SPU),而我的营销活动限制的库存才是真正的库存(SKU),另外这个又涉及到周期性库存的概念,每天每个时间段都有一个自己的独立库存,这比单一的商品库存要复杂许多许多。
正文:
一.明确团购秒杀为什么不好做?
不好做,说道理是由于团购属于折扣商品,通常价格比较低,流量真的极大,并发访问下容易造成各种并发症,举几个例子如下:
- 服务器流量,负载极高,万一挂了就玩球了,这里可以指业务、中间件、数据库等服务器,比如数据库我们都知道抗压能力不咋地;
- 流量不均匀,有些时候会有部分热门商品(比如首发的iphone)特别高频访问,把服务器资源全给占用了其他商品就被搁置排队了;
- 逻辑复杂,下单和回滚或者取消订单要保证幂等,防止数据不一致以及超买超卖的可能,万一造成资损,大家都知道这个的严重性;
二.如何解决秒杀可能带来的问题呢?我这里提出一些我在项目里使用的经验和技巧
2.1.解决高流量问题
首先流量过高,处理速度慢
是根本原因,因此我们最先解决这部分问题;
2.1.1 访问类请求;
-
隔离
动态数据和静态数据,静态数据可以前置到用户端或者CDN里,比如商品图片这玩意,这样非必要的请求就可以拦截一部分了; - 后端数据加
缓存
,让高性能的玩意直面高并发,比如我上面提到的营销活动,目前基本上做到了查询全部缓存化,所有查询全部走Redis缓存,大家都知道Redis的性能吧; -
限频
,比如限制用户对于同一商品详情页的刷新次数,大家是不是经常会在活动开始的时候疯狂刷新....到刷新频次的时候可以显示的提示,也可以默默的请求无效化,另外这种限制尽量前置化
, -
层层过滤
,另外涉及到数据校验的,如果大家的系统有多个层次(比如前端->api站点等->server),最好做好层层过滤,每个层次都将一部分数据拦截了,比如前端可以做限频,api站点层做风控,数据校验等等,这样最后有效请求就少了许多了; -
任务拆分
,尤其IO型,很多时候我们有些调用是不需要严格保障串行化的,比如获取商品详情接口,可能里面需要展示优惠券信息,以及该用户购买该商品的价格,库存余量,那么我们这里可能需要调用券服务A,计价服务B,库存服务C三个服务的接口,这三接口就没有先后顺序,并且都是比较耗时的服务,串行调用耗时A+B+C,比如0.4+0.5+0.5=1.4秒,那么我们多线程分发任务去调用的话,总耗时就是最长的服务时间即0.5秒。
2.2.2 下单类请求(很多和上面一样的就不说了,比如层层过滤)
削峰
,如果我们的流量极大,我们可以进行削峰限流,推荐大家采用异步下单的方式,安全可靠,所谓削峰限流我的理解是可以看做银行柜体,就算流量再大,你都要排队,能处理多少看银行的能力,我有一个银行就处理慢点,有十个银行就处理快点,没啥问题,大不了你就办不成功呗,当然这里注意防止丢单,可以采用分布式事务;延缓请求
我们可以订阅系统负载,或者一些监控服务,在系统流量极大的时候,触发答题系统,在大家进行下单的时候进行答题,那么有的人答题快有的人答题慢,大家拼的就不全是手速了,还有脑速,哈哈,最主要的是把开团那一瞬间的流量均匀到答题的时间了,这点效果显著;-
精准限流
,比如我们可以采用付定金预购的方式先把购买人群锁死,有几个好处:- 第一看的人少了,你没付定金到时候你看他干啥?
- 第二 买的时候尝试下单的人少了,只有付了定金的允许下单
- 第三呢由于定金的存在大家买的时候会慎重一些减轻了退货啥的造成无用订单以及商家卖不出去情况;
-
库存缓存化
,我们可以把库存做到redis里,我们先采用lua(预查redis库存->再预扣redis的方式)->乐观锁扣除mysql库存- 为什么这里不直接decr预扣redis要用lua先查redis再预扣?主要是考虑到由于秒杀团购等属于赔钱引流的生意,所以大部分请求都是远远大于库存的,因此极有可能买不到,如果我们直接扣redis,那扣失败了,咱们不还是要还库存加回去吗
- 另外呢,要注意由于库存的特殊性,我们要保证redis里的数据和mysql 的数据的一致性,因此我们要使用一些事务来保证,比如TCC或者MQ事务实现;
- 这里咱们将mysql库存扣减放到了最后一步也减少了mysql锁竞争的过程了,这性能飞升啊;
2.2.3 一些别的优化以及线上环境积累的服务高可靠经验
-
引入
热点发现,数据隔离,精准路由
机制,正如我前面所说某些商品确实牛逼,流量极大,虽然这部分数据极少,但是却有可能会挤压正常服务;- 热点发现又分为
静态发现和动态发现
,所谓静态发现
呢就是在商品还没卖,我们就知道这个是不是热点,比如京东这边呢就有根据店铺流量,过往同类商品售卖情况啥啥的智能打标签服务,有个预判,一下就知道你是不是热点了,动态发现
是某些商品咱们不知道他这么牛逼,然后卖的时候一下就火爆了(商家都惊喜的笑醒了); -
数据隔离、精准路由
,数据隔离,这部分极其热点的数据呢可以把他预热到指定的热点缓存和热点数据库避免影响普通商品,另外为了避免影响呢,我们还要将这部分商品路由到指定服务器进行操作,路由到指定服务器还不够,甚至我们还要加队列进行排队,引入极热点数据的异步下单过程,另外优化的点就是如果前面已经卖完了就不要傻傻的再一个个排队尝试了,直接提前结束,提前通知;
- 热点发现又分为
多级缓存
机制,虽然说Redis单机就十来万的并发就比较牛逼了,但终归还是个第三方服务的网络查询,有的时候redis使用率过高也可能造成瓶颈,如果我们可以将部分不会变的数据进行本地化,进行本地缓存,那这样是效率最高的,用啥都没这个好使,另外也分担了redis的压力。啥是不会变的呢?比如活动信息,很多参与618,1111的秒杀大促活动是不允许随便变更数据的;降级次要请求
,在下单等重要服务压力极大的情况下,我们可以降级次要请求,将服务器性能给重要请求用,比如说降级评论查询,只允许查几页,只允许查一层。另外像双十一这样的超级活动日,可能会降级很多服务,比如不允许活动日退货啊啥的;限流
比如某接口服务压测的极限值是5W qps,我们就设置为4.5W为限流值,防止服务突破系统瓶颈,并做到既可以人工执行开关,也支持自动化保护的措施。这个一般公司的网关服务或者混沌工程里都会提供该服务;有一点需要注意的是,这里的限流其实也不只是保护自己的服务,其他作为被调用测的下游小伙伴的服务这里也起到的保护作用。兜底
拒绝服务,根据系统资源的情况,为了防止系统产生不可控的情况,要做到考虑最坏的情况,那么最后一招就是直接拒绝服务了。
当系统负载达到一定阈值时,例如 CPU 使用率达到 90% 或者系统 load 值达到 2*CPU 核数时,系统直接拒绝所有请求,这种方式是最暴力但也最有效的系统保护方式。例如秒杀系统,我们在如下几个环节设计过载保护:机会检查,库存检查,下单
在最前端的网关
上设置过载保护
,当机器负载达到某个值时直接拒绝 HTTP 请求并返回 503 错误码,在 Java 层同样也可以设计过载保护。
拒绝服务可以说是一种不得已的兜底方案,用以防止最坏情况发生,防止因把服务器压跨而长时间彻底无法提供服务。像这种系统过载保护虽然在过载时无法提供服务,但是系统仍然可以运作,当负载下降时又很容易恢复,所以每个系统和每个环节都应该设置这个兜底方案,对系统做最坏情况下的保护。依赖隔离-熔断
熔断器与保险丝有些类似,当电流过大时,保险丝自动熔断以保护我们的电器。这里的熔断也是一样,当第三方服务长期不可用,我们要制定快速失败策略,防止影响或者拖垮我方服务. 这也是一个十分重要的点!
假设在没有熔断机制保护下,我们可能会无数次的重试或慢请求,势必持续加大服务端压力或系统耗尽业务线程,造成恶性循环;如果直接关闭重试功能,当服务端又可用的时候,我们如何恢复?
当请求失败比率(失败/总数)达到一定阈值后,熔断器开启,并休眠一段时间,这段休眠期过后熔断器将处与半开状态(half-open),在此状态下将试探性的放过一部分流量(Hystrix只支持single request),如果这部分流量调用成功后,再次将熔断器闭合,否则熔断器继续保持开启并进入下一轮休眠周期。
建议使用场景:Client端直接调用远程的Server端(server端由于某种原因不可用,从client端发出请求到server端超时响应之间占用了系统资源,如内存,数据库连接等)或共享资源。-
异常报警
,异常告警一定要有,这个属于危机处理,有哪些方面需要关注的呢?- 服务器资源告警,涉及CPU、内存、带宽、线程、磁盘空间等
- 服务质量告警,涉及tp99阈值、FullGC、异常code、超时时间、qps阈值等等
- 中间件质量告警,使用了啥一般都需要添加告警规则,我这里说一些常见的
- Redis(内存使用率(尤其设置了拒绝淘汰一定要关注),tps,相应时间,bigkey,流入流出流量,hotkey订阅和告警)
- MQ(主要关注挤压消息量,内存,CPU,消费者数量异常变动告警)
- Mysql(内存,CPU,磁盘空间,连接数,阻塞线程,等待线程,超时线程等等)
- 分布式任务调度平台(时间,积压数据,超时等)
- ES,银河,混沌,鹰眼等等等其他平台类的或者不常用的这里不说了,自己衡量吧.........
三、应急处理原则
- 1、保证自己能存活,死道友不死贫道(我们也只能做到自己的可用问题)
- 2、若已雪崩,快速止损,恢复可用服务
- 3、若自己属于下游,接近瓶颈,联系调用方,协商限流。
题外话:真正优化的地方还有很多很多很多,服务架构也是非常复杂的,我做项目请教了下这边做商品服务的架构师,据他给我说的双十一商品详情页服务,光CPU就用了几万核。。。让我当场就震惊了。。。也给我看了下架构和服务的图,确实非常非常牛逼!目前京东这边在备战春晚红包,流量按照双十一的十倍以上去备战,真心期待后面有关于这次备战的分享~~
今天关于应对高流量,我就说到这了,后面有时间还会补点细节
关于下单,回滚订单,定时取消订单等可以看我后面写的这个https://www.jianshu.com/p/a844a1592902
关于有效订单的高并发问题可以看https://www.jianshu.com/p/552c4093832e (需要先看上面再看这个)