SOFA 源码分析 — 自动故障剔除

Wesley13
• 阅读 663

SOFA 源码分析 — 自动故障剔除

前言

集群中通常一个服务有多个服务提供者。其中部分服务提供者可能由于网络,配置,长时间 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 自动故障剔除的基本流程。后面我们会详细分析自动故障剔除的细节代码。敬请期待。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
5个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
11个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这