阿里架构师如何演绎一场完美的“秒杀”:API加速的业务逻辑

一天清晨,我被一个客户电话惊醒,客户异常焦急,寻问CDN能不能帮助他们解决“秒杀”的问题,他们昨天刚刚进行了“整点秒杀活动”,结果并发量过大,致使服务宕机,用户投诉。php

为了理清思路,我问了对方三个问题:前端

(1)服务宕机的表现是什么?java

(2)业务的基本架构什么样?程序员

(3)秒杀的峰值并发到多少?面试

顺着这些线索,咱们先一块儿还原了应用场景:redis

某电商业务架构图算法

该公司是一家P2P理财网站,常有用户在整点抢购高利率理财产品的“整点秒杀活动”。如上图所示,终端用户请求先经过前端负载均衡,而后到达运行实际电商逻辑的Web Server;再下层是运行在VM上的8台Redis,负责存储与业务相关的Cache数据,如用户Profile、理财产品信息、用户帐单信息等。实际落地数据存储在MySQL中,该MySQL只进行了简单的分库分表及读写分离。sql

进行“秒杀”时,先由风控和运营人员选好理财产品,而后标记到数据库中;活动开始由产品人员放开,终端用户抢购。数据库

该公司的业务主要来自移动端,平时流量较少,但“秒杀”活动时会瞬间产生大量流量,峰值并发达到10万以上(其中可能包括bot),如此大的并发主要是集中在如下两类接口:后端

对于理财产品的刷新接口,相似GET /get_fprod.php?uid={$1}&pid={$2}&sid={$3},此类接口的请求量最多,占比90%。

对于理财产品的下单接口,相似 GET /order_fprod?uid={$1}&pid={$2}&oid={$3}&sid={$4},此类接口的请求量较少,占比不到1%,但存在大量504超时。

其中uid是用户ID,pid是理财产品ID,oid是订单号,sid是随着客户端用户变化的随机token标识。

场景解读

根据与客户沟通获得的场景,初步获得了如下结论:

(1)客户以移动业务为主,产品经过API在客户端渲染UI,产品中几乎没有静态资源,带宽流量不高,传统CDN没法达到卸载压力的做用;

(2)秒杀时,产生大量502/504超时请求,说明此时用户请求已超过服务端的业务承载能力,急需扩容。

基于以上两点,我没有建议该公司采购CDN服务,而是推荐服务扩容,但随着我方对于业务更深层次的分析,逐渐发现了一些诡异的事情。

“诡异”现象

(1) 数据库主从负载极不均衡,经过MySQL管理工具,发现主库的Query量高达80%;

(2)Redis Cache节点负载极不均衡,经过查看redis info发现,秒杀时,其中一台Redis请求量极大,占比达90%以上,其余Redis请求量却很低。

上述反常现象激起了双方技术人员的兴趣,这也许就是问题的关键!随着分析深刻,第一个现象的缘由浮出水面:该公司在使用数据库时,并未如某些大型电商平台同样使用数据库中间件层进行MySQL请求的路由分发,而是在业务代码端,使用语言层面的框架完成读写分离工做。这带来了两个弊端:

程序员绕过语言层框架开发,并未真正实施读写分离; 产品人员要求展示效果实时,倒逼开发人员修改业务逻辑,会牺牲读写分离,使数据都在主库读写。

Cache穿透示意图

接着,第二个现象的缘由也逐渐清晰:秒杀时,大量用户访问极少数理财产品,当这几个产品的pid刚好被hash到同一个Redis上,就会致使Cache节点热点失衡,全部请求最终集中在一个Redis,而这个Redis就是业务的瓶颈!

对症下药

  1. 使用数据库中间件进行读写分离以及横向扩展控制

使用数据库中间件能够带来诸多好处,其中最重要的是可对业务层隐藏部分数据库细节,更好地控制业务。固然,引入数据库中间层也存在明显缺点,在业务总体架构中增长一层组件,违反了“简单有效”的设计原则。对于不少互联网公司,在早期甚至中期没有数据库中间层也很正常。 但当业务发展到必定阶段,引入数据库中间层是利大于弊的。

基于经验,我方推荐客户使用MySQL Route,基本能够知足简单需求,如:链接复用;负载均衡;读写分离。

MySQL Route架构图

上图是MySQL Router官方架构图,能够看到,MySQL Router优点在于插件化设计,官方提供了一系列插件供使用。

除MySQL Router,国内还有不少开源数据库中间件能够采用,如阿里、美团等。 使用数据库中间层,不只能够解决性能问题,还能在安全方面起到做用,如审计、流量限制等,甚至拦截SQL注入、劣质SQL语句等。

2. 使用API加速服务缓解服务端压力

Cache服务失衡是比较棘手的问题。“秒杀”时,用户高频访问少数几个理财产品信息,当其Cache数据恰巧分配在同一节点,大量请求会瞬间集中到一台或少数几台节点,这就是Cache服务失衡的本质缘由。不只在电商“秒杀”场景中,其余有瞬间热点访问的业务类型也会存在这个问题。以微博为例,曾因明星热点事件致使接口缓慢甚至服务宕机,归根到底也是这个缘由。“爆料”的瞬间,一个微博会在短期内海量传播,该微博ID被同时打开,全部流量会集中到一个Redis节点。

这个问题如何解决?首先,Cache一般以某个数据结构的key为维度进行hash存储,大量用户只访问一个或几个key时,将致使Redis Cache节点负载不均衡,这是否必定对服务产生影响,则视并发状况而定,但这是一个巨大隐患。针对这个问题,客户提出了一种解决方案:把一个理财产品的Cache数据再拆散,1个key变成多个,下降key被分配到同一Cache节点的几率。但这种方法存在很大弊端:

(1)须要修改代码,本来一条get请求就能够完成的逻辑,要换成多条才能拼成;

(2)平常全部get/set操做的时间消耗都将成倍增长,由于1%的热点事件增长99%常规操做的时间,严重违背二八法则。

基于以上问题,咱们推荐客户使用白山云聚合CLN-X的“API加速”来解决这个问题。

API加速

API加速彻底不一样于传统CDN的链路加速,经过缓存API返回内容并结合TCP广域网优化技术,对API请求进行优化。白山API加速将每一个API的response数据毫秒级缓存在全网边缘节点,节点内存中的response数据以LRU(Least Recently Used)算法交换。在“热点事件”时,最热的信息持续保存在边缘节点,当客户端访问该API时,边缘节点可直接返回结果,没必要返回源站。整个架构以下:

API加速架构图

API加速服务在网络边缘节点提供对API的加速能力,包括:API返回结果缓存能力、API请求回源网络加速能力。

传统观点认为,动态资源(API)没法缓存,但白山提出“任何资源均可以被缓存,只是过时时间不一样”。对于常见的静态资源,缓存过时时间较长;而API并不是不能被缓存,只是过时时间很短。如一个查询股价的API,可设定过时时间为50毫秒;百米运动员起跑反应时间为100-200毫秒,50毫秒对于PC端或移动端的用户体验并不会形成影响。

没有缓存时,1秒内若有10000个用户同时访问,后端承受10000个并发;若是设置50毫秒的缓存时间,理论上可将后端并发下降到20个(1秒/50毫秒=20),后端负载下降至五百分之一,其余请求由缓存服务器直接返回给用户。

综上所述,白山API加速为客户提供毫秒级缓存,在不影响用户体验的前提下提升终端用户响应速度,同时下降服务端的业务负载压力。

API加速还支持自定义缓存规则,使其更贴近业务,包括QueryString、Header、Path三种类型,针对场景,设定以下规则:

GET /get_fprod.php?uid={$1}&pid={$2}&sid={$3},每一个理财产品都有独立ID,产品信息不随用户ID和客户端随机信息变化,所以Cache key可忽略URI中参数的{$1}和{$3},/get_fprod.php?pid={$2}就是在边缘节点存储毫秒级的Cache key。

缓存的过时时间如何肯定呢?与业务相关,这须要对客户提供的脱敏日志进行分析,可初步设定过时时间为500毫秒,最后还需考虑RTT修正值,以适应广域网环境;RTT则由API加速服务自动捕捉并实时更新。

实际效果

经过为客户主要的瓶颈接口配置API加速服务,并在峰值时间,从如下两个维度对比API加速服务开启与关闭时的效果:

终端用户请求平均响应时间和响应码200比例 服务集群平均负载 最终效果以下:

如图A所示,峰值期间终端用户请求平均响应时间,从3秒左右压缩至40毫秒之内;如图B所示,峰值期间全部请求响应码200的比例从70%左右提高至100%;图C表示,峰值期间,后端CPU Idle从10%左右提升至97%左右。实测对比数据代表,API加速对下降平均响应时间、提高用户体验效果十分显著,在下降后端服务器负载方面效果更加明显,使用API加速的后端CPU Idle可保持在91%以上。

针对上面的技术我特地整理了一下,有不少技术不是靠几句话能讲清楚,因此干脆找朋友录制了一些视频,不少问题其实答案很简单,可是背后的思考和逻辑不简单,要作到知其然还要知其因此然。若是想学习Java工程化、高性能及分布式、深刻浅出。微服务、Spring,MyBatis,Netty源码分析的朋友能够加个人Java进阶群:680130298,群里有阿里大牛直播讲解技术,以及Java大型互联网技术的视频免费分享给你们。

  • 具备1-5工做经验的,面对目前流行的技术不知从何下手,须要突破技术瓶颈的能够加群。

  • 在公司待久了,过得很安逸,但跳槽时面试碰壁。须要在短期内进修、跳槽拿高薪的能够加群。

  • 若是没有工做经验,但基础很是扎实,对java工做机制,经常使用设计思想,经常使用java开发框架掌握熟练的能够加群。

后续建议

数据库失衡和缓存Redis失衡问题已经解决,但除上述问题,还有不少环节能够改善:

1. 队列服务异步化请求

目前客户最终落地数据库请求直接请求到MySQL,未经队列缓冲,建议使用队列服务排队处理峰值请求,其好处在于能在大访问量时对请求进行调度,并可控制实际到达数据库的并发,从而有效保护数据库后端。

2. API防火墙屏蔽恶意Bot

用户日志中含有大量明显且规律的扫描软件痕迹,如sqlmap、fimap等,虽然还没有对业务形成较大影响,但却使服务端资源被占用。建议在负载均衡最前端对扫描行为予以屏蔽,以提升安全性,同时提高服务效率。除恶意Bot,抢单、刷单等行为也会对服务产生影响,建议使用API防御服务识别与拦截。

3. 产品层考虑服务降级设计

该客户在总体业务上,没有服务降级设计,产品功能优先级未作划分,致使重要的数据库、Cache等众多基础服务混杂。一旦“秒杀”致使数据库穿透等严重问题时,总体服务将不可用。这种状况应从新梳理业务单元,按照优先级切分基础服务,首屏、产品列表、购买、订单等信息优先级最高;其次是非重要功能,如评论、帐单等;若是后端负载较大,必要时可直接舍弃次要功能,从而下降后端负载,保证服务稳定。

总结

解决相似“整点秒杀活动”的情景,是一个系统复杂的工程,就文中客户暴露出来的数据库负载不均匀、Cache缓存负载不均匀等问题,可经过采用数据库中间层和API加速等技术解决,最终可取得理想效果。

上述“秒杀”案例,只是API加速的一个典型应用场景,接下来我还会撰文对API加速问题进行更为系统的剖析。

相关文章
相关标签/搜索