前两章《面试官:如何构建一个高可用的系统?》、《面试官:如何构建一个高可用的系统?(2)》主要是从"减少故障次数" 和 "降低故障时长"的角度进行分析的。
本文我们从"缩小故障范围"的角度,再来详细讲讲。
缩小故障范围的核心点在于四个字------"简化链路"。
对于分布式系统来说,通过一些手段进行"简化链路",这样可以把整个系统从全线崩盘的大故障,变成局部可控的小故障,以保证系统的可用性,降低公司的业务损失。
熔断
熔断机制,是指在分布式系统中,当某个下游服务出现超时、错误率过高或资源不足等过载现象时,上游服务会迅速切断对该下游服务的请求,以避免出现故障扩散的情况。
熔断机制可以保证整个系统的可用性,避免因一个服务的局部小规模故障,导致整个系统全局瘫痪的后果。
上述概念有些枯燥晦涩,我们以上图进行举例说明:
电商的商品详情页接口,用户端服务需要调用商品中心、营销中心、门店服务、推荐服务和评价服务,来完成商品详情页的数据聚合展示。
正常情况下,我们假定商品详情接口需要200ms,其中用户端服务本身需要消耗50ms,其余的五个下游服务各消耗30ms。
那么单线程每秒可以处理5个接口请求,以Tomcat默认的最大线程数(maxThreads)等于200来算,用户端单服务器最大可承载1000 QPS。
公式如下:
1s / (50ms + (30ms * 5)) * 200 = 1000 QPS
但有一天评价服务出现异常,原本的30ms的耗时变成了830ms,那么公式如下:
1s / (50ms + (30ms * 4)+ 830ms) * 200 = 200 QPS
在系统吞吐量降为原来的1/5的情况下,非常容易被用户的高并发请求打挂。
此时,我们就需要程序中的自动熔断策略,在服务有损的情况下,将商品详情页接口耗时依然维持在原来的200ms左右。
现在主流的实现熔断功能的断路器组件,是Netflix Hystrix和Alibaba Sentinel,两者对比如下:
至于代码级的使用细节和参数解释,由于篇幅的原因,这里先不过多解释了。
唯一要提醒的是,只有下游依赖服务中的弱依赖才可以进行熔断,强依赖熔断了就没有价值了,下面我再解释一下强弱依赖:
- 强依赖:假定服务A依赖于服务B,服务B出现故障不可用时,服务A也不可用。
- 弱依赖:假定服务A依赖于服务B,服务B出现故障不可用时,服务A虽然受到了些许影响,但是仍然可用。
降级
服务降级,是指当系统出现高负载或异常时,通过牺牲部分非核心功能的方式,保证系统核心功能的可用性,这是一种弃车保帅的策略。
我们依然用电商场景进行举例:
电商的商家端管理系统,商家每天需要登录系统进行商品、库存和订单的管理,以保证业务的正常运转,此为核心功能。
而对于评价、财务的管理,以及商家自促和统计分析功能,其重要性和实时性要求相对较低,此为商家后台的非核心功能。
那么当商家端管理系统出现高负载或异常时,我们对其进行降级操作,把上图右侧的非核心功能通过拒绝请求的方式,保证上图左侧的核心功能可用。
另外,基于此降级场景,最好的方式是通过配置中心以修改参数的方式操作降级,而不是现场修改业务代码进行Hotfix的方式。
此外,我们还可以对功能繁多的复杂系统采取自定义多Level降级的方式,在保证系统可用性的同时,尽量兼顾用户体验。
我们还以上图的商家端管理系统举例,配置中心的表达式如下:
- degradation.level = 0(默认值,不启用降级)
- degradation.level = 1(启用轻度降级,拒绝"统计分析"功能)
- degradation.level = 2(启用中度降级,拒绝"统计分析"和"商家自促"功能)
- degradation.level = 3(启用重度降级,拒绝上图右侧的所有非核心功能)
当然,此方案适合于功能繁多的复杂系统,如果是上图的商家端管理系统,其实一个功能对应一个降级配置是最灵活的。
链路拆分
分布式系统中,各个微服务是按照其重要程度分等级的。
我们以电商场景进行举例:
P0级服务:当该服务不可用时,会导致用户侧业务完全瘫痪、不可进行。如:用户端聚合服务、商家端聚合服务、商品中心、订单中心、用户中心、支付服务。
P1级服务:当该服务不可用时,用户侧业务虽然可以继续运行,但会收到较大影响;或是公司内部业务不可进行。如:营销平台、评价服务、CRM系统、上单系统。
P2级服务:当该服务不可用时,用户侧业务轻微影响体验,或是公司内部业务虽然可以继续进行,但会受到较大影响。如:消息中心、图像检索服务。
P3级服务:重要程度不高的内部系统,如:OA系统、报表系统、自动化测试平台等。
链路拆分的全称是"核心链路拆分",即:识别并拆分出P0级别的服务,使其与非P0级服务在资源上(MySQL、Redis、MQ、ES等)进行完全隔离,在服务间可具备可隔离性(熔断、降级、代码级容错处理),以保证系统核心功能的可用性。
链路拆分和降级所要达成的目标是一致的,只是要复杂很多,涉及到了系统的整体架构设计。
请见上图的电商服务,当然,真正意义上的电商业务,服务数量要远比这多很多,这里仅作为举例。
(1)图左侧的服务均为核心链路的服务(P0级服务),意思是只要这些服务不挂,电商业务虽然会影响些许体验,但业务依然会正常运行。
(2)图左侧的中间件资源(Redis、MySQL、Redis)也都是作为核心链路独立的,意味着其不会被图右侧的非核心服务的读写操作所影响。
(3)图左侧的服务会依赖图右侧的服务,比如:用户端服务会依赖评价系统服务,如果评价系统服务慢了或者挂了,只要做好熔断降级或代码级容错处理,业务依然会正常运行。
(4)图右侧的非核心服务,尽量不要依赖于图左侧的核心服务,如果有业务诉求的话,可以独立部署一套与核心服务代码完全相同的非核心服务以供使用。如:图右侧非核心链路中的商品中心、订单中心和用户服务。
(5)图左侧核心链路的服务数量是要控制的,因为数量越多,链路环节就越长,不可控的未知因素就越多。如果核心链路的服务数量超过全部服务的15%,那就需要进行服务梳理整合。
另外,链路拆分是一项极其耗时且复杂的工作,研发团队在考虑此策略时,一定要衡量好ROI(投入产出比)。
个人建议,如果是一天都没多少业务量的创新业务或边缘业务,那就免了吧。
切MVP版本
MVP,最小可行产品(Minimum Viable Product),该策略往往是在上述三种策略都失效后才启用的,是"缩小故障范围"中的终极保命大招,但对用户体验的伤害也是最大的。
如果说,正常产品形态是美味可口的满汉全席的话,那MVP版本只是能勉强填饱肚子的窝头咸菜。
上述概念有些枯燥晦涩,我们以上图进行举例说明:
某在线教育平台,正常产品形态具备上图右侧的N多功能,但当系统发生故障,用了"熔断"、"降级"、"链路拆分"策略依然没能解决问题时,只能把系统切到图左侧只具备"登录"、"查看课表"和"上课"的MVP版本中,保证学生在较差的用户体验下,可以正常上课。
一般来讲,MVP版本是用一个单体服务的方式,来完成系统仅存的所有功能的,且该单体服务与图右侧的整个系统服务 + 中间件服务,是完全分开独立部署的,只为完全简化和解耦化。
结语
整个关于系统可用性的三部曲就全部讲完了,因为涉及的知识点太多,所以没能讲到代码级的细节中,后续有机会再进行深入讲解。