1. 背景java
微服务化以后,系统分布式部署,传统单个流程的本地API调用被拆分红多个微服务之间的跨网络调用,因为引入了网络通讯、序列化和反序列化等操做,系统发生故障的几率提升了不少。算法
微服务故障,有些是因为业务自身设计或者编码不当致使,有些是底层的微服务化框架容错能力不足致使。在实际项目中,须要从业务和平台两方面入手,提高微服务的可靠性。数据库
1.1. 无处不在的故障编程
1.1.1. 分布式部署和调用后端
传统单体架构一个完整的业务流程每每在同一个进程内部完成处理,不须要进行分布式协做,它的工做原理以下所示:缓存
图1-1 传统单体架构本地方法调用服务器
微服务化以后,不一样的微服务采用分布式集群部署方式,服务的消费者和提供者一般运行在不一样的进程中,须要跨网络作RPC调用,它的工做原理以下所示:网络
图1-2 微服务分布式RPC调用数据结构
分布式调用以后,相比于传统单体架构的本地方法调用,主要引入了以下潜在故障点:多线程
序列化与反序列化:微服务的请求和应答都须要通过序列化和反序列化,作消息的跨网络通讯,因为数据结构不一致、不支持的数据类型、对方编解码错误等都会致使序列化和反序列化失败,进而致使微服务调用失败。
网络问题:常见的包括网络超时、网络闪断、网络单通、网络拥塞等,均可能会致使微服务远程调用的失败。
1.1.2. 大型系统微服务进程内合设
理想状况下,每一个微服务都独立打包和部署,微服务之间自然就支持进程级隔离,但事实上,对于一个大规模的企业IT系统、或者大型网站,是由成百上千个微服务组成的,在实践中,微服务一般是不可能作到百分之百独立部署的,缘由以下:
一、方便开发:一般会按照业务域划分团队,同一个业务域每每包含多个微服务,由一个团队负责开发。为了方便 CI/CD,同一业务域的微服务每每打包和部署在一块儿,而不是每一个微服务独立打包部署。
二、方便运维:海量的微服务进程(以1000个微服务 * 10个进程实例为例),会增长部署、数据采集(性能KPI和日志 等)、告警、问题定位等成本,若是运维自动化程度不高,很难支撑大规模的微服务独立部署。
三、提高性能:一些业务对时延很是敏感,若是该业务链上的全部微服务调用都跨网络通讯,时延每每没法知足业务要 求。经过将微服务合设在同一个进程以内,利用路由短路,把RPC调用转化成本地方法调用,能够极大的提高性能。
四、简化分布式事务处理:分布式部署以后,会带来分布式事务问题。有时候业务为了简化分布式事务的处理,将事务相 关的微服务部署在同一个进程中,把分布式事务转换成本地事务,简化事务处理。
不一样的微服务合设在同一个进程之中,就会引入一系列潜在的故障点,例如:
处理较慢的微服务会阻塞其它微服务
某个微服务故障蔓延,可能致使整个进程不可用
低优先级的微服务,抢占高优先级微服务的资源
1.1.3. 微服务健康度
传统状况下,每每使用服务注册中心检测微服务的状态,当检测到服务提供者不可用时,会将故障的服务信息广播到集群全部节点,消费者接收到服务故障通知消息以后,根据故障信息中的服务名称、IP地址等信息,对故障节点进行隔离。它的工做原理以下所示:
图1-3 微服务状态检测
使用基于心跳或者会话的微服务状态检测,能够发现微服务所在进程宕机、网络故障等问题,但在实际业务中,微服务并不是“非死即活”,它可能处于“亚健康状态”,服务调用失败率很高,但又不是所有失败。或者微服务已经处于过负荷流控状态,业务质量受损,可是又没有所有中断。
使用简单的微服务状态检测,很难应对上述这些场景。经过对微服务的运行质量建模,利用微服务健康度模型,根据采集的各类指标对微服务健康度实时打分,依据打分结果采起相应的可靠性对策,能够更有针对性的保障系统的可靠性。
1.1.4. 同步的I/O操做
在整个微服务调用过程当中,主要会涉及到三类I/O操做:
网络I/O操做,涉及到网络读写
磁盘I/O操做,主要是记录日志、话单、写本地文件等
数据库访问,例如Java使用JDBC驱动进行数据库操做
图1-4 微服务涉及的主要I/O操做
凡是涉及到I/O操做的,若是I/O操做是同步阻塞模式,例如Java的BIO、文件File的读写操做、数据库访问的JDBC接口等,都是同步阻塞的。只要访问的网络、磁盘或者数据库实例比较慢,都会致使调用方线程的阻塞。因为线程是Java虚拟机比较重要的资源,当大量微服务调用线程被阻塞以后,系统的吞吐量将严重降低。
1.1.5. 第三方SDK API调用
在微服务中,调用第三方SDK API,也可能会引入新的故障点,例如经过FTP客户端访问远端的FTP服务,或者使用MQ客户端访问MQ服务,若是这些客户端API的容错性设计很差,也会致使调用方的级联故障,这些故障是潜在和隐性的,在设计的时候每每容易被忽视,但它带来的风险和危害是巨大的。
1.2. 微服务可靠性
软件可靠性是指在给定时间内,特定环境下软件无错运行的几率。软件可靠性包含了如下三个要素:
1) 规定的时间:软件可靠性只是体如今其运行阶段,因此将运行时间做为规定的时间的度量。运行时间包括软件系统运行后工做与挂起(启动但空闲)的累计时间。因为软件运行的环境与程序路径选取的随机性,软件的失效为随机事件,因此运行时间属于随机变量。
2) 规定的环境条件:环境条件指软件的运行环境。它涉及软件系统运行时所需的各类支持要素,如支持硬件、操做系统、其它支持软件、输入数据格式和范围以及操做规程等。
3) 规定的功能:软件可靠性还与规定的任务和功能有关。因为要完成的任务不一样,则调用的子模块就不一样(即程序路径选择不一样),其可靠性也就可能不一样。因此要准确度量软件系统的可靠性必须首先明确它的任务和功能。
1.2.1. 关键的可靠性因素
微服务的运行质量,除了自身的可靠性因素以外,还受到其它因素的影响,包括网络、数据库访问、其它相关联的微服务运行质量等。微服务的可靠性设计,须要考虑上述综合因素,总结以下:
图1-5 微服务可靠性设计模型
2. 异步I/O操做
2.1. 网络I/O
2.1.1. 使用同步阻塞I/O的问题
以Java为例,在JDK 1.4推出JAVA NIO1.0以前,基于JAVA的全部Socket通讯都采用了同步阻塞模式(BIO),这种一请求一应答的通讯模型简化了上层的应用开发,可是在可靠性和性能方面存在巨大的弊端:
2-1 传统Java 同步阻塞I/O模型
采用BIO通讯模型的服务端,一般由一个独立的Acceptor线程负责监听客户端的链接,接收到客户端链接以后为客户端链接建立一个新的线程处理请求消息,处理完成以后,返回应答消息给客户端,线程销毁,这就是典型的一请求一应答模型。该架构最大的问题就是不具有弹性伸缩能力,当并发访问量增长后,服务端的线程个数和并发访问数成线性正比,因为线程是JAVA虚拟机很是宝贵的系统资源,当线程数膨胀以后,系统的性能急剧降低,随着并发量的继续增长,可能会发生句柄溢出、线程堆栈溢出等问题,并致使服务器最终宕机。
2.1.2. 使用非阻塞I/O通讯
微服务进行远程通讯时,经过使用非阻塞I/O,能够解决因为网络时延大、高并发接入等致使的服务端线程数膨胀或者线程被阻塞等问题。
以Java为例,从JDK1.4开始,JDK提供了一套专门的类库支持非阻塞I/O,能够在java.nio包及其子包中找到相关的类和接口。JDK1.7以后,又提供了NIO2.0类库,支持异步I/O操做。
利用JDK的异步非阻塞I/O,能够实现一个I/O线程同时处理多个客户端链路,读写操做不会由于网络缘由被阻塞,I/O线程能够高效的并发处理多个客户端链路,实现I/O多路复用,它的工做原理以下所示:
2-2 Java非阻塞I/O模型
使用非阻塞I/O进行通讯,以Java语言为例,建议策略以下:
1) TCP私有协议:建议直接基于Netty开发。
2) HTTP/Restful/SOAP等:选择支持非阻塞I/O的Web框架。也能够选择基于Netty构建的开源应用层协议栈框架,例如支持异步Restful的RestExpress。
2.2. 磁盘I/O
微服务对磁盘I/O的操做分为两类:
直接文件操做:例如调用File的open、write、read等接口,进行文件操做。
间接文件操做:例如调用日志类库写日志,虽然微服务并无直接操做日志文件,可是日志类库底层仍是会进行 文件的读写等操做。
在实际项目中,最容易被忽视的就是日志操做。不一样的日志类库,写日志的机制不一样,以Log4j 1.2.X版本为例,当日志队列满以后,有多种策略:
同步等待,直到新的日志消息可以入队列,它会阻塞当前业务线程。
丢弃当前的日志消息,不会阻塞当前业务线程。
不入队列,由当前调用写日志的业务线程执行日志I/O操做,若是此时磁盘I/O写入速度慢,则会阻塞当前业务线 程。
在实际生产环境中,咱们就遇到过相似问题,在某些时段,磁盘WIO达到10+持续几秒钟-10几秒钟,而后又恢复正常。WIO较高的时段,须要写接口日志、话单等,因为系统默认采用的是同步等待策略,结果致使通讯I/O线程、微服务调度线程等都被阻塞,最终链路由于心跳超时被强制关闭、微服务被大量阻塞在消息队列中致使内存居高不小、响应超时等。
因为偶现的WIO高致使同步写日志被阻塞,继而引发通讯线程、微服务调用线程级联故障,定位起来很是困难,平时Code Review也很难被注意到。因此,隐性的磁盘I/O操做,更须要格外关注。
要解决上面的问题,有三种策略:
使用非阻塞I/O,对文件进行异步读写操做。
业务层面封装一个异步的I/O操做,最简单的策略就是由一个独立的线程或者线程池来执行磁盘I/O操做。
选择支持非阻塞方式调用的I/O类库,例如使用log4j的异步日志API。
以JDK1.7为例,它提供了异步的文件I/O操做类库,基于该类库,就不须要担忧磁盘I/O操做被阻塞:
2-3 JDK1.7异步非阻塞文件接口
本身在上层封装异步I/O操做,也比较简单,它的优势是能够实现磁盘I/O操做与微服务之间的线程隔离,可是底层仍然使用的是同步阻塞I/O,若是此时磁盘的I/O比较高,依然会阻塞写磁盘的I/O线程。它的原理以下所示:
2-4 应用层封装的异步文件操做
将文件I/O操做封装成一个Task或者Event,投递到文件I/O线程池的消息队列中,根据投递结果,构造I/O操做相关联的Future对象给微服务调用线程。经过向Future对象注册Listener并实现callback接口,能够实现异步回调通知,这样微服务和文件I/O操做就实现了线程隔离。文件I/O操做耗时,并不会阻塞微服务调度线程。
当使用第三方文件I/O操做类库时,须要注意下相关API,尽可能使用支持异步非阻塞接口的API,若是没有,则须要考虑是否作上层的异步封装。
2.3. 数据库操做
部分数据库访问支持非阻塞方式,例如Oracle的OCI,它支持non-blocking模式和blocking模式:阻塞方式就是当调用 OCI操做时,必须等到此OCI操做完成后服务器才返回客户端相应的信息,无论是成功仍是失败。非阻塞方式是当客户端提交OCI操做给服务器后,服务器当即返回OCI_STILL_EXECUTING信息,而并不等待服务端的操做完成。对于non-blocking方式,应用程序若收到一个OCI函数的返回值为OCI_STILL_EXECUTING时必须再次对每个OCI函数的返回值进行判断,判断其成功与否。 可经过设置服务器属性为OCI_ATTR_NONBLOCKING_MODE来实现。
对于Java语言而言,因为JDK自己提供了数据库链接驱动相关的接口定义,JDBC驱动自己就是同步API接口,所以,Java语言的开源ORM框架也都是同步阻塞的,例如MyBatis、Hibernate等。
尽管大部分数据库访问接口是同步阻塞的,可是因为数据库中间件的超时控制机制都比较成熟,所以经过合理设置超时时间,能够避免微服务的数据库访问被长时间挂住。
也能够在应用上层封装异步数据库操做层,实现微服务调度与数据库操做的线程级隔离,原理2.2章节已经介绍过,采用该方式一样存在两点不足:
排队现象:若是某个数据库操做很是耗时,超时时间配置的又比较大(例如30S),会致使后续的数据库操做在队 列中排队。
没法充分发挥数据库效能:因为底层数据库访问采用同步阻塞的方式,因此不能高效发挥数据库的效能。
3. 故障隔离
因为大部分微服务采用同步接口调用,并且多个领域相关的微服务会部署在同一个进程中,很容易发生“雪崩效应”,即某个微服务提供者故障,致使调用该微服务的消费者、或者与故障微服务合设在同一个进程中的其它微服务发生级联故障,最终致使系统崩溃。
为了不“雪崩效应”的发生,须要支持多种维度的依赖和故障隔离,以实现微服务的HA。
3.1. 通讯链路隔离
因为网络通讯自己一般不是系统的瓶颈,所以大部分服务框架会采用多线程+单个通讯链路的方式进行通讯,原理以下所示:
3-1 多线程-单链路P2P通讯模式
正如前面章节所述,因为微服务使用异步非阻塞通讯,单个I/O线程能够同时并发处理多个链路的消息,并且网络读写都是非阻塞的,所以采用多线程+单链路的方式进行通讯性能自己问题不大。可是从可靠性角度来看,只支持单链路自己又存在一些可靠性隐患,咱们从下面的案例中看下问题所在。
某互联网基地微服务架构上线以后,发如今一些时段,常常有业务超时,超时的业务没有固定规律。经定位发现当有较多的批量内容同步、语音和视频类微服务调用时,系统的总体时延就增高了不少,并且存在较突出的时延毛刺。因为这些操做获取的消息码流每每达到数M到数十兆,微服务之间又采用单链路的方式进行P2P通讯,致使大码流的传输影响了其它消息的读写效率,增大了微服务的响应时延。
问题定位出来以后,对微服务之间的通讯机制作了优化,节点之间支持配置多链路,每一个链路之间还能够实现不一样策略的隔离,例如根据消息码流大小、根据微服务的优先级等策略,实现链路级的隔离,优化以后的微服务通讯机制:
图3-2 支持多链路隔离
3.2. 调度资源隔离
3.2.1. 微服务之间隔离
当多个微服务合设运行在同一个进程内部时,能够利用线程实现不一样微服务之间的隔离。
对于核心微服务,发布的时候能够独占一个线程/线程池,对于非核心微服务,则能够共享同一个大的线程池,在实现微服务隔离的同时,避免线程过于膨胀:
图3-3 微服务之间故障隔离
假如非核心服务3发生故障,长时间阻塞线程池1的工做线程,其它与其共用线程池消息队列的非核心服务1和服务2只能在队列中排队等待,当服务3释放线程以后,排队的服务1和服务2可能已经超时,只能被丢弃掉,致使业务处理失败。
采用线程池隔离的核心服务1和服务2,因为各自独占线程池,拥有独立的消息队列,它的执行不受发生故障的非核心服务1影响,所以能够继续正常工做。经过独立线程池部署核心服务,能够防止故障扩散,保障核心服务的正常运行。
3.2.2. 第三方依赖隔离
在微服务中一般会调用第三方中间件服务,例如分布式缓存服务、分布式消息队列、NoSQL服务等。只要调用第三方服务,就会涉及跨网络操做,因为客户端SDK API的封装,不少故障都是隐性的,所以,它的可靠性须要额外关注。
总体而言,第三方依赖隔离能够采用线程池 + 响应式编程(例如RxJava)的方式实现,它的原理以下所示:
1) 对第三方依赖进行分类,每种依赖对应一个独立的线程/线程池。
2) 微服务不直接调用第三方依赖的API,而是使用异步封装以后的API接口。
3) 异步调用第三方依赖API以后,获取Future对象。利用响应式编程框架,能够订阅后续的事件,接收响应,针对响应进行编程。
利用Netflix开源的hystrix + RxJava,能够快速实现第三方依赖的隔离,后续章节咱们会详细介绍下如何使用。
3.3. 进程级隔离
对于核心的微服务,例如商品购买、用户注册、计费等,能够采用独立部署的方式,实现高可用性。
3.3.1. 容器隔离
微服务鼓励软件开发者将整个软件解耦为功能单一的服务,而且这些服务可以独立部署、升级和扩容。若是微服务抽象的足够好,那么微服务的这一优势将可以提高应用的敏捷性和自治理能力。
利用Docker容器部署微服务,能够带来以下几个优势:
高效:Docker容器的启动和中止不须要几分钟,只要几百毫秒就足够了。使用Docker部署微服务,微服务的启动 和销毁速度很是快,在高压力时,能够实现秒级弹性伸缩。
高性能:Docker容器的性能接近裸的物理机,比VM平均高20%+。
隔离性:利用Docker,能够实现0.1 core的隔离。基于细粒度的资源隔离机制,能够实现高密度的部署微服务,同 时实现它们之间的资源层隔离,保障微服务的可靠性。
可移植性:在基于虚拟机的解决方案中,应用的可移植性一般来讲会受到云提供商所提供的虚拟机格式限制。若是 应用程序须要部署到不一样类型的虚拟机中,须要针对特定的虚拟机格式作镜像文件,新增不少额外的开发和测试工 做量。Docker容器的设计理念是“一次编写,处处运行”,这可使开发者避免上面这种限制。
基于Docker容器部署微服务,实现物理资源层隔离示意图以下所示:
图3-4 基于Docker容器的微服务隔离
3.3.2. VM隔离
除了Docker容器隔离,也可使用VM对微服务进行故障隔离,相比于Docker容器,使用VM进行微服务隔离存在以下优点:
微服务的资源隔离性更好,CPU、内存、网络等能够实现彻底的资源隔离。
对于已经完成硬件虚拟化的遗留系统,能够直接使用已有的VM,而不须要在VM中从新部署Docker容器。
4. 集群容错
当微服务不可用时,须要根据预置的策略作容错处理,大部分的容错能力和策略是公共的,所以能够下沉到服务框架中实现。
4.1. 路由容错
当集群环境中微服务调用失败以后,利用路由容错机制,能够在底层实现微服务的自动容错处理,提高系统的可靠性。
经常使用的容错策略包括:
失败自动切换机制:微服务调用失败自动切换策略指的是当发生服务调用异常时,从新选路,查找下一个可用的微 服务提供者。微服务发布的时候,能够指定服务的集群容错策略。消费者能够覆盖服务提供者的通用配置,实现个 性化的容错策略。
失败回调机制:微服务调用失败以后,提供异常回调接口,执行微服务消费者自定义的失败处理逻辑。
快速失败机制:在业务高峰期,对于一些非核心的服务,但愿只调用一次,失败也再也不重试,为重要的核心服务节 约宝贵的运行资源。此时,快速失败是个不错的选择。快速失败策略的设计比较简单,获取到服务调用异常以后, 直接忽略异常,记录异常日志。
4.2. 服务降级
大促或者业务高峰时,为了保证核心服务的SLA,每每须要停掉一些不过重要的业务,例如商品评论、论坛或者粉丝积分等。
另一种场景就是某些服务由于某种缘由不可用,可是流程不能直接失败,须要本地Mock服务端实现,作流程放通。以图书阅读为例,若是用户登陆余额鉴权服务不能正常工做,须要作业务放通,记录消费话单,容许用户继续阅读,而不是返回失败。
经过服务治理的服务降级功能,便可以知足上述两种场景的需求。
4.2.1. 强制降级
当外界的触发条件达到某个临界值时,由运维人员/开发人员决策,对某类或者某个服务进行强制降级。
强制降级的经常使用策略:
一、不发起远程服务调用,直接返回空。例如mock = force: return null。
二、不发起远程服务调用,直接抛出指定异常。例如mock = force: throw Exception。
三、不发起远程服务调用,直接执行本地模拟接口实现类。mock = force: execute Bean: <Spring beanName>。
4.2.2. 容错降级
当非核心服务不可用时,能够对故障服务作业务逻辑放通,以保障核心服务的运行。
容错降级与屏蔽降级的主要差别是:
一、触发条件不一样:容错讲解是根据服务调用结果,自动匹配触发的;而屏蔽降级每每是经过人工根据系统运行情 况手工操做触发的。
二、做用不一样:容错降级是当服务提供者不可用时,让消费者执行业务放通;屏蔽降级的主要目的是将原属于降级 业务的资源调配出来供核心业务使用。
三、调用机制不一样:一个发起远程服务调用,一个只作本地调用。
容错降级的经常使用策略以下:
一、异常转义:mock = fail: throw Exception。
二、自定义降级逻辑:mock = fail: execute Bean: <beanName>。将异常屏蔽掉,直接执行本地模拟接口实现类, 返回Mock接口的执行结果。
4.2.3. 服务降级Portal
利用服务治理Portal,能够在线的动态修改微服务的降级策略,实时生效,它的界面以下所示:
图4-1 服务降级配置界面
4.3. 熔断机制
熔断机制(Circuit Breaker),也叫自动停盘机制,是指当股指波幅达到规定的熔断点时,交易所为控制风险采起的暂停交易措施。
在微服务领域,熔断机制是从消费端保护微服务提供者的措施,当微服务的运行质量低于某个临界值时,启动熔断机制,暂停微服务调用一段时间,以保障后端的微服务不会由于持续过负荷而宕机。
4.3.1. 工做原理
微服务的熔断机制原理以下所示:
微服务调用时,对熔断开关状态进行判断,当熔断器开关关闭时, 请求被容许经过熔断器。若是当前微服务健康 度高于指定阈值, 开关继续保持关闭。不然开关切换为打开状态。
当熔断器开关打开时,微服务调用请求被禁止经过。调用失败,执行本地降级逻辑,若是没有实现降级逻辑,默认 返回异常。
当熔断器开关处于打开状态时, 通过指定周期T, 熔断器会自动进入半开状态, 这时熔断器会容许请求经过,当请 求调用成功时, 熔断器恢复到关闭状态。若失败, 则继续保持打开状态。
它的工做原理示意以下:
图4-2 微服务熔断器工做原理
熔断器机制能保证微服务消费者在微服务运行状态不佳时,快速返回结果,避免大量的同步等待。而且能在指定周期T后继续侦测微服务是否可用, 以实现故障恢复以后的自动感知。
4.3.2. 微服务健康度
熔断器开关的状态取决于微服务的运行质量,微服务的运行质量一般由多种因素决定,具备多个衡量因子。经过对微服务健康度建模,能够实现对微服务运行质量的360°实时评估。
微服务健康度模型以下所示:
图4-3 微服务健康度模型
微服务运维体系经过分布式日志采集系统、告警系统、性能KPI数据采集等,利用在线大数据实时分析技术,经过健康度模型,对微服务的健康度按照周期进行实时打分,同时将微服务的得分经过消息队列订阅发布出去,各个节点订阅微服务的健康度得分,与熔断器阈值进行比较,修改熔断器开关的状态。
5. 流量控制
当资源成为瓶颈时,服务框架须要对消费者作限流,启动流控保护机制。流量控制有多种策略,比较经常使用的有:针对访问速率的静态流控、针对资源占用的动态流控等。
在实践中,各类流量控制策略须要综合使用才能起到较好的效果。
5.1. 动态流控
动态流控的最终目标是为了保命,并非对流量或者访问速度作精确控制。当系统负载压力很是大时,系统进入过负载状态,多是CPU、内存资源已通过载,也多是应用进程内部的资源几乎耗尽,若是继续全量处理业务,可能会致使消息严重积压或者应用进程宕机。
动态流控检测的资源包括:
CPU使用率。
内存使用率(对于Java,主要是JVM内存使用率)。
队列积压率。
主机CPU、内存使用率采集算法很是多,例如使用java.lang.Process执行top、sar等外部命令获取系统资源使用状况,而后解析后计算得到资源使用率。也能够直接读取操做系统的系统文件获取相关数据,须要注意的是,不管是执行操做系统的本地命令,仍是直接读取操做系统的资源使用率文件,都是操做系统本地相关的,不一样的操做系统和服务器,命令和输出格式可能存在很大差别。在计算时须要首先判断操做系统类型,而后调用相关操做系统的资源采集接口实现类,经过这种方式就能够支持跨平台。
动态流控是分级别的,不一样级别拒掉的消息比例不一样,这取决于资源的负载使用状况。例如当发生一级流控时,拒绝掉1/4的消息;发生二级流控时,拒绝掉1/2消息;发生三级流控时,全部的消息都被流控掉。
不一样的级别有不一样的流控阈值,系统上线后会提供默认的;流控阈值,不一样流控因子的流控阈值不一样,业务上线以后一般会根据现场的实际状况作阈值调优,所以流控阈值须要支持在线修改和动态生效。
须要指出的是为了防止系统波动致使的偶发性流控,不管是进入流控状态仍是从流控状态恢复,都须要连续采集N次并计算平均值,若是连续N次平均值大于流控阈值,则进入流控状态;同理,只有连续N次资源使用率平均值低于流控阈值,才能脱离流控恢复正常。
5.2. 静态流控
静态流控主要针对客户端访问速率进行控制,它一般根据服务质量等级协定(SLA)中约定的QPS作全局流量控制,例如计费服务的静态流控阈值为200 QPS,则不管集群有多少个计费服务实例,它们总的处理速率之和不能超过200 QPS。
因为微服务具有弹性伸缩、动态上线和下线等特性,所以集群中某个微服务实例的节点个数是动态变化的,采用传统的平均分配制没法作到精准的控制。
在实践中,比较成熟的集群静态流控策略是动态配额申请制,它的工做原理以下:
系统部署的时候,根据微服务节点数和静态流控QPS阈值,拿出必定比例的配额作初始分配,剩余的配额放在配额 资源池中。
哪一个微服务节点使用完了配额,就主动向服务注册中心申请配额。配额的申请策略:若是流控周期为T,则将周期T 分红更小的周期T/N(N为经验值,默认值为10),当前的服务节点数为M个,则申请的配额为 (总QPS配额 - 已经 分配的QPS配额)/M * T/N。
总的配额若是被申请完,则返回0配额给各个申请配额的服务节点,服务节点对新接入的请求消息进行流控。
5.3. 用户自定义流控机制
不一样的业务,存在不一样的流控策略,例如基于微服务优先级的流控、基于节假日的流控、基于业务字段的流控等。底层的服务框架没法实现全部业务级的定制流控策略,所以,过于业务化的流控每每由业务经过自定义流控机制定制实现。
服务框架提供服务调用入口的拦截点和切面接口,由业务实现自定义流控。也能够提供基础的流控框架,供业务实现流控条件判断、流控执行策略等,简化业务的定制工做量。
6. 使用Hystrix提高微服务可靠性
6.1. Hystrix简介
Hystrix是Netflix开源的一个可靠性组件,主要用于分布式环境中的依赖解耦,Hystrix library经过添加延迟容忍和容错逻辑来控制分布式服务之间的相互影响,经过服务之间访问的隔离点阻止连锁故障,并提供了失败回调机制,来改进系统的可靠性。
Hystrix提供以下机制来提高分布式系统的可靠性:
保护经过第三方客户端API依赖访问,控制其延迟和故障
阻止级联故障和“雪崩效应”
提供熔断机制,快速失败和恢复
失败回调和优雅降级机制
近实时检测、报警和KPI指标展现
6.2. Hystrix的核心功能
Hystrix提供了一些很是有价值、与具体微服务框架实现无关的特性,方便不一样的分布式系统集成使用。
6.2.1. 依赖隔离
Hystrix使用命令模式HystrixCommand(Command)包装依赖调用逻辑,每一个命令在单独线程/信号受权下执行。依赖调用的超时时间可配置,若是超时,则则返回失败或者执行fallback逻辑。原理以下所示:
图6-1 基于线程/信号的依赖隔离
6.2.2. 熔断器
Hystrix会先通过熔断器,此时若是熔断器的状态是打开,则说明已经熔断,这时将直接进行降级处理,不会继续将请求发到线程池。
熔断器的开关状态由熔断算法决定,它的原理以下:
判断是否熔断:根据bucket中记录的次数,计算错误率。若是错误率达到熔断预置的阈值,则开启熔断开关。
熔断恢复:对于被熔断的请求,暂停处理一段时间以后 (HystrixCommandProperties.circuitBreakerSleepWindowInMilliseconds()),容许单个请求经过,若请求成功, 则取消熔断,不然,继续熔断。
Hystrix熔断器的工做原理以下所示:
图6-2 Hystrix熔断机制
6.2.3. 优雅降级
当微服务调用异常、超时,或者熔断时,能够经过回调Fallback()的方式实现业务的优雅降级,它的原理以下所示:
图6-3 Hystrix优雅降级机制
6.2.4. Reactive编程
Hystrix支持响应式编程,并提供了相关接口给用户,以下所示:
利用响应式编程,能够更加优雅和灵活的实现异步回调逻辑的处理。
6.2.5. 信号量隔离
为了下降线程资源的开销,Hystrix提供了信号量Semaphores,用于实现轻量级的依赖隔离。
开发者能够限制系统对某一个依赖的最高并发数,这个基本上等同于并发流控策略。每次微服务调用依赖时都会检查一下是否到达信号量的限制值,如达到则拒绝。该隔离策略的优势是不新起线程,减小上下文切换和线程数,缺点是没法配置断路,每次都必定会去尝试获取信号量。
6.3. 集成Hystrix
因为Hystrix与特定的分布式系统、微服务框架无关,是个通用的分布式系统可靠性组件,能够经过类库集成的方式方便的集成到已有的微服务架构体系中。
6.3.1. 集成架构
在已有微服务体系中集成Hystrix的策略以下:
一、微服务框架中,对于通用的微服务调用、磁盘I/O操做、数据库操做和网络I/O操做等使用HystrixCommand作一 层异步包装,实现业务的微服务调用线程和第三方依赖的线程隔离。
二、对于非通用的第三方依赖,或者业务微服务自身引入的第三方依赖,直接基于HystrixCommand作异步隔离。
三、对第三方依赖进行分类、分组管理,根据依赖的特色设置熔断策略、优雅降级策略、超时策略等,以实现差别 化的处理。
集成架构示例以下:
图6-4 集成Hystrix的微服务架构
6.3.2. 集成Hystrix带来的优势
第三方依赖隔离具有必定的通用性,例如数据库隔离、磁盘I/O隔离、第三方服务调用隔离等,若是各自构建一套隔离机制,除了增长工做量以外,后续维护起来也比较麻烦。
另外,业务微服务自身也会引入第三方依赖,若是没有通用的隔离机制,则业务须要本身构建业务级的隔离体系,相应的开发难度和工做量都较大,架构上也很难统一。
集成Hystrix,能够快速的构建微服务的隔离、熔断、优雅降级和响应式编程体系,提高系统的可靠性。
另外,Hystrix很是成熟,在Netflix已经经历过苛刻的生产环境考验,它的可靠性和成熟度彻底可以知足大部分业务场景的须要。