信海龙(花名沧龙),十余年的互联网开发经验,2013年加入阿里巴巴,深耕于电商、社区相关应用开发与架构。同时也是多个开源项目的开发者和维护者。代表开源作品,tclip,基于人脸识别的图片裁剪扩展。
01
稳定性的重要性
对很多企业来说服务稳定性非常重要,首先稳定性问题会对企业带来直接的经济损失。举例来说,亚马逊的“Prime Day”当天出现的一个故障,给亚马逊带来了高达9900万美元的损失。这一个故障损失就可能是其它小公司市值的几倍。所以服务稳定性对公司影响是特别大的。而对于个人来说,服务不稳定性会影响员工的绩效,甚至影响个人前程。
02
保障策略架构篇
从架构层面保障稳定性,常见的策略包括限流,降级,隔离,超时,重试和集群等。
2.1.限流
- 限流目的
限流的目的主要有两点,第一点是防止系统高负荷运行,第二点是有效利用服务器资源。为什么要做限流?假如不封锁请求,可能会导致服务器报警,如果平时服务器只能处理100个请求,突然多出两个请求服务器或许勉强能够处理,但突然多了500个请求的话,后面的400个请求只处在积压状态,等服务器处理到第500个请求的时候,用户等待时间就会过长,而且最后积压部分的请求可能根本就是无效的处理,因为用户早已流失。
- 限流算法
常见限流的算法包括漏桶算法和令牌桶算法。漏桶算法如下图,图中的例子有个小桶,桶下面有个孔,每流一滴水就可以认为是一个请求进去。滴水的速率是一样的,孔的高度也是固定的。漏桶算法能保证每个请求的负载时长,即确定每秒能处理的请求数量。
漏痛算法实现如下图,可以设定桶的高度是5个,每秒漏两个。执行效果中前面5次结果都是true,之后中间一次结果是false,这说明桶已经装满,之后又漏了两滴水,因为false的时候sleep了一秒,所以下面又有两个true出来。
- 令牌桶算法
如下图,令牌桶算法也是有一个桶,但是桶不漏,桶里面放了一些令牌,每来一个请求就在桶里拿一个令牌,如果没有令牌它就可以等待,令牌满了就不再往里面加令牌。这样方法基本上也可以达到一个限流的目的。令牌桶算法和漏桶算法的一个显著区别是漏桶算法在后端取请求量时,基本上漏的速率是一样的,但是令牌桶算法中后端部分可以有突发请求,如果桶满了,可以将桶里所有令牌都拿走。
下图是令牌桶算法lua代码实现部分,当然读者还可以使用Nginx,Java脚本或者php脚本来实现。
2.2.降级
- 社区降级案例
一般情况下,系统上线之后总会遇到一些不稳定情况,比如redis挂掉,甚至后端数据库My SQL挂掉。当出现不稳定情况之后,系统如何保证继续提供这些服务。以社区案例为例,即便是My SQL挂掉,也要能够保证社区为用户提供基本的可读服务。其中一个策略是将一些热点数据,即用户经常浏览的信息或者最新的信息缓存起来,当后端服务不可用的时候,把这些数据展现给用户。大概流程如下图,数据存储部分后端会有一个脚本去分析Nginx里面的日志,然后去请求Vanish,Vanish再去请求后端,这样的话Vanish会有一个有效期,能够保证Vanish存进去的数据都是用户经常访问的一些数据。第二步,如何保证后端数据库挂掉的数据时候能迁过去?下图可以看到,Nginx中使用lua
脚本进行实现,它会检测后端服务返回的一些状态,使用计数器计算失败次数,如果频繁的达到一定程度的失败次数,就切换到从Vanish获取数据,最后推送给用户。这样能保证即便是后端的数据库挂掉,甚至即便所有的php进程都挂掉的时候,社区也能给用户提供一些基本的服务。
- 降级目的
降级的目的比较简单,第一个是保障服务器基本可用,第二个是保障服务的核心服务可用。降级是怎么一个思路呢?一般降级的每个策略都是针对一个场景,预想特定场景下需要要解决什么问题;然后再梳理在这个场景下需要保留哪些核心基本服务;最后才选定技术方案,系统化的进行实现。简单讲就是先确定需要达到什么目的,再去了解是什么样的情况,最后制定策略或者计划。比如,系统会调用第三方服务,而第三方服务有可能挂掉,这是一种典型的场景。再比如,系统本身调用推荐服务,但是推荐服务也会挂掉,这种场景下不能够因为没有推荐数据就不显示数据,还是需要展示一些数据,这是一种基本的核心服务。每年的双11或者一些大型活动中基本都会存在降级。降级不仅仅是存在于资源故障场景下,资源不够用时也可能会需要降级,因为资源不够用需要关注重点。如大促活动中,需要先保证交易服务正常运行,其它消耗资源的服务(如对账)可以后续再去处理。
2.3.超时
- 超时案例
社区对外提供接口服务,对方的反馈是接口服务较慢。接口部分流程是查一段数据,然后将数据反映过去,其问题点在于系统中超时时间设置过长。比如调用Memcache,但是Memcache已经挂掉,由于超时设置过长,数据需要等到超时时间结束以后再返回,导致接口一直在等待。那如何设置超时时间才合理?要注意超时时间并不是固定的值,而是需要针对整个业务,根据特定场景设置超时时间值。
- 如何设置超时时间
大体的思路如下图。第一步,识别业务需要的服务响应时间。比如,需要100毫秒去响应数据,之后统计业务里面可能需要调多少服务。第二步,统计服务日常的响应时间。第三步,分清主次,即分出哪些是核心服务。因为核心服务一旦失败,整个链路便不可用,所以可以对核心服务的时间设置的宽松一些。如果一些服务调不通,但又不影响整个链路,可以对它的时间设置的相对严格。
设置完超时之后需要验证,借助模拟手段封端口(如下图),模拟故障,然后检查数据返回时间是否在指定的时间内。
2.4.隔离
- 隔离案例
2013年左右,手机客户端开始逐渐升级起来,很多项目既有PC端也有客户端,所以同一个服务即要为PC端又要为客户端提供API接口。一旦遇到大型活动或者需要手机推送,服务会遇到不稳定情况,服务的不稳定会导致PC端也受影响,所以需要将服务进行物理隔离,从原先耦合到一块的服务器分到不同的机器组。隔离目的非常简单,要限制住不稳定因素导致的风险,停止传播。
- 隔离形式
隔离的常见形式包括几种。第一是秒杀场景,秒杀场景一个高并发的场景,可能带来的问题也比较多,在高并发场景下秒杀的时候,需要和一些正常的业务区分开来,不建议一台机器既提供秒杀也提供进程服务。另外,秒杀的时候会产生热点数据,如售卖数据。数据库更新比较频繁,从数据库层面也可以进行隔离,将热点部分和正常服务部分从资源上隔离。第二个场景是慢SQL隔离,一个资源隔离。一条慢SQL会导致整个服务不稳定。每请求一次线程,慢SQL会一直耗着当前线程,所以资源占用非常大。第三个场景是机房隔离。一般大公司都会做多机房部署,其目的就是确保稳定性。确保稳定性时不要做跨机房调用,否则耦合度会比较高,假如A调B,B挂掉,A服务也会受影响。一般确保稳定性都是做本机房的调用。而且本机房的调用性能也比较快。最后一个场景是进程隔离,因为进程比线程更加稳定。
2.5.集群
对小公司而言,一台机器就提供一个服务,如果机器挂掉服务恢复就会成为一个问题。一般解决方法是做一个集群,从原来的一台机器提供服务变为可以用多台机器提供服务。集群的目的是为了解决单点的问题。集群的形式主要有主备,即同时只有一台机器提供整个服务,可以有一台或者多台提供备份,备份不仅要包含代码层面,整个服务运行所依赖的资源都要有备份。另外一个形式是主从。主是提供一个完整的服务,从是提供部分的服务。还有一种是多主,多主指的是每一台机器的决策是对等的,都会对外提供一些服务。随着集群形式的不同,对代码编写的并发性上有一定要求。主备只需要考虑单机的并发控制,主从是考虑同时提供服务的部分。比如加锁,主备上只要加一个本地的技能锁就可以,主从或者多主则需要加分布式锁。
03
保证策略流程篇
保证稳定性策略的流程方面上分为下图中四个点,code review, 压测,灰度和监控。
3.1.Code Review
code review目的是在项目上线前及时发现一些问题。经验比较丰富的人可以将经验进行分享。code review基本经过三个阶段。第一个阶段是头脑风暴式,一群开发人员围着代码做code review,虽然时间成本较高,效果也不太理想,但是这种方式也有好处,在前期可以将大家的意见进行整理,制定code review的规范。第二种code review形式是演讲式,专家事先把代码做一下review,整理一些点,然后进行分享。演讲式可以按照轮岗制,相对头脑风暴式大大节约了时间。目前常见的code review 形式是结对式,由一个或者两个专家结对,相互review,时间上比较灵活,也不需要占据会议室资源。
3.2.压测
- 压测目的
压测的目的,第一是保证系统稳定性。在高并发的时候,检测系统是否稳定,因为一些问题在流量比较低的时候发现不了,只有在高并发的时候才能发现这个问题。第二是检测性能的抗压能力,检查系统能承受多大的QPS。
- 压测关注点
首先,压测机器和被压测服务在同一网段,尽量避免因为网络原因导致压测的结果不准确。第二点是关注服务器的负载,注意不要把服务器压到100%,服务器快要崩的时候,得到的值意义不大。应该是服务器负载达到60%~70%的时候,看QPS是多少。另外,压测并发数据是逐步递增的过程,到一个点的时候,并发数据越多代表QPS越低。最后,根据测试环境的压测结果估算线上的承载能力。估算的公式是线上QPS = 单机QPS 机器数 0.7。后面会乘以一个系数(0.7)是因为线上put上去的时候总会存在一些损耗。
- 全链路压测
有一些测试在测试环境下无法实现压测,所以现在发展成了全链路压测。全链路压测大概分成三个核心关注点。第一个是数据模型的构造。全链路压测是模拟线上真正的数据模型,比如说访问详情页的人数,下单的人数,人数比例,登陆人数等等参数,尽量按照真实数据模拟,构建仿真模型,这样才能真正的发现线上的一些问题。注意全链路压测不是在测试环境下实现,而是在线上压测。第二个是压测工具构建。可以是借助开源的压测工具,阿里自建了压测平台,根据数据模型提升流量。第三点是流量的隔离。对流量增加标识,保证不影响线上的数据,将全链路测试流量放到测试的存储中。比如生成一个订单order表,同时也会生成一个影子表test_order。如果发现是来自于全链路压测的流量,就把这个数据写到影子表test_order里面,这样能够保证存储。无论是缓存还是数据库存储都能够进行流量隔离。
3.3.灰度
灰度目的是小范围试错,尽量发现问题。灰度的策略大概有以下几种,第一个策略是只让某一个地区的人先访问最新的特性,遇到问题的话用户及时反馈,问题也只会影响特定地区。另外一个策略是基于用户属性,如一个推荐系统,请求过来的时候能区分新老用户,它对新老用户的推荐的策略可能是不一样的,从而来验证策略的准确性和有效性。第三种策略是基于数据,从一批用户中选取几个用户进行处理。比如,对供应链的供应商的数据做处理,但是一般情况下不敢保证代码上线之后100%没问题。这时先选择一个供应商处理,验证数据,确保没问题再全量处理所有的供应商。最后是基于平台,一般都发生在客户端场景下。客户端与服务端不同,服务端一般是针对这个平台,先指挥这个平台先发布新版本,反馈不错再推到整个全面平台。对于客户端的灰度技术的实现如下图,给客户端集中一个Cookie,请求到了之后在Nginx中去检查Cookie,根据不同的Cookie把情趣转到不同的组。比如组A有新特性,组B是老版本,根据不同的Cookie转到不同组,保证只有一部分人可以看到新的特性。
3.4.监控
- 监控注意点
监控的目的是可以自动化及时发现问题。监控需要注意几点问题,第一是全方面监控,系统和服务全部都要监控。第二是报警分级,监控报警的系数设置的要合理。最后一点是在真实环境下做数据收集。比如,A和B服务器,只在B服务器做监控。如果A服务器My SQL数据库网络出问题后,因为在监控上B服务器是正常的,监控不会报警。所以要在应用服务器上做监控才会报警具体哪台机器哪个服务出现故障等信息。
- 自研监控系统
下图是阿里自研的监控系统。首先确定对哪些指标进行监控。将整个指标的数据绘制出来,查看指标数据波动。一旦遇到问题,可以很方便的进行对比。另外要确定影响,将所有相关的指标聚合起来。比如供应商的团队操控系统经常会发生仓库操作卡顿,有很多因素都会导致卡顿,如PC端调用其它接口较慢,服务器load比较高等。仓库人员无法关注具体的细节,他们在影响界面查看指标影响值,一眼就可以知道是哪项指标不合格导致的卡顿。之后对造成的影响进行相应的处理,目前一般的行为有效报警或短信报警。
通过以上的介绍,希望大家能够全面了解服务稳定性的重要性和相关策略。熟悉保障服务稳定性的常见策略(限流,降级,隔离,超时,重试和集群)以及保障策略的流程。