服务网格中的级联故障预测方法
2021-11-15李海飞徐政钧
李海飞 徐政钧
(北京邮电大学 北京100876)
0 引 言
近年来微服务架构发展迅猛,逐步成为主流部署模型。相对于单体架构而言,微服务架构有着明显的优势,单体架构庞大笨重,持续开发非常复杂[1],可扩展性差。微服务的诞生正好弥补了这一缺陷,微服务架构将一个应用拆分成多个微服务,每个微服务独立开发、独立部署、自由扩展,实现了开发和部署的敏捷性和灵活性[2]。但是随着人们对微服务系统的需求日益增加,对微服务架构的要求也逐渐增高,不仅要求微服务平台满足服务隔离及弹性伸缩等灵活性需求,还要求微服务平台保证高度的可靠性及可用性[3]。为此,就要尽可能避免系统故障发生,尤其是大规模系统故障。
为了实现更好的弹性伸缩及负载均衡,微服务架构将应用细分为多个微服务,且每个微服务可以部署多个实例,微服务间通过网络层相互通信,实现良好的性能隔离,同时也实现了故障隔离,这意味着微服务可以被设置为单独失败[4]。但并不表示微服务间不存在干扰。当系统达到一定规模时,微服务的数量变得相当庞大,微服务间或因为网络通信或因为资源共享联系在一起,形成一个复杂的关系网,这个网络中有一个节点出现问题,就会级联到其他节点,造成连锁反应,甚至可能发生雪崩,造成整个系统瘫痪[5-6]。
针对上述问题,本文提出了一套级联故障预测方法。基于当下具有很高研究热度的服务网格系统,本文在服务网格边缘嵌入故障预测组件,对系统进行实时监测[7-8]。每当有大量微服务负载进入系统时,组件被唤醒,依据当前微服务间的调用关系和资源依赖关系,建立模型,利用GRU神经网络[9-11]进行故障概率预测。
1 相关工作
随着云服务市场的发展,微服务架构势头更盛,国内外包括AWS(亚马逊)、Azure(微软)、阿里云、腾讯云、百度云、华为云等都纷纷投入服务网格的应用研究中。学术界也发表了一些关于微服务故障研究的文章,但针对服务网格的相对较少,更多的是关于分布式系统、云环境及Web服务中的故障研究。
文献[12]提出了一种自适应的基于多因素的心跳检测算法,用以进行微服务故障诊断。它结合了分布式系统中的心跳检测机制和微服务的特征,采用了三因素拉模式心跳检测机制。心跳检测是分布式系统经常采用的一种服务健康监控机制,它通过服务的心跳确认服务是否良好可运行。文献[13]提出了基于日志检测的分布式服务异常诊断模型,并提出了一种基于混合图模型的对数异常检测算法。该模型捕获服务的正常执行流程,然后观察与混合模型的偏差,由此触发异常警报。文献[14]提出了一个针对大规模时间序列异常进行自动检测的通用可扩展框架,模拟时间序列的正常行为,把与模型存在显著偏差的节点作为异常处理。文献[13-14]提出的方法都是基于单个服务进行监控并告警,忽略了服务间的关联性。
文献[15]提出了一种云环境中的故障转移方法,并提出了一种表示容错模型的数学关系。该模型依据每个虚拟节点的服务请求通过率指定容错方案。面向节点进行故障诊断可以作为分析故障的一个方面,但就微服务来说,还远远不够,还需要以服务为单位进行排查。文献[16]提出了一种基于系统参数搜索的在线检测模型,可以对向量机的参数进行优化。它通过皮尔森相关系数和主成分分析进行云系统数据提取来做出故障预测。文献[17]提出了一个分布式系统的数据跟踪监控框架,它克服了数据预先定义的限制,将系统当前操作与动态工具结合,使用户能够在系统运行时的某一节点定义任何度量标准,同时还可以根据系统中的重要事件进行数据过滤和分组。
以上文献中的方法都适用于分布式系统,主要基于单个服务或单个节点展开。而服务网格中的级联故障更多取决于服务间的依赖关系。
2 系统设计
2.1 系统架构
Web服务规模不断扩大,促使单体应用架构向分布式微服务架构转化。但传统的分布式架构应用模块多且复杂,使得系统在进行资源分配、部署、管理等方面面临着巨大的重复性工作[18]。而容器化的出现有效地解决了以上提出的问题。本文采用的Docker+ Kubernetes+ Istio的服务网格部署模式,实现了容器的自动化部署、高度的可扩展性及更高的安全性。Docker实现了轻量级的操作系统虚拟化解决方案,克服了硬件层面虚拟的诸多弊端。它通过镜像封装容器启动代码,以及应用运行的环境和依赖,可以快速地实现应用的部署和迁移,同时为应用提供了一个一致的运行环境。Kubernetes是一个容器集群的自动化部署、运维平台,针对Docker实现了更灵活的容器编排调度。它通过组件间的协同配合,实现更精准的服务部署、更有效的资源分配和更便捷的更新迭代。Istio是一个服务网格架构,它类似一个网络代理,是微服务外层的一个基础设施层,控制着微服务间的通信,并具备负载均衡、服务认证和监控等功能。可以通过Istio配置各种流量规则和安全策略,无须改动应用代码。
图1所示为本文的系统架构,在服务网格架构基础上嵌入了级联故障预测组件。每次部署一个微服务实例,都会相应启动一个代理,微服务间通过代理相互通信,它们与外界的所有交互也都由代理执行。如此即可在不需要修改应用代码的条件下,获取微服务的相关信息,并对微服务进行管理配置。Mixer是微服务与系统交互的接口,它通过代理收集微服务遥测数据,并对微服务请求进行策略检查。所有的流量策略、安全策略、故障恢复策略,以及监控策略等都写在控制平面中,控制平面会将我们的策略解析为特定的配置文件,以便代理进行解读。本文方法形成一个级联故障预测组件,它位于Mixer与控制平面之间,从Mixer获取信息进行分析预测,并将结果以策略文件的形式传递给控制平面。
图1 系统架构
2.2 系统工作流程
级联故障发生在系统高负载运行状态下,系统接收到微服务请求后,Mixer按照负载策略将请求分发给各个代理,此时Mixer只能确定每个代理所代表的服务是否可访问,但不能确定主机状态和相关联的微服务状态。当主机资源接近瓶颈,而Mixer在无法感知的情况下,会继续对微服务发出访问请求。此时节点一方面资源负载过高,可能出现微服务处理不及时,另一方面又会面临新的微服务请求,如此就会发生级联故障。
级联故障预测组件嵌入后,作为一个独立的组件,与Mixer等其他组件一样,以容器的形式部署在平台中。它并不会一直运行,它会委派Mixer在系统迎来高负载时,唤醒该组件,并且将相关微服务信息传递给它。级联故障预测组件对得到的微服务信息进行处理计算,获得针对特定微服务的级联故障发生概率,并形成一定的规避策略,传递给控制平面。控制平面对用户策略进行解析,形成特定配置文件,进而转由Mixer传达给代理执行,这样就规避了造成级联故障风险的微服务调用。
3 级联故障预测方法
3.1 级联故障预测模型
微服务系统将单个应用拆分成多个微服务,运行在不同容器中,并依据需求为同一微服务启动多个容器,并将其绑定到不同节点来实现系统的高并发和负载均衡,使微服务系统能够应对大量访问请求,同时也增加了系统复杂性。不同节点间微服务的通信及同一节点上微服务的交互增加,也使得级联故障概率大大增强[19]。为了使微服务系统能够对级联故障做出及时响应,本文从微服务运行状况和资源依赖的角度出发,设计了一套级联故障预测方法[20]。
如图2所示,首先构建微服务调用树,调用树存储着微服务间的调用关系和微服务执行的时序关系,据此可以定位到一定时间段内正在或将要执行的微服务[21-22],这些微服务是接下来进行服务健康度及负载计算的对象。
图2 级联故障预测模型
当系统产生大量服务请求时,会触发微服务健康度计算模块和微服务负载计算模块,这两个模块会向调用树模块申请获取当前启动的微服务及预设时间段内将会启动的微服务。微服务健康度通过微服务的服务特性和节点资源特性计算每一个微服务针对每一属性的健康度,以及该属性的健康标准。微服务负载计算模块会根据每一个节点每一项资源的初始负载计算该资源的最大负载,依据邻居节点计算该节点的额外负载,并计算负载代价。以上获取的数据包括微服务健康度、健康标准、节点最大负载、额外负载和负载成本,还有实时微服务相关属性将作为神经网络模型的输入,用以训练并计算获得级联故障概率。
3.2 构建调用树
微服务系统中的每个请求是由多个微服务协同完成的,微服务间的相互调用形成了一个微服务请求的执行轨迹[23],我们将此执行轨迹构建为一个微服务调用树,如图3所示。调用树中每个节点代表一个微服务,有向边表示一个服务调用过程,虚线框代表一个节点,虚线有向边表示跨节点的调用。调用树记录并跟踪微服务调用路径,以确认实时微服务的映射。
图3 微服务调用树
对于同一个节点上的微服务,可以依据系统时钟确认执行的先后顺序。但是不同节点间由于网络不稳定等因素的影响很难实现时钟的完全同步。因此本文提出在每个服务请求中插入一段监控代码,并为每个微服务生成两个标识信息元组,以此确定调用顺序:
Mt={requestID,methodID,callID,info}
info={callType,order,duration}
系统每接收一个业务请求,都会为该请求生成一个唯一标识符requestID。每一次微服务调用过程中,主调微服务都会将该requestID传递给被调微服务,被调微服务解析该字段以确定当前调用归属哪一个业务请求。methodID是微服务标识符,它用于标记微服务本身之外,还会传递给被调微服务,并作为被调微服务的callID存储,如此一来就确认微服务间的调用关系。callType用于表示微服务调用类别,包括计算密集型,内存敏感型和高存储需求型。order字段继承自主调微服务,并在主调微服务的基础上依次递增。duration字段记录了两次调用请求间的时间成本。
每一个微服务都使用本地请求作为根节点,依据methodID和callID依次匹配,构造调用子树,并依据order和duration确定调用顺序。然后对具有相同requestID的子树进行聚合,并根据自上而下的宽度优先级原理合并成完整的调用树。其算法过程如算法1所示。
算法1构建调用树
输入:Mt={requestID,methodID,callID,info},info={callType,order,duration}。
输出:微服务调用树。
1.foreachMtandinfodo
2.ifrequestID相同then
3.将requestID的微服务划分到同一组
4.endif
5.endfor
6.for每组微服务do
7.依据methodID和callID构建调用子树;
8.依据和确定调用顺序;
9.子树聚合形成调用树
10.endfor
11.return微服务调用树
3.3 微服务健康度计算
(1)
(2)
算法2微服务健康度计算
输入:微服务调用树,Ai,GCj。
1.遍历调用树定位目标微服务
2.for每组微服务do
6.endfor
3.4 微服务负载及负载成本计算
微服务间除了相互调用之外,还有一个关键联系——资源争用。部署在同一节点上的微服务共享节点资源,那么就不可避免资源争用。微服务系统的级联故障起源于微服务的依赖关系,但微服务系统具有良好的隔离性,能够实现独立失败,因而由于调用关系而引起的级联故障比重较小,特别是在高负载的状态下,资源瓶颈显得尤为突出。由于网络延迟,CPU忙碌,内存高负荷或远程调用无响应等原因,微服务可能陷入长时间等待状态,这在一般系统中可能不会造成大的危害,但是微服务系统服务体量太大,且存在故障重启机制,大量甚至越来越多的微服务运行并占用资源,进一步加大系统压力,进而极易引发级联故障。
对于级联故障的预测不能局限于单纯的微服务性能监控或者心跳检测等机制,应该从资源方向入手。因此,本文提出了一种资源负载计算策略对微服务状态进行实时监控。假设节点i上资源j的初始负载为Lij,当大量微服务请求涌入时,与节点i部署了相同微服务实例的节点,或者可以为节点i分担负载压力,或者可以成为节点i的负载来源,这样的节点我们称之为邻居节点。在邻居节点的作用下,可以计算一下每个节点的额外负载ΔLij,计算公式如下:
(3)
式中:N代表节点i的邻居节点个数;Cij代表节点i上资源j的最大负载。节点的最大负载同样受邻居节点影响,因而最大负载Cij的计算公式如下:
(4)
式中:α和β是两个容差参数;Lijmin是邻居节点中最低的初始负载。而
(5)
节点资源是有限的,因而存在一个关系:Lij+ΔLij 另一方面,如果不考虑代价,节点资源容量不受限制,那么可以说级联故障完全可以避免。但是节点资源不能无限扩张,其中要花费的代价是需要考虑的问题。代价来源于节点资源负载,并受邻居节点影响。本文把Cij重新表示为Cij=λijLij,把资源i的负载代价定义为ecj: (6) 式中:i代表邻居节点。 (7) 根据式(5),又可以将其改写为: (8) 算法3微服务负载及负载成本计算 输入:Lij,α,β。 输出:Cij,ΔLij,ecj。 1.foreachSido 2.利用式(4)计算Cij; 3.利用式(3)计算ΔLij; 4.利用式(8)计算ecj 5.endfor 6.returnCij,ΔLij,ecj 门控循环单元(GRU)是长短期记忆网络(LSTM)的一种变体,LSTM实现了递归神经网络(RNN)基于时间序列的特性的同时,又克服了RNN梯度消失的问题,即随着时间的递进传播梯度逐渐下降,直至停止学习。对于这个问题,LSTM采取门控结构的方式进行解决。通过遗忘门、输入门、输出门控制信息的丢弃、保留、更新和传递,实现了有用信息的保留和无关信息的丢弃[25]。GRU是LSTM变体中最有效的一个,它只设置了重置门和更新门,如图4所示。重置门决定是否丢弃之前的状态,更新门决定是否将之前的状态更新为新的状态。GRU在保留了与LSTM相当效果的前提下实现了更高的效率。 图4 GRU神经网络结构图 本文采用两层隐藏层的GRU神经网络,即输入层、GRU隐藏层、GRU隐藏层、输出层的结构。输入为前文总结计算得到的数据,包括微服务的可用性、吞吐量、时延、故障概率、I/O频率、CPU使用率、内存使用率、磁盘使用率、网络访问频率、微服务健康度、健康标准、微服务资源最大负载、额外负载和负载代价14个特征值;输出为级联故障的发生概率。隐藏层包含30个神经元,为全连接层,输入层与隐藏层间采用ReLU为激活函数,两层隐藏层之间采用softplus为激活函数。 同时,还需要一个损失函数来估计预测值与实际值之间的差异。由于均方误差(MSE)对异常值的敏感性,将MSE作为本文预测方法的损失函数。关于优化器方面,选择自适应矩估计(Adam)优化器。算法过程如算法4所示。 算法4微服务级联故障故障概率计算 输出:故障概率。 1.在神经网络输入层与隐藏层间嵌入ReLU函数 2.在两层隐藏层之间嵌入softplus函数 3.利用Adma对模型训练进行优化 4.利用MSE计算训练误差 5.foreachSido 6.将属性带入神经网络进行计算 7.endfor 8.return故障概率 在基于Docker+ Kubernetes+ Istio的网格服务平台基础上,运行本文的级联故障预测组件,模拟CPU、内存、磁盘和网络四个资源不足的应用场景,并对比贝叶斯网络和基于马尔可夫的动态故障树两种方法,来分析本文方法的优势及其效率。 4.1.1硬件环境 本文系统建立在拥有12个物理服务器节点的集群之上,其中两个主节点用于集群管理和集群调度,是系统的入口节点,余下的11个节点为工作节点。这些节点的配置信息如表1所示。 表1 服务器配置信息 4.1.2软件环境 本文系统部署在64位的Ubuntu16.04操作系统之上,采用的软件环境如表2所示。 表2 软件版本信息 4.1.3微服务实例 在服务网格平台上部署了72个具有代表性的微服务,这些微服务包括计算密集型服务、数据密集型和通信密集型服务,模拟不同的级联故障问题场景,具体微服务类型如表3所示。 表3 微服务实例类型 神经网络训练需要在大量有代表性有意义的数据的基础上进行,实验需要的数据包括系统正常运行时的微服务状态信息以及级联故障下的微服务状态信息。为了获取足够的数据,采用故障注入在系统中设置障碍模拟级联故障。 本文构建了一个故障注入控制器(FIC)。FIC是介于微服务请求者和微服务响应者之间的一个故障注入代理,它通过控制微服务之间的请求和响应消息来模拟和注入故障。FIC由拦截控制器(IC)和触发控制器(TC)两部分组成。在IC中预设了要拦截的目标微服务,IC会进行实时拦截,判定该请求是否针对目标微服务,如图5所示。如果是,则将该请求消息传递给TC,TC对该请求进行更改,更改策略包括扩大请求负载、延迟请求、篡改数据包及丢弃数据包。TC将更改后的数据包回送给IC,IC将该请求发出。 图5 故障注入架构 本次实验设置了三组故障场景,如表4所示。 表4 故障注入场景 续表4 在连续的时间内周期性地向系统中注入级联故障,使系统在正常运行与故障运行中交替进行,以一次交替为周期,从每个周期中抽取85%为训练集,15%为测试集。分别从三个级联故障场景中收集了3 000条数据,共9 000条数据,7 650条为训练集,1 350条为测试集,其中均交叉分布着故障数据和非故障数据。 用训练好的模型对测试集进行测试,并计算了模型的精确率和MSE,获得的结果如表5所示。 表5 模型精确率和MSE 模型对级联故障预测的精确率在85%以上,最高可达91.66%,MSE低于0.02,由此可知本文模型训练是有效的,可以应用到级联故障预测中。 级联故障的发生有其边界性,当系统负载达到某一程度,才可能引发故障。因此实验的第一步是找到一个故障高发的负载段,然后在此负载下,从前面设计的三个场景入手进行对比实验,对本文故障预测方法进行评估。 4.4.1动态负载下的故障频率 首先测试了两个场景下随着负载变化级联故障发生概率,分别是单一资源争用微服务和多资源竞争微服务。 观察随着单一资源争用微服务负载增加,级联故障发生状况。如图6所示,在微服务负载低于300时,没有级联故障发生。只有当微服务负载突破300后,才开始出现级联故障,并且随着单个微服务负载的增加,级联故障发生概率开始呈上升趋势,且在负载达到650左右时,故障概率稳定在0.36左右。 图6 故障概率与单一资源争用微服务负载关系 当存在多个资源竞争的微服务负载动态变化时,级联故障的发生状况又不一样了。如图7所示,当微服务负载超过250时,系统就会出现一定程度的级联故障,相比图6出现得更早。也从一定程度上说明了微服务间的资源争用关系越复杂越容易导致级联故障的发生。另一方面,当负载超过500时,级联故障发生概率就达到了0.37左右,即接近单一资源争用微服务动态负载情况下的极限值,且在负载达到600左右时,故障概率稳定在0.42左右。 图7 故障概率与多资源争用微服务负载的关系 综合以上两个实验数据,可以更加确定微服务间资源争用导致级联故障这一观点的正确性,同时也显示具有多个资源争用关系的微服务实例,更易引起级联故障。 4.4.2故障预测评估 基于前文提出的三个场景,设置了三组对比实验,对比本文方法与贝叶斯网络和基于马尔可夫链的动态故障树两种方法的故障预测效果。三组实验数据同时从三个故障场景中采集,其中贝叶斯网络输入为微服务调用树和微服务健康指标(包括可用性、吞吐量、时延、故障概率、I/O频率、CPU使用率、内存使用率、磁盘使用率和网络访问频率),以存在调用依赖的微服务和9个微服务健康指标为故障信息源节点构建贝叶斯网络。基于马尔可夫链的故障树输入与贝叶斯网络相同,它在微服务调用树的基础上进行故障树的转换,将存在依赖的微服务的状态作为故障树的节点,状态的判定以9个微服务健康指标为依据。 1) 场景一。图8为基于场景一的三种方法级联故障预测准确率随微服务负载变化趋势。通过之前的实验可以知道,当微服务负载达到400时,系统才能显现明显的级联故障,因此我们的实验从负载400开始。当我们向系统中大量注入计算密集和通信密集型微服务时,本文方法一开始准确率较低,低于贝叶斯方法。但随着负载增加,方法预测的准确率逐步上升,从负载600后优势逐渐明显,在负载1 000时准确率达到90%。而基于贝叶斯网络和基于故障树的级联故障预测准确率上升不明显,且不稳定。 图8 基于场景一的级联故障预测对比 2) 场景二。图9是基于场景二的级联故障下的对比实验正确率曲线,与场景一相似,本文方法在负载低于600时,准确率较低,甚至低于余下两种方法。但随着负载增加,本文方法准确率增势明显且更稳定。 图9 基于场景二的级联故障预测对比 3) 场景三。图10是场景三下的级联故障预测的对比情况。可以看出,相比于场景一和场景二的曲线,场景三在负载低于600时,本文方法准确率略高,增势也很明显,准确率最高可达93%。余下两种方法的准确率仍旧相对平稳,且存在一定波动。 图10 基于场景三的级联故障预测对比 结合以上所有实验,本文方法在微服务系统中能够有效地预测出级联故障,且相比于贝叶斯网络和基于马尔可夫链的动态故障树,本文方法在高负载时具有明显优势,准确率更高而且更稳定。但是在负载较低时,本文方法表现不如另外两种方法,分析认为,GRU神经网络更适合处理大量数据,它善于从海量数据中搜索规律,而对于小量数据的分析会存在一定偏差。 本文研究重点是服务网格中级联故障的预测,为此提出了一种结合微服务性能、微服务依赖和微服务资源争用的级联故障建模方法,并利用能够基于时间序列进行学习的GRU神经网络算法对级联故障进行预测。搭建了实验平台,并在平台上进行了对比实验,验证了本文方法的有效性和稳定性。实验表明在高负载时,本文方法与另外两种方法相比有明显的优势,而且正确率可以达到90%以上。实验中发现,对于不同的因素,级联故障的敏感度不同,因此未来可能会针对不同资源角度更加细致地研究微服务的故障因素以及针对不同故障可以采取的规避措施。3.5 基于GRU的微服务级联故障预测
4 实 验
4.1 实验环境
4.2 故障注入
4.3 GRU神经网络模型训练
4.4 实验结果
5 结 语