前言

集群中通常一个服务有多个服务提供者。其中部分服务提供者可能由于网络,配置,长时间 fullgc ,线程池满,硬件故障等导致长连接还存活但是程序已经无法正常响应。单机故障剔除功能会将这部分异常的服务提供者进行降级,使得客户端的请求更多地指向健康节点。当异常节点的表现正常后,单机故障剔除功能会对该节点进行恢复,使得客户端请求逐渐将流量分发到该节点。单机故障剔除功能解决了服务故障持续影响业务的问题,避免了雪崩效应。可以减少人工干预需要的较长的响应时间,提高系统可用率。

这种功能叫做自动故障剔除。

而 SOFA 是怎么实现的呢?

如何使用

自动故障剔除的运行机制:

  • 单机故障剔除会统计一个时间窗口内的调用次数和异常次数,并计算每个服务对应ip的异常率和该服务的平均异常率。
  • 当达到ip异常率大于服务平均异常率到一定比例时,会对该服务+ip的维度进行权重降级。
  • 如果该服务+ip维度的权重并没有降为0,那么当该服务+ip维度的调用情况正常时,则会对其进行权重恢复。
  • 整个计算和调控过程异步进行,不会阻塞调用。

根据官方例子,使用方式如下:

FaultToleranceConfig faultToleranceConfig = new FaultToleranceConfig();
// 是否开启调控.
faultToleranceConfig.setRegulationEffective(true);
// 是否进行降级
faultToleranceConfig.setDegradeEffective(true);
// 时间窗口大小
faultToleranceConfig.setTimeWindow(20);
// 每次权重降级的比率
faultToleranceConfig.setWeightDegradeRate(0.5); FaultToleranceConfigManager.putAppConfig("appName", faultToleranceConfig);

如上,该应用会在打开了单机故障剔除开关,每20s的时间窗口进行一次异常情况的计算,如果某个服务+ip的调用维度被判定为故障节点,则会进行将该服务+ip的权重降级为0.5倍。

可以看到,这个功能面向框架用户的 API 就是这个 FaultToleranceConfig 类,即故障容错配置。

用户可以配置某个服务是否开启调控,是否进行降级,实际窗口大小(秒),每次权重降级的比率。

那么,SOFA 是如何实现的呢?

源码分析

首先说明,由于这个功能相比较之前的功能,代码要复杂一些,因此,本次分析主要分析主流程,不会面面俱到。关于详细的代码细节,我们将在后面的源码分析中详细解释。

1. 初始化

SOFA 对于该功能的设计使用了 Modle 的方式,简单来说,就是一个可扩展,可热插拔的中间件。SOFA 的中间件和 RPC 框架都是通过 Modle 的方式来实现的。

如何实现呢?

RPC 初始化的时候,会调用 RpcRuntimeContext 类的静态块,该静态块内部会初始化其他模块,即调用 ModuleFactory.installModules() 方法。该方法会加载配置文件中所有 Module 接口的 SPI 文件。然后循环调用 install 方法,即初始化。

目前的源码中只有一个 Module 的实现类,即 FaultToleranceModule 。故障容错模块。该类的 install 方法 会创建一个订阅者,在事件总线中订阅两个事件:ClientSyncReceiveEvent 和 ClientAsyncReceiveEvent。然后创建一个事件窗口调控器。并初始化该调控器。

TimeWindowRegulator 是故障调控的核心类,内部包含以下属性:

  1. measureCounter 度量计数器,每执行一次度量任务,就加一。
  2. measureScheduler 度量定时任务线程池,使用 RATE 模式,即从任务开始时间开始计算。如果任务的时间超过了间隔时间,间隔时间将失效。这里的间隔时间是 1 秒。
  3. regulationExecutor 计算线程池,即在定时任务线程池中提交计算任务给这个线程池,以实现快速返回。该线程池核心大小为 2.
  4. measureModels 度量模型,一个存放 MeasureModel 对象的 List。
  5. measureStrategy 计算策略(根据度量结果,判断是否需要执行降级或者恢复)
  6. weightDegradeStrategy 降级策略: 调整权重
  7. logDegradeStrategy 降级策略: 只打印日志
  8. recoverStrategy 恢复策略:调整权重
  9. listener 调用统计监听器,当发生事件时,会调用监听器的方法。

属性很多,暂时不详细解释。说主流程。

该类的 intit 方法是初始化这些属性,并注册监听器。注册方式是调用 InvocationStatFactory.addListener(listener) 方法。而这个监听器是该类的内部类 —— TimeWindowRegulatorListener。

好,初始化完毕之后,开始说流程。

当 RPC 框架调用了发送消息的方法,并返回(或者失败)后,就会向事件总线 EventBus 丢一个事件。例如 ClientSyncReceiveEvent 事件,该事件需要包含以下属性:

    private final ConsumerConfig consumerConfig;
private final ProviderInfo providerInfo;
private final SofaRequest request;
private final SofaResponse response;
private final Throwable throwable;

基本包含了此次调用的所有信息。

此时,就会触发订阅者的 onEvent 方法。即 FaultToleranceSubscriber 的 onEvent 方法。该方法会判断,如果用户启用了自动故障剔除功能,则根据 consumerConfig 和 providerInfo 得到一个调用统计器对象,并对调用次数加一。

关键的方法在 onEvent 中调用的 InvocationStatFactory.getInvocationStat(consumerConfig, providerInfo); 该方法会创建一个 InvocationStat 调用统计器对象,放入 Map 中,而对应的 key 则是根据上面两个参数生成的 InvocationStatDimension 统计维度对象。

创建完 InvocationStat 调用统计器对象后,调用所有监听器的 onAddInvocationStat 方法,表示,我添加了一个监听器了,你可以做点什么了。还记得 TimeWindowRegulator 初始化的时候会添加一个监听器吗。就是这里的监听器。

内部类 TimeWindowRegulatorListener 的方法逻辑如下:

调用度量策略对象的 buildMeasureModel 方法,传入调用统计器。返回一个度量模型。然后,将这个模型添加进 List(measureModels 属性) 中。并调用外部类的 startRegulate 方法,开始进行调控。

startRegulate 方法就是启动了定时任务,使用了一个原子 boolean 变量进行状态判断。

定时任务的内容是什么呢?

答:运行 MeasureRunnable 任务。

该任务首先会对度量计数器加一。然后循环 List 中的 MeasureModel 度量模型。并判断该 MeasureModel 是否到达了用户设定的时间窗口(取于用户设置的时间大小)。

如果到达了时间窗口,并调用 measureStrategy 度量策略对象(serviceHorizontal)的 measure 方法,参数则是度量模型,返回一个度量的结果对象 —— MeasureResult。

得到这个对象后,向计算线程池提交一个 RegulationRunnable 任务,该任务内容如下:

拿到刚刚传入的度量结果拿到度量结果详情的集合 —— measureResultDetails,循环这些集合,并执行 doRegulate 方法,进行调控。

该方法就是真正的对服务进行调控的方法了。首先,一个服务有 3 个状态:健康,异常,忽略。状态来自于刚刚的 measure 方法。

如果用户设置了可以降级的话,则判断服务的度量状态,如果异常了且不超过一个服务的最大调控 IP 数,则执行权重降级逻辑。反之,打印日志。

如果度量状态是正常的,则执行权重恢复,并从降级 IP 列表中删除。

如果用户没有设置可以降级,且度量状态是异常,那么,执行日志降级。即对异常 IP 记性异常信息的日志打印。

当对权重进行降级之后,能够被负载均衡击中的几率就会对应的小很多。甚至了无法击中。

以上,就是 SOFA 自动故障剔除功能的基本实现流程。

总结

还是那句话,由于这个功能比较繁杂,限于篇幅,今天看的是总流程,总结一下这个流程。

RPC 框架在启动的时候,会自动加载故障容错模块,并监听客户端发送事件。同时会初始化故障容错相关的类和监听器。

当发生订阅事件的时候,会调用 onEvent 方法,进而调用 TimeWindowRegulatorListener 的监听器方法。该方法会将度量模型添加进 List 中。

定时任务每隔一秒会调度 MeasureRunnable 任务,内容是根据用户设置的时间窗口处理 List 中的调度模型。

定时任务会向计算任务线程池提交一个 RegulationRunnable 任务。用于处理度量结果中的数据。该任务会循环度量结果的所有度量结果详情,并调用 doRegulate 方法进行调控。

最后,doRegulate 方法则是根据 度量结果详情 的状态判断是否应该对服务 + IP 进行权重降级或者权重恢复,再或者是打印日志 —— 这依据用户设置。

以上就是 SOFA 自动故障剔除的基本流程。后面我们会详细分析自动故障剔除的细节代码。敬请期待。

SOFA 源码分析 — 自动故障剔除的更多相关文章

  1. SOFA 源码分析 —— 服务引用过程

    前言 在前面的 SOFA 源码分析 -- 服务发布过程 文章中,我们分析了 SOFA 的服务发布过程,一个完整的 RPC 除了发布服务,当然还需要引用服务. So,今天就一起来看看 SOFA 是如何引 ...

  2. SOFA 源码分析 —— 服务发布过程

    前言 SOFA 包含了 RPC 框架,底层通信框架是 bolt ,基于 Netty 4,今天将通过 SOFA-RPC 源码中的例子,看看他是如何发布一个服务的. 示例代码 下面的代码在 com.ali ...

  3. SOFA 源码分析 — 调用方式

    前言 SOFARPC 提供了多种调用方式满足不同的场景. 例如,同步阻塞调用:异步 future 调用,Callback 回调调用,Oneway 调用. 每种调用模式都有对应的场景.类似于单进程中的调 ...

  4. SOFA 源码分析 — 负载均衡和一致性 Hash

    前言 SOFA 内置负载均衡,支持 5 种负载均衡算法,随机(默认算法),本地优先,轮询算法,一致性 hash,按权重负载轮询(不推荐,已被标注废弃). 一起看看他们的实现(重点还是一致性 hash) ...

  5. SOFA 源码分析 — 预热权重

    前言 SOFA-RPC 支持根据权重对服务进行预热功能,具体地址:预热权重. 引用官方文档: 预热权重功能让客户端机器能够根据服务端的相应权重进行流量的分发.该功能也常被用于集群内少数机器的启动场景. ...

  6. SOFA 源码分析— 事件总线

    前言 大部分框架都是事件订阅功能,即观察者模式,或者叫事件机制.通过订阅某个事件,当触发事件时,回调某个方法.该功能非常的好用,而 SOFA 内部也设计了这个功能,并且内部大量使用了该功能.来看看是如 ...

  7. SOFA 源码分析 — 自定义线程池原理

    前言 在 SOFA-RPC 的官方介绍里,介绍了自定义线程池,可以为指定服务设置一个独立的业务线程池,和 SOFARPC 自身的业务线程池是隔离的.多个服务可以共用一个独立的线程池. API使用方式如 ...

  8. SOFA 源码分析 — 链路数据透传

    前言 SOFA-RPC 支持数据链路透传功能,官方解释: 链路数据透传功能支持应用向调用上下文中存放数据,达到整个链路上的应用都可以操作该数据. 使用方式如下,可分别向链路的 request 和 re ...

  9. SOFA 源码分析 —— 过滤器设计

    前言 通常 Web 服务器在处理请求时,都会使用过滤器模式,无论是 Tomcat ,还是 Netty,过滤器的好处是能够将处理的流程进行分离和解耦,比如一个 Http 请求进入服务器,可能需要解析 h ...

随机推荐

  1. tar 压缩和解压缩使用笔记

    tar 压缩和解压缩使用笔记 1 文件 1.1 打包 1.1 压缩 $ tar czf myfile.txt.tar.gz ./myfile.txt 1.2 解压缩 解压缩到目录: $ mkdir o ...

  2. linux下使用异步通知

    阻塞式I/O是一直等待直到设备可以访问,非阻塞式I/O是定期轮询设备是否可以访问. 异步通知则是当设备可以访问时才主动通知应用程序,有点像设备的硬中断. 并不是所有的设备都支持异步通知,应用程序通常假 ...

  3. Socket层实现系列 — I/O事件及其处理函数

    主要内容:Socket I/O事件的定义.I/O处理函数的实现. 内核版本:3.15.2 我的博客:http://blog.csdn.net/zhangskd I/O事件定义 sock中定义了几个I/ ...

  4. MySQL学习笔记_6_SQL语言的设计与编写(下)

    SQL语言的设计与编写(下) --SELECT查询精讲 概要: SELECT[ALL | DISTINCT] #distinct 明显的,清楚的,有区别的 {*|table.*|[table.]fie ...

  5. 【一天一道LeetCode】#53. Maximum Subarray

    一天一道LeetCode系列 (一)题目 Find the contiguous subarray within an array (containing at least one number) w ...

  6. (九)UIScrollView和PageControl的分页

    涉及到内容的滚动与拖拽,使用UIScrllView. 对于滚动的多张图片,由于超出屏幕,应该使用代码添加代码. 添加的细节是:图片的宽高即为滚动视图的宽高,图片的y=0,x=图片的序号乘以图片的宽度. ...

  7. 再回首UML之下篇

    接着我们上篇博客再回首UML之上篇说,在类图中有四种关系,关联.依赖.泛化.实现,接下来,我们来看看依赖,依赖--描述的是一种使用关系,她说明一个事物的规格说明的变化可能影响到他使用的另一个事物,反之 ...

  8. Cocos2D遍历场景图(Scene Graph)

    另一个Cocos2D有用的调试特性是打印出递归的打印出节点的孩子们. 你可以添加以下一行到MainScene或GameScene的didLoadFromCCB的方法中: [self.scene wal ...

  9. scrapy模拟登录微博

    http://blog.csdn.net/pipisorry/article/details/47008981 这篇文章是介绍使用scrapy模拟登录微博,并爬取微博相关内容.关于登录流程为嘛如此设置 ...

  10. 深入认识AsyncTask

    1.概述 在android开发中是采用单线程模型,主线程通常称为UI线程,由于UI线程的操作不是线程安全的,因此android规定有关更新界面的操作必须在主线程中进行,其他线程直接报错. 如果我们把所 ...