毕业这一年多来,对于学生时代面试“必考”的高并发高可用,主要是高并发状态下的读接口,保证其处于高可用状态,有了一些实战的经验,这里记录和总结一下。
在我看来,如果一个后端流程要能够扛住高并发大流量,必须严格经历以下几个流程:
1、梳理关键路径
首先我们需要梳理一个业务流程的关键路径以及次要路径,比如展示一段商品列表,最核心的过程就是要使得商品列表给用户正常展示,不影响用户进入商详和下单,即商品的列表,以及商品的基本属性是主业务流程。
而关于商品的其他属性,比如商品的标签、商品包含什么优惠券、商品有哪些服务能力等等,这些其实属于对商品列表产出锦上添花的功能,是次要流程。
对于次要流程,除了得保证将其摘除时不影响主要流程外,还需要严格配置好每一段次要流程的超时时间,如果调用超时,需要及时断掉以免影响到主流程。
2、限流
限流的目的是通过对并发访问/请求进行限速或者一个时间窗口内的请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务、排队或等待、直接降级。
比如商品列表,排序的逻辑通常有一个推荐服务来展示,而这个推荐服务通常不是只服务于单一业务。如果因为我们业务使得推荐服务负载过高,可能会影响到其他业务的正常展示,所以需要对下游推荐业务做好限流。
而限流之后,如何展示本来应该出现的信息,则取决于这些信息是否属于关键路径。对于非关键路径,直接使其不展示即可;对于关键路径,则需要做好必要的兜底,具体如何兜底,会在之后介绍。
3、降级
降级的最终目的是为了保证核心服务可用,即我们之前梳理的关键路径是可用的,其可以分为主动降级和被动降级。
比如下游某个服务突然不可用时,我们自己的服务可以通过灰度开关等方式,主动切换到不依赖该下游服务的状态,只要做好了对应的兜底,或者说是属于非关键路径,我们的业务并不会受到下游服务挂掉的影响。
被动降级是指我们自己服务,检测到下游服务的成功率下跌严重时,可以切换为不依赖该下游服务的状态,保证自身主业务的正常运行。
4、兜底
兜底是每个高并发业务最最重要的环节,对于关键路径上的信息,在保证数据时效性的前提下,必须做好各种缓存兜底,使得在下游服务全部异常时,本地依然有对应的数据结果展示出来。
兜底缓存可以分为两种,一种是启动定时任务主动刷新。比如商品列表,我们可以使用一个定时任务,定时从数据表中,针对每个类目,拉取前N条商品id,缓存在redis缓存中。而redis缓存在高并发下也并不是绝对可靠的(实际上经常抖动),我们可以使得每个实例启动一个定时任务,定时从redis中拉取必要信息,在本地做local cache,这种情况下,接口的瓶颈基本是在机器的CPU和网关了,之后只需要堆机器即可横向扩展流量的承载量。(至于为啥先拉到redis,我们的实例经常是几百个,如果全部并发访问数据库,可能直接将数据库打爆)
另一种是懒加载形式,当流量过来时,直接将上一次的结果做一次缓存,设定一个短失效时间即可。这种形式存在缓存被瞬时流量击穿的可能,更推荐第一种定时任务的形式。
5、过载保护
过载保护组件,是对服务的每个接口做一个自动的评级,当服务的负载到了一定程度时,会优先保障重要的接口,以及重要的用户访问请求得到返回。
6、压测
压测是每个高并发业务的必经过程,通过压测,可以让我们模拟实际高并发状态下服务的抗压情况,经常的业务可以让我们对业务的各个流程,限流、降级等情况更加了解,最好每次业务迭代完成后都进行对应的压测。
7、涉及组件
用到的流控组件主要是阿里巴巴的Sentiel,虽然似乎只支持单机限流,但是已经可以满足绝大多数的高并发场景了。