万字长文聊聊微服务治理能力

万字长文聊聊微服务治理能力

  • 1、说在前面
  • 2、流量治理
    • 2.1 限流
      • 2.1.1本地限流
        • 典型限流算法
        • 限流算法实现
      • 2.1.2 集群限流
    • 2.2 服务容错
      • 2.2.1熔断
      • 2.2.2 超时重试
        • 单点重试限制
        • 单链路重试限制
    • 2.3 负载均衡
      • 2.3.1 负载均衡
      • 2.3.2 健康检查
        • 主动健康检测
        • 被动健康检测
    • 2.4 流量染色
    • 2.5 故障注入
    • 2.6 流量镜像

1、说在前面

传统微服务通常是基于框架提供一系列服务治理能力,例如 Spring-Cloud 或者 Dubbo,而下一代微服务架构服务网格更是抽象一系列流量治理能力,其中包括流量限流、服务容错、负载均衡、流量染色、故障注入、流量镜像等。

2、流量治理

2.1 限流

任何一个系统的运算、存储、网络资源都不可能是无限大,当系统资源不足以支撑外部超过预期的突发流量时,便应该有所取舍,建立面对超额流量的自我保护机制,也就是需采用限流措施。

2.1.1本地限流

典型限流算法

典型的限流算法包括流量计数器、滑动时间窗口、漏桶模式、令牌桶模式。
1、计数器模式:通过维护一个单位时间内的计数值,每当一个请求通过时,就将计数值加1,当计数值超过阈值时,就进入限流处理流程。
2、滑动窗口模式:将时间窗口在进行细化,分为N个小窗口,窗口以小窗口为最小滑动单位,这样就可以避免在两个时间窗口之间产生毛刺现象。(当然在小窗口之间仍然会产生毛刺)
3、漏桶模式:漏桶可以看作是一个带有常量服务时间的单服务器队列,如果漏桶(包缓存)溢出,那么请求会被丢弃。 在实现中,漏桶算法可以控制端口的流量输出速率,平滑突发请求流量,实现流量整形,提供一个稳定的输出流量。
万字长文聊聊微服务治理能力_第1张图片
4、牌桶模式:令牌桶模式整体可以分为3个阶段

  • 令牌的产生:以r/s的速率将token放入bucket中,如果bucket中token数已到达bucket的容量b,则丢弃token
  • 获取令牌:在请求处理前从bucket中获取(移除)一个令牌,只有获取到令牌才进行处理
  • 无法获取令牌:当bucket中令牌不够,请求进入限流处理流程(阻塞等待或者直接返回失败,等待应该计算好超时时间)

万字长文聊聊微服务治理能力_第2张图片
针对上述限流模式的特点以及实现总结如下:

限流模式 限制突发流量 平滑限流 典型实现
计数器模式
滑动窗口模式 相对实现。滑动窗口的格子划分的越多,那么滑动窗口的滚动就越平滑 TCP滑动窗口限流、Sentinel 限流
漏桶模式 Nginx 限流
计数器模式 Guava 限流

限流算法实现

下面以限流最常用的两种模式令牌桶和滑动窗口为例,分别阐述下对应的设计。

令牌桶实现的核心思想是根据请求数、当前存储令牌数以及令牌桶流入速率,计算下次请求可以获得令牌的时间,在下次请求进来时基于计算结果判断是否需要等待,以保障不会超过令牌桶限制。

万字长文聊聊微服务治理能力_第3张图片
当然在计算下次可获得令牌时间时,可以采用不同策略进行计算,例如可以加上流量预热功能,使流量可以平稳增加至预设流控速度。

滑动窗口模式算法相对直接,算法通过对于当前时间进行规整到某个计算窗口内,如在窗口内的流量次数超过阈值,则进行限流措施。下图为基于Sentinel限流的滑动窗口示意图。

万字长文聊聊微服务治理能力_第4张图片
万字长文聊聊微服务治理能力_第5张图片
万字长文聊聊微服务治理能力_第6张图片
在实现上述限流算法模型后,可提供更上层贴合业务的AOP层,通过注解解耦业务逻辑与限流模块本身,同时保留SDK能力,为特殊场景例如参数限流等业务场景提供支撑。

2.1.2 集群限流

上述限流算法模式仅是基于单进程内的限流手段,针对分布式系统,可提供针对集群的限流手段。主要的实现方式如下:

  • 基于Redis的分布式限流。该限流方式在Spring-Cloud-Gateway工程中作为默认的集群限流模块。基本原理是基于redis的单线程和lua脚本的原子特性,实现了令牌桶等限流算法。
  • 基于分布式发票服务分布式限流。该限流方式在Sentinel工程中作为默认的集群限流模块。基本原理是,集群本身通过建设发票服务,由各个的限流节点监听配置变更完成各自节点的限流。

万字长文聊聊微服务治理能力_第7张图片
整体的配置流程为:
1、用户更新Sentinel Dashboard 以更新限流规则
2、Sentinel 将配置更新至远程配置中心,例如 ZooKeeper 或者 Apollo
3、客户端通过注册的监听器感知到配置的变化,并更新限流策略

2.2 服务容错

在一个大的服务集群内,程序可能出现崩溃、节点可能宕机、网络可能中断,种种事情都要求分布式系统要有容错能力。常见的容错策略包括如下:

  • 故障转移(failover):在调用服务器出现故障时,系统不会立即向调用者返回失败结果,而是自动切换到其他服务副本,通过重试提高整体服务可用性。故障转移前提是服务需服务本身提供幂等性。
  • 快速失败(failfast):针对的非幂等服务,对于重复调用产生的将产生脏数据。此时最恰当的方案是尽快让服务报错。
  • 安全失败(failsafe):在调用链路中通常有主路和旁路之分。针对旁路逻辑,可以将调用失败记录在日志中,保障旁路逻辑不影响正常调用。
  • 故障恢复(failback):当服务调用出错后,将调用失败信息存入消息队列中,有系统自动开始异步的重试调用。

2.2.1熔断

由于某些原因使得服务出现了过载现象,为防止造成整个系统故障,从而采用的一种保护措施,所以很多地方把熔断亦称为过载保护。很多时候刚开始可能只是系统出现了局部的、小规模的故障,然而由于种种原因,故障影响的范围越来越大,最终导致了全局性的后果。

解决上述问题常见技术方案是通过断路器机制,实现服务在对下游服务失败比例达到预设值时会进行熔断降级。下图是断路器模式的状态图。

万字长文聊聊微服务治理能力_第8张图片
在熔断中通常有三种状态:

  • close:熔断器默认为关闭状态(初始状态)
  • open: 受下游服务请求异常,达到阈值后,启动熔断器,此时熔断器会打开。
  • half-open: 半熔断状态,此时允许一定量的服务请求通过,如果通过的请求都调用成功,则判定服务恢复,关闭断路器

三种状态的转换逻辑如下:

  • closed->open:正常情况下熔断器为closed状态,当访问同一个接口次数超过设定阈值并且错误比例超过设置错误阈值时候,就会打开熔断机制,这时候熔断器状态从closed->open。
  • open->half-open:当服务接口对应的熔断器状态为open状态时候,所有服务调用方调用该服务方法时候都是执行本地降级方法。熔断器状态在open状态开始的一个时间窗口内,调用该服务接口时候都委托服务降级方法进行执行。如果时间超过了时间窗口,则把熔断状态从open->half-open,这时候服务调用方调用服务接口时候,就可以发起远程调用而不再使用本地降级接口,如果发起远程调用还是失败,则重新设置熔断器状态为open状态,从新记录时间窗口开始时间。
  • half-open->closed: 当熔断器状态为half-open,这时候服务调用方调用服务接口时候,就可以发起远程调用而不再使用本地降级接口,如果发起远程调用成功,则重新设置熔断器状态为closed状态。
    万字长文聊聊微服务治理能力_第9张图片
    中间桥接各个状态变化是工程上通常是用滑动窗口来实现,在滑动窗口内分别统计慢调用比例,异常比例以及异常数,然后根据各窗口内的统计指标进行判断是否更新状态。

2.2.2 超时重试

从微服务架构这个宏观角度来说,超时是为了确保服务链路的稳定性,提供了一种框架级的容错能力。在同步场景下,配置合理的超时时间尤其关键,但鉴于交易的主要框架均采用异步方案,所以就不展开。

重试是在幂等前提下故障场景的是重要的故障恢复手段,框架层面需提供重试能力以增强整体服务鲁棒性。但重试本身也存在着一定风险,主要体现在重试具有链路放大效果,不合理的重试将导致重试风暴产生,约在上游的重试次数越多,分布式系统的整体负担越重。

常见的重试策略分别如下:

  • 线性退避:每次等待固定时间后进行重试
  • 随机退避:在一定范围内随机等待一个时间后重试
  • 指数退避:连续重试后,每次等待时间都是前一次的倍数

为避免重试风暴的产生,可以通过两个维度进行限制,分别是单点重试限制和链路重试限制。

单点重试限制

通过滑动窗口内统计请求成功与失败的数量,当失败率高于设定阈值(例如10%)则不重试;在新的滑动窗口统计范围内,如错误率降至阈值以下,则允许重试操作。

万字长文聊聊微服务治理能力_第10张图片

单链路重试限制

链路层面的防重试风暴的核心是限制每层都发生重试,理想情况下只有最下一层发生重试。Google SRE 中指出了 Google 内部使用特殊错误码的方式来实现:

  • 统一约定一个特殊的 status code ,它表示:调用失败,但别重试。
  • 任何一级重试失败后,生成该 status code 并返回给上层。
  • 上层收到该 status code 后停止对这个下游的重试,并将错误码再传给自己的上层。
    万字长文聊聊微服务治理能力_第11张图片
    在链路中,推进每层都接入重试组件,这样每一层都可以通过识别这个标志位来停止重试,并逐层往上传递,上层也都停止重试,做到链路层面的防护,达到“只有最靠近错误发生的那一层才重试”的效果。

2.3 负载均衡

2.3.1 负载均衡

负载均衡(Load Balance,简称 LB)是高并发、高可用系统必不可少的关键组件,目标是 尽力将网络流量平均分发到多个服务器上,以提高系统整体的响应速度和可用性。

常见的负载策略包括轮询均衡(Round Robin)、权重轮询均衡(Weighted Round Robin)、随机均衡(Random)、权重随机均衡(Weighted Random)、一致性哈希均衡(Consistency Hash)、最小连接数均衡(Least Connection)。

在RPC框架内,在客户端根据注册中心选择实际调用节点时,负载均衡策略具有着重要作用,但鉴于交易主要使用的是组播方式,并且AMI属于一主多备架构无需负载均衡,所以就不在此展开。

2.3.2 健康检查

健康检查机制的引入,有效提高了业务服务的可用性,而健康检查本身也可以分为主动健康检查和被动健康检查。主动健康检查指的是框架层面主动进行tcp或者http层面的探测;被动健康检查概念主要参考了Nginx和Envoy的设计理念,被动检测通过异常点检测来实现,异常点包括连续的请求返回错误(http场景下例如连续的5xx)以及连续的本地错误(例如大量请求超时或者tcp重置)。

主动健康检测

主动健康检查的设计逻辑较为简单,即通过周期性的tcp/http/其他形式的方式对目标集群进行检测。为了避免频繁的健康检查失败引起的切换对系统可用性的冲击,健康检查只有在健康检查时间窗内连续多次检查成功或失败后,才会进行状态切换。同时,对负载敏感性高,建议采用降低健康检查频率、增大健康检查间隔的方式,保证健康探测检测对于系统的影响。

被动健康检测

被动健康检查在实现层面上需要两个重要参数max_fails 和 fail_timeout,fail_timeout 定义了健康检查的执行时长,而 max_fails 表示服务不可用的最大尝试次数。当一定时间内(此时间由 fail_timeout 定义),发生了一定次数的服务器不响应的事件(此次数由 max_fails 定义),那么 就会将该节点标识为不可用,并且在一定时间内禁止请求分发到该服务器。

2.4 流量染色

流量染色是一种路由筛选功能。当流量带有特定的标记(颜色)时,会从目标服务的虚拟环境列表中,选取一个与流量标记最匹配的环境,并将该流量转发到这个虚拟环境中。利用这种流量标识以及路由的能力,可以在灰度发布、全链路压测、流量路由优化(例如同机房流量优先等)等场景下进行落地。

服务提供方会在部署态标识自身的染色标识,并将相关信息同步到服务注册中心,实现了服务的标注;针对于南北向,网关会从请求头内获取流量染色标识,并根据注册中心的服务标识信息选择对应服务发起调用;针对东西向,框架会从请求头内获取流量染色标识,并根据注册中心的服务标识信息选择对应服务发起调用。

在这个过程中,对具有流量标识的服务不存在的场景下,需建立兜底机制即在无流量标识路由中进行流量穿梭。

2.5 故障注入

故障注入是一种软件测试方式,通过在代码中引入故障来发现系统软件中隐藏的问题,常见的框架可以提供的故障是延迟注入和中断注入。

  • 中断异常:注入运行时异常,中断请求并返回既定的错误状态码。
  • 超时异常:满足条件的请求,会增加响应时间,请求正常调用。

实现原理层面是通过框架提供的Filter或者AOP机制,在用户配置的服务方法上增加故障。

2.6 流量镜像

流量镜像是将流量复制到目标服务进行测试,大致的实现思路是将流量进行拷贝,并异步方式进行镜像。大致可通过如下方式进行实现,在流量入口进行流量请求头和请求体进行复制,并提交到队列中,由异步线程进行流量的镜像。

你可能感兴趣的:(云原生,微服务,云原生)