架构设计 8-高可用架构设计之故障处理

扫描左侧二维码 关注公众号 回复 “架构设计” 获取架构设计笔记完整思维导图

典型表现

  • 系统并没有宕机、网络也没有中断,但业务却出现问题了
  • 如业务响应缓慢、大量访问超时和大量访问出现异常

主要原因

在于系统压力太大、负载太高,导致无法快速处理业务请求,由此引发更多的后续问题。原因分类:

  • 内部原因:包括程序 bug 导致死循环,某个接口导致数据库慢查询,程序逻辑不完善导致耗尽内存等。
  • 外部原因:包括黑客攻击,促销或者抢购引入了超出平时几倍甚至几十倍的用户,第三方系统大量请求,第三方系统响应缓慢等。

常见情况

最常见的情况就是,数据库慢查询将数据库的服务器资源耗尽,导致读写超时,业务读写数据库时要么无法连接数据库、要么超时,最终用户看到的现象就是访问很慢,一会儿访问抛出异常,一会儿访问又是正常结果

解决接口级故障的核心思想

  • 优先保证核心业务
  • 优先保证绝大部分用户

应对方法

降级

降级指系统将某些业务或者接口的功能降低,可以是只提供部分功能,也可以是完全停掉所有功能。核心思想:丢车保帅,优先保证核心业务。

常见的实现降级的方式

  • 系统后门降级:就是系统预留了后门用于降级操作

    • 优点:系统后门降级的方式实现成本低
    • 缺点:如果服务器数量多,需要一台一台去操作,效率比较低,这在故障处理争分夺秒的场景下是比较浪费时间的。
  • 独立降级系统:将降级操作独立到一个单独的系统中,实现复杂的权限管理、批量操作等功能。

熔断

熔断是指按照规则停掉外部接口的访问,防止某些外部接口故障导致自己的系统处理能力急剧下降或者出故障。与降级区别:

  • 降级的目的是应对系统自身的故障
  • 熔断的目的是应对依赖的外部系统故障的情况

实现熔断机制键点

  • 一是需要有一个统一的 API 调用层,由 API 调用层来进行采样或者统计。如果接口调用散落在代码各处,就没法进行统一处理了。
  • 二是阈值的设计,例如 1 分钟内 30% 的请求响应时间超过 1 秒就熔断,这个策略中的“1 分钟”“30%”“1 秒”都对最终的熔断效果有影响。实践中,一般都是先根据分析确定阈值,然后上线观察效果,再进行调优。

限流

限流指只允许系统能够承受的访问量进来,超出系统访问能力的请求将被丢弃。与降级区别:

  • 降级是从系统功能优先级的角度考虑如何应对故障
  • 限流则是从用户访问压力的角度来考虑如何应对故障

基于请求限流

分类

  • 限制总量,也就是限制某个指标的累积上限,常见的是限制当前系统服务的用户总量
  • 限制时间量,也就是限制一段时间内某个指标的上限,例如 1 分钟内只允许 10000 个用户访问;每秒请求峰值最高为 10 万。

优点:无论是限制总量还是限制时间量,共同的特点都是实现简单。

缺点:

  • 比较难以找到合适的阈值
  • 即使找到了合适的阈值,基于请求限流还面临硬件相关的问题。例如一台 32 核的机器和 64 核的机器处理能力差别很大

解决方案

  • 为了找到合理的阈值,通常情况下可以采用性能压测来确定阈值,但性能压测也存在覆盖场景有限的问题,可能出现某个性能压测没有覆盖的功能导致系统压力很大
  • 逐步优化:先设定一个阈值然后上线观察运行情况,发现不合理就调整阈值。

适用场景:根据阈值来限制访问量的方式更多的适应于业务功能比较简单的系统,例如负载均衡系统、网关系统、抢购系统等。

基于资源限流

基于请求限流是从系统外部考虑的,而基于资源限流是从系统内部考虑的,也就是找到系统内部影响性能的关键资源,对其使用上限进行限制。常见的内部资源包括连接数、文件句柄、线程数和请求队列等。基于资源限流相比基于请求限流能够更加有效地反映当前系统的压力。难点:

  • 如何确定关键资源
  • 如何确定关键资源的阈值
  • 解决方案:逐步调优的过程:设计的时候先根据推断选择某个关键资源和阈值,然后测试验证,再上线观察,如果发现不合理,再进行优化。

算法

时间窗:限制一定时间窗口内的请求量或者资源消耗量

  • 固定时间窗:统计固定时间周期内的请求量或者资源消耗量,超过限额就会启动限流。优缺点:

    • 优点:实现简单
    • 缺点:存在临界点问题
  • 滑动时间窗:两个统计周期部分重叠,从而避免短时间内的两个统计点分属不同的时间窗的情况。优缺点:

    • 优点:解决了临界点问题
    • 缺点:实现复杂些

桶算法:用一个虚拟的“桶”来临时存储一些东西,来起到控制速率的效果。

漏桶:将请求放入“桶”(消息队列等),业务处理单元(线程、进程和应用等)从桶里拿请求处理,桶满则丢弃新的请求。

  • 设计关键点:

    • 流入速率不固定:可能瞬间流入非常多的请求,例如 0 点签到、整点秒杀。
    • 匀速 (极速) 流出:这是理解漏桶算法的关键,也就是说即使大量请求进入了漏桶,但是从漏桶流出的速度是匀速的,速度的最大值就是系统的极限处理速度。需要注意的是:如果漏桶没有堆积,那么流出速度就等于流入速度,这个时候流出速度就不是匀速的。这样就保证了系统在收到海量请求的时候不被压垮,这是第一层的保护措施。
    • 桶满则丢弃请求:这是第二层保护措施,也就是说漏桶不是无限容量,而是有限容量,例如漏桶最多存储 100 万个请求,桶满了则直接丢弃后面的请求。
  • 优点

    • 实现简单
    • 提供双层保护措施
  • 缺点

    • 突发大量流量时丢弃的请求较少,因为漏桶本身有缓存请求的作用。
    • 桶大小动态调整比较困难(例如 Java BlockingQueue),需要不断的尝试才能找到符合业务需求的最佳桶大小。
    • 无法精确控制流出速度,也就是业务的处理速度。
  • 适用场景:主要适用于瞬时高并发流量的场景(例如刚才提到的 0 点签到、整点秒杀等)。

令牌桶:令牌桶算法和漏桶算法的不同之处在于,桶中放入的不是请求,而是“令牌”,这个令牌就是业务处理前需要拿到的“许可证”。也就是说,当系统收到一个请求时,先要到令牌桶里面拿“令牌”,拿到令牌才能进一步处理,拿不到就要丢弃请求。

  • 设计关键点

    • 有一个处理单元往桶里面放令牌,放的速率是可以控制的。
    • 桶里面可以累积一定数量的令牌,当突发流量过来的时候,因为桶里面有累积的令牌,此时的业务处理速度会超过令牌放入的速度。
    • 如果令牌不足,即使系统有能力处理,也会丢弃请求。
  • 优点:可以动态调整处理速率,实现更加灵活。

  • 缺点

    • 突发大量流量的时候可能丢弃很多请求,因为令牌桶不能累积太多令牌。
    • 实现相对复杂。
  • 适用场景

    • 一种是需要控制访问第三方服务的速度,防止把下游压垮,例如支付宝需要控制访问银行接口的速率;
    • 一种是需要控制自己的处理速度,防止过载,例如压测结果显示系统最大处理 TPS 是 100,那么就可以用令牌桶来限制最大的处理速度。

排队

排队实际上是限流的一个变种,限流是直接拒绝用户,排队是让用户等待一段时间。优缺点:

  • 优点:排队虽然没有直接拒绝用户。
  • 缺点:
    • 用户等了很长时间后进入系统,体验并不一定比限流好
    • 由于排队需要临时缓存大量的业务请求,单个系统内部无法缓存这么多数据,一般情况下,排队需要用独立的系统去实现,例如使用 Kafka 这类消息队列来缓存用户请求。

适用场景:秒杀活动等

个人思考

服务不可能一直100%的可用性,开发运维人员要做的就是尽可能的减少损失,提前做好预案,发生故障之后,第一时间就是止损。止损往往是一个抉择的过程,要优先保证核心用户可以正常使用。在前司,平台对第三方和自己的服务提供支持,同时,这些能力也有核心能力和非核心能力,当有故障的时候,运维平台通过预案,会将非核心能力摘除(降级)处理。

此外,笔者还负责开发了一套基于 OpenResty 实现的防攻击平台,支持 IP、用户、特定字段的屏蔽、限流、导流(分流)功能。可以有效的减少服务故障或者流量异常带来的损失。由于已经在 gitchat 发布了付费文章,因此没办法公开发表了。感兴趣的同学可以 gitchat 上查看 https://gitbook.cn/gitchat/activity/5e94596fe416f80b73e3e278。

reference

  1. 《从 0 开始学架构》 https://time.geekbang.org/column/intro/100006601?tab=catalog