腾讯后台开发技术总监浅谈过载保护 小心雪崩效应

摘要: 每个系统,都有自己的最大处理能力,后台技术人员对此必须很清楚,且要注意自我保护,不然就会被雪球压垮,出现雪崩。

雪球:

对于时延敏感的服务,当外部请求超过系统处理能力,如果系统没有做相应保护,可能导致历史累计的超时请求达到一定规模,像雪球一样形成恶性循环。由于系统处理的每个请求都因为超时而无效,系统对外呈现的服务能力为0,且这种情况下不能自动恢复。

腾讯后台开发技术总监bison,给大家分享了非常精彩的过载保护,其看似简单,但是要做好并不容易。这里用两个曾经经历的反面案例,给出过载保护的直观展现,并附上一点感想。

案例一 基本情况

如下图,进程A是一个单进程系统,通过udp套接字接收前端请求进行处理。在处理过程中,需要访问后端系统B,是同步的方式访问后端系统B,根据后端系统B的SLA,超时时间设置是100ms。前端用户请求的超时时间是1s。

进程A的时序是:

Step1: 从socket接收缓冲区接收用户请求

Step2: 进行本地逻辑处理

Step3: 发送请求到后端系统B

Step4: 等待后端系统B返回

Step5: 接收后端系统B的应答

Step6: 应答前端用户,回到step1处理下一个请求

正常情况下的负载

正常情况下:

1、前端请求报文大小约100Bytes。前端请求的峰值每分钟1800次,即峰值每秒30次。

2、后端系统B并行能力较高,每秒可以处理10000次以上,绝大多数请求处理时延在20ms内。

3、进程A在处理请求的时候,主要时延是在等待后端系统B,其他本地运算耗时非常少,小于1ms

这个时候,我们可以看出,系统工作良好,因为处理时延在20ms内,每秒进程A每秒中可以处理50个请求,足以将用户每秒峰值30个请求及时处理完。

导火索

某天,后端系统B进行了新特性发布,由于内部逻辑变复杂,导致每个请求处理时延从20ms延长至50ms,根据sla的100ms超时时间,这个时延仍然在正常范围内。当用户请求达到峰值时间点时,灾难出现了,用户每次操作都是“服务器超时无响应”,整个服务不可用。

过载分析

当后端系统B处理时延延长至50ms的时候,进程A每秒只能处理20个请求(1s / 50ms = 20 )。小于正常情况下的用户请求峰值30次/s。这个时候操作失败的用户往往会重试,我们观察到前端用户请求增加了6倍以上,达到200次/s,是进程A最 大处理能力(20次/s)的10倍!

这个时候为什么所有用户发现操作都是失败的呢? 为什么不是1/10的用户发现操作能成功呢? 因为请求量和处理能力之间巨大的差异使得5.6s内就迅速填满了socket接收缓冲区(平均能缓存1000个请 求,1000/(200-20)=5.6s),并且该缓冲区将一直保持满的状态。这意味着,一个请求被追加到缓冲区里后,要等待50s(缓存1000个请 求,每秒处理20个,需要50s)后才能被进程A 取出来处理,这个时候用户早就看到操作超时了。换句话说,进程A每次处理的请求,都已经是50s以前产生的,进程A一直在做无用功。雪球产生了。

案例二 基本情况

前端系统C通过udp访问后端serverD,后端server D的udp套接字缓冲区为4MB,每个请求大小约400字节。后端serverD偶尔处理超时情况下,前端系统C会重试,最多重试2次。

正常情况下的负载

正常情况,后端serverD单机收到请求峰值为300次/s,后端serverD单机处理能力是每秒1500次,时延10ms左右。这个时候工作正常。

导火索

由于产品特性(例如提前通知大量用户,未来某某时刻将进行一项秒杀活动;类似奥运门票,大量用户提前得知信息:某日开始发售门票),大量的用户聚集在同 一时刻发起了大量请求,超出了后台serverD的最大负载能力。操作响应失败的用户又重试, 中间系统的重试,进一步带来了更大量的请求(正常情况下的9倍)。导致所有用户操作都是失败的。

过载分析

只是导火索不一样,同案例一,巨大的请求和处理能力之间的鸿沟,导致后端serverD的4M大小的接收缓冲区迅速填满(4秒就填满),且过载时间内, 接收缓冲区一直都是满的。而处理完缓冲区内的请求,ServerD需要6秒以上(4MB / 400 / 1500 = 6.7S)。所以serverD处理的请求都是6s之前放入缓冲区的,而该请求在最前端早已经超时。雪球形成了。

启示

1、 每 个系统,自己的最大处理能力是多少要做到清清楚楚。例如案例一中的前端进程A,他的最大处理能力不是50次/s,也不是20次/S,而是10次/S。因为 它是单进程同步的访问后端B, 且访问后端B的超时时间是100ms,所以他的处理能力就是1S/100ms=10次/S。而平时处理能力表现为50次/S,只是运气好。

2、 每个系统要做好自我保护,量力而为,而不是尽力而为。对于超出自己处理能力范围的请求,要勇于拒绝。

3、 每个系统要有能力发现哪些是有效的请求,哪些是无效的请求。上面两个案例中,过载的系统都不具备这中慧眼,逮着请求做死的处理,雪球时其实是做无用功。

4、 前端系统有保护后端系统的义务,sla中承诺多大的能力,就只给到后端多大的压力。这就要求每一个前后端接口的地方,都有明确的负载约定,一环扣一环。

5、 当过载发生时,该拒绝的请求(1、超出整个系统处理能力范围的;2、已经超时的无效请求)越早拒绝越好。就像上海机场到市区的高速上,刚出机场就有电子公示牌显示,进入市区某某路段拥堵,请绕行。

6、 对于用户的重试行为,要适当的延缓。例如登录发现后端响应失败,再重新展现登录页面前,可以适当延时几秒钟,并展现进度条等友好界面。当多次重试还失败的情况下,要安抚用户。

7、 产品特性设计和发布上,要尽量避免某个时刻导致大量用户集体触发某些请求的设计。发布的时候注意灰度。

8、 中间层server对后端发送请求,重试机制要慎用,一定要用的话要有严格频率控制。

9、 当雪球发生了,直接清空雪球队列(例如重启进程可以清空socket 缓冲区)可能是快速恢复的有效方法。

10、过载保护很重要的一点,不是说要加强系统性能、容量,成功应答所有请求,而是保证在高压下,系统的服务能力不要陡降到0,而是顽强的对外展现最大有效处理能力。

对于“每个系统要有能力发现哪些是有效的请求,哪些是雪球无效的请求”,这里推荐一种方案:在该系统每个机器上新增一个进程:interface进程。 Interface进程能够快速的从socket缓冲区中取得请求,打上当前时间戳,压入channel。业务处理进程从channel中获取请求和该请 求的时间戳,如果发现时间戳早于当前时间减去超时时间(即已经超时,处理也没有意义),就直接丢弃该请求,或者应答一个失败报文。

Channel是一个先进先出的通信方式,可以是socket,也可以是共享内存、消息队列、或者管道,不限。

Socket缓冲区要设置合理,如果过大,导致及时interface进程都需要处理长时间才能清空该队列,就不合适了。建议的大小上限是:缓存住超时时间内interface进程能够处理掉的请求个数(注意考虑网络通讯中的元数据)。


分布式存储系统的雪崩效应

一 分布式存储系统背景

副本是分布式存储系统中的常见概念:将一定大小的数据按照一定的冗余策略存储,以保障系统在局部故障情况下的可用性。

副本间的冗余复制方式有多种,比较常用有两类:

  • Pipeline:像个管道,a->b->c,通过管道的方式进行数据的复制。该方式吞吐较高,但有慢节点问题,某一节点出现拥塞,整个过程都会受影响
  • 分发:client -> a  client ->b client ->c。系统整体吞吐较低,但无慢节点问题

对于冗余副本数目,本文选择常见的三副本方案。

分布式存储系统一般拥有自动恢复副本的功能,在局部存储节点出错时,其他节点(数据副本的主控节点或者client节点,依副本复制协议而定)自动发起副本修复,将该宕机存储节点上的数据副本恢复到其他健康节点上。在少量宕机情况下,集群的副本自动修复策略会正常运行。但依照大规模存储服务运维经验,月百分之X的磁盘故障率和月千分之X的交换机故障率有很大的可能性导致一年当中出现几次机器数目较多的宕机。另外,批量升级过程中若出现了升级bug,集群按照宕机处理需要进行副本修复,导致原本正常时间内可以完成的升级时间延长,也容易出现数目较多的宕机事件。

二 雪崩效应的产生

在一段时间内数目较多的宕机事件有较大可能性诱发系统的大规模副本补全策略。目前的分布式存储系统的两个特点导致这个大规模副本补全策略容易让系统产生雪崩效应:

   a. 集群整体的free空间较小:通常整体<=30%, 局部机器小于<=20% 甚至10%

   b. 应用混布:不同的应用部署在同一台物理/虚拟机器上以最大化利用硬件资源

今年火起来的各种网盘、云盘类服务就是a的典型情况。在各大公司拼个人存储容量到1T的背后,其实也在拼运营成本、运维成本。现有的云存储大多只增不减、或者根据数据冷热程度做数据分级(类似Facebook的数据分级项目)。云存储总量大,但增量相对小,为了减少存储资源和带宽资源浪费,新创建的文件若原有的存储数据中已有相同的md5或者sha1签名则当做已有文件做内部链接,不再进行新文件的创建。但即使这样,整体的数据量还是很大。

目前云存储相关业务未有明显的收入来源,每年却有数万每台的服务器成本,为运营成本的考虑,后端分布式存储系统的空闲率很低。而瞬间的批量宕机会带来大量的副本修复,大量的副本修复很有可能继而打满原本就接近存储quota的其他存活机器,继而让该机器处于宕机或者只读状态。如此继续,整个集群可能雪崩,系统残废。

三 预防雪崩

本节主要讨论如何在系统内部的逻辑处理上防止系统整体雪崩的发生。预防的重要性大于事故之后的处理,预测集群状态、提前进行优化也成为预防雪崩的一个方向。

下面选取曾经发生过的几个实际场景与大家分享。

1. 跨机架副本选择算法和机器资源、用户逻辑隔离

现场还原:

某天运维同学发现某集群几十台机器瞬间失联,负责触发修复副本的主控节点开始进行疯狂的副本修复。大量用户开始反馈集群变慢,读写夯住。

现场应对:

优先解决——副本修复量过大造成的集群整体受影响。

a. 处理的工程师当机立断,gdb到进程更改修复副本的条件为副本<2,而非原本的3(replicas_num),让主控节点这个时候仅修复副本数小于2个的文件,即保证未丢失的文件有至少一个冗余副本,防止只有一个副本的数据因可能再次发生的挂机造成文件丢失。

b. 紧急解决这批机器失联问题,发现是交换机问题,a.b.c.d ip网段的c网段机器批量故障。催促网络组尽快修复。

c. 副本修复到>=2之后,Gdb更改检测副本不足周期,将几十秒的检测时间推迟到1天。等待网络组解决交换机问题。

d. 网络恢复,原有的机器重新加入集群。大量2副本文件重新变为3副本,部分3副本全丢失文件找回。

e. 恢复主控节点到正常参数设置状态,系统开始正常修复。

改进措施:

在改进措施前,先分析下这次事件暴露的系统不足:

1) Master参数不支持热修正,Gdb线上进程风险过大。

2) 一定数量但局域性的机器故障影响了整体集群(几十台相对一个大集群仍属于局域性故障)。如上所述,月千分之几的故障率总有机会让你的存储系统经历一次交换机故障带来的集群影响。

案例分析后的改进措施出炉:

1)  Master支持热修正功能排期提前,尽早支持核心参数的热修改。

热修改在上线后的效果可观,后续规避过数次线上问题。

2) 在选择数据副本存储宿主机器的pickup算法中加入跨交换机(机架位)策略,强制——或者尽量保证——副本选择时跨机架位。这种算法底下的副本,至少有1个副本与其他两个副本处于不同的交换机下(IP a.b.c.d的c段)。该措施同时作用于新的存储数据副本选择和副本缺失后的副本补全策略,能在副本宿主选择上保证系统不会因为交换机的宕机而出现数据丢失,进而避免一直处于副本补全队列/列表的大量的丢失副本节点加重主控节点负载。

3) 机器按region划分隔离功能提上日程;用户存储位置按照region进行逻辑划分功能提上日程;Pickup算法加入跨region提上日程。

a) 机器按照物理位置划分region、用户按照region进行逻辑存储位置划分,能让集群在局部故障的情况下仅影响被逻辑划分进使用这部分机器的用户。

这样一来,最坏情况无非是这个region不可用,导致拥有这个region读写权限的用户受影响。Pickup算法跨region的设计进一步保证被划分region的用户不会因为一个region不可用而出现数据丢失,因为其他副本存到其他region上了。于是,核心交换机故障导致一个region数百台机器的宕机也不会对集群造成范围过大的影响了。

b) 增加region可信度概念,将机器的稳定性因素加入到副本冗余算法中。

当集群规模达到一定量后,会出现机器稳定性不同的问题(一般来说,同一批上线的机器稳定性一致)。通过标记region的稳定性,能强制在选择数据副本的时候将至少一个副本至于稳定副本中,减少全部副本丢失的概率。

c) Region划分需要综合考虑用户操作响应时间SLA、物理机器稳定情况、地理位置等信息。

合理的region划分对提升系统稳定性、提升操作相应时间、预防系统崩溃都有益处。精巧的划分规则会带来整体的稳定性提升,但也增加了系统的复杂度。这块如何取舍,留给读者朋友深入思考了。

2. 让集群流控起来

流控方面有个通用且符合分布式存储系统特点的原则:任何操作都不应占用过多的处理时间。这里的“任何操作”包含了在系统出现流量激增、局部达到一定数量的机器宕机时进行的操作。只有平滑且成功的处理这些操作,才能保证系统不因为异常而出现整体受影响,甚至雪崩。

现场还原:

1) 场景1 某天运维同学发现,集群写操作在某段时间大增。通过观察某个存储节点,发现不仅是写、而且是随机写!某些产品线的整体吞吐下降了。

2) 场景2 某集群存储大户需要进行业务调整,原有的数据做变更,大量数据需要删除。

运维同学发现,a. 整个集群整体上处于疯狂gc垃圾回收阶段 b. 集群响应速度明显变慢,特别是涉及到meta元信息更新的操作。

3) 场景3 某天运维同学突然发现集群并发量激增,单一用户xyz进行了大量的并发操作,按照原有的用户调研,该用户不应该拥有如此规模的使用场景。

此类集群某些操作预期外的激增还有很多,不再累述。

现场应对:

1) 立刻电联相关用户,了解操作激增原因,不合理的激增需要立刻处理。

我们发现过如下不合理的激增:

a. 场景1类:通过Review代码发现,大量的操作进行了随机读写更改。建议用户将随机读写转换为读取后更改+写新文件+删除旧文件,转换随机读写为顺序读写。

b. 场景3类:某产品线在线上进行了性能测试。运维同学立刻通知该产品线停止了相关操作。所有公有集群再次发通过邮件强调,不可用于性能测试。如有需要,联系相关人员在独占集群进行性能场景测试。

2) 推动设计和实现集群各个环节的流控机制功能并上线。

改进措施:

1) 用户操作流控

a. 对用户操作进行流控限制

可通过系统内部设计实现,也可通过外部的网络限流等方式实现,对单用户做一定的流控限制,防止单个用户占用过多整个集群的资源。

b. 存储节点操作流控

可按照对集群的资源消耗高低分为High – Medium – Low三层,每层实现类似于抢token的设计,每层token数目在集群实践后调整为比较适合的值。这样能防止某类操作过多消耗集群负载。若某类操作过多消耗负载,其他操作类的请求有较大delay可能,继而引发timeout后的重试、小范围的崩溃,有一定几率蔓延到整个集群并产生整体崩溃。

c. 垃圾回收gc单独做流控处理。删除操作在分布式存储系统里面常用设计是:接收到用户删除操作时,标记删除内容的meta信息,直接回返,后续进行策略控制,限流的删除,防止大量的gc操作消耗过多单机存储节点的磁盘处理能力。具体的限流策略和token值设置需要根据集群特点进行实践并得出较优设置。

2) 流控黑名单

用户因为对线上做测试类的场景可以通过人为制度约束,但无法避免线上用户bug导致效果等同于线上测试规模的场景。这类的场景一般在短时间内操作数严重超过限流上限。

对此类场景可进行流控黑名单设置,当某用户短时间内(e.g. 1小时)严重超过设置的上限时,将该用户加入黑名单,暂时阻塞操作。外围的监控会通知运维组同学紧急处理。

3) 存储节点并发修复、创建副本流控

大量的数据副本修复操作或者副本创建操作如果不加以速度限制,将占用存储节点的带宽和CPU、内存等资源,影响正常的读写服务,出现大量的延迟。而大量的延迟可能引发重试,加重集群的繁忙程度。

同一个数据宿主进程需要限制并发副本修复、副本创建的个数,这样对入口带宽的占用不会过大,进程也不会因为过量进行这类操作而增加大量其他操作的延迟时间。这对于采用分发的副本复制协议的系统尤其重要。分发协议一般都有慢节点检查机制,副本流控不会进一步加重系统延迟而增大成为慢节点的可能。如果慢节点可能性增大,新创建的文件可能在创建时就因为慢节点检查机制而缺少副本,这会让集群状况更加恶化。

3. 提前预测、提前行动

1) 预测磁盘故障,容错单磁盘错误。

场景复现:

某厂商的SSD盘某批次存在问题,集群上线运行一段时间后,局部集中出现数量较多的坏盘,但并非所有的盘都损坏。当时并未有单磁盘容错机制,一块磁盘坏掉,整个机器就被置成不可用状态,这样导致拥有这批坏盘的机器都不可用,集群在一段时间内都处于副本修复状态,吞吐受到较大影响。

改进措施:

a) 对硬盘进行健康性预测,自动迁移大概率即将成为坏盘的数据副本

近年来,对磁盘健康状态进行提前预测的技术越来越成熟,技术上已可以预判磁盘健康程度并在磁盘拥有大概率坏掉前,自动迁移数据到其他磁盘,减少磁盘坏掉对系统稳定性的影响。

b) 对单硬盘错误进行容错处理

存储节点支持对坏盘的异常处理。单盘挂掉时,自动迁移/修复单盘的原有数据到其他盘,而不是进程整体宕掉,因为一旦整体宕掉,其他盘的数据也会被分布式存储系统当做缺失副本,存储资源紧张的集群经历一次这样的宕机事件会造成长时间的副本修复过程。在现有的分布式存储系统中, 也有类似淘宝TFS那样,每个磁盘启动一个进程进行管理,整机挂载多少个盘就启动多少个进程。

2) 根据现有存储分布,预测均衡性发展,提前进行负载均衡操作。

这类的策略设计越来越常见。由于分布式存储集群挂机后的修复策略使得集群某些机器总有几率成为热点机器,我们可以对此类的机器进行热点预测,提前迁移部分数据到相对负载低的机器。

负载均衡策略和副本选择策略一样,需要取舍复杂度和优化程度问题。复杂的均衡策略带来好的集群负载,但也因此引入高复杂度、高bug率问题。如何取舍,仍旧是个困扰分布式存储系统设计者的难题。

四 安全模式

安全模式是项目实践过程中产生的防分布式存储系统雪崩大杀器,因此我特别将其单独列为一节介绍。其基本思路是在一定时间内宕机数目超过预期上限则让集群进入安全模式,按照策略配置、情况严重程度,停止修复副本、停止读写,直到停止一切操作(一般策略)。

在没有机器region概念的系统中,安全模式可以起到很好的保护作用。我过去参与的一个项目经历的某次大规模宕机,由于没有安全模式,系统进行正常的处理副本修复,生生将原本健康的存储节点也打到残废,进而雪崩,整个集群都陷入疯狂副本修复状态。这种状态之后的集群修复过程会因为已发生的副本修复导致的元信息/实际数据的更改而变的困难重重。 该事件最后结局是数据从冷备数据中恢复了一份,丢失了冷备到故障发生时间的数据。

当然,安全模式并非完美无缺。“一段时间”、“上限”该如何设置、什么时候停副本修复、什么时候停读、什么时候停写、是自己恢复还是人工干预恢复到正常状态、安全模式力度是否要到region级别,这些问题都需要安全模式考虑,而此类的设计一般都和集群设计的目标用户息息相关。举例,如果是低延迟且业务敏感用户,可能会选择小规模故障不能影响读写,而高延迟、高吞吐集群就可以接受停读写。

五 思考

由于分布式存储系统的复杂性和篇幅所限,本文仅选择有限个典型场景进行了分析和讨论, 真实的分布式存储系统远比这数个案例复杂的多、细节的多。如何平衡集群异常自动化处理和引入的复杂度,如何较好的实现流控和避免影响低延迟用户的响应时间,如何引导集群进行负载均衡和避免因负载均衡带来的过量集群资源开销,这类问题在真实的分布式存储系统设计中层出不穷。如果设计者是你,你会如何取舍呢?


你可能感兴趣的:(大规模分布计算(云,搜索引擎))