Ribbon是Netflix出品的一套负载均衡组件,提供了许多Rule规则从负载列表中选取合适的server实例。
当实例出现问题时候,需要将这部分异常的服务提供者从负载列表中剔除,从而避免雪崩效应。而Riibbon本身具有自动移除问题实例的功能,于是我们可以结合Ribbon对现有的负载均衡策略做一些改进,实现自动故障剔除功能。Ribbon中实现自动移除问题实例的Rule是AvailabilityFilteringRule,它的运行原理如上图所示,大致可以分为以下几步:
结合实际业务需求,在serverStats中,目前定义了两类异常,连接异常(主要指tcp层面的,包括sokcetException,socketTimeoutException,ConnectException)以及不可用异常(主要是底层通信框架rxNetty抛出的异常,包括timeoutException以及PoolExhaustedException)。
考虑到两种异常的发生场景的差异,为这两类异常分别设置了各自的连续失败阈值(connectionFailureThreshold,unavailableThreshold)以及断路超时时间(connectionFailureCircuitTimeout,unavailableCircuitTimeout)。
其中不可用异常参数(unavailableThreshold,unavailableCircuitTimeout)会暴露给使用者,使用springboot的项目可以直接读取yml文件或者托管到第三方配置中心。而其中连接失败异常相关的参数,考虑到较为底层,使用者不大关心,是默认设置好了,只通过读取环境变量的方式来允许改动。
从上面定义的参数就指定,目前断路器统计失败是靠连续失败次数去判断断路逻辑的。之后可以根据不同场景做不同的适配。
目前断路器的工作算法大致如下当有链接失败情况出现断路逻辑时,将会最多:1<<16 * 10 = 655360 s (如果超过自定义超时时间阈值,则最大为自定义超时时间),最少1<<0*10 = 10 s的请求熔断时间,再此期间内,此Server将会被忽略。
熔断的超时时间在没有超过自定义超时阈值的情况下,会随着该server的失败次数呈指数级动态增加。如果当自定义阈值很大,而一个服务实例连续失败很多次,那他基本就没什么希望被调用到了。。框架中算法实现如下:
private long getCircuitBreakerBlackoutPeriod(AtomicInteger atomicInteger, Integer threshold, Integer circuitTimeout) { final int failureCount = atomicInteger.get(); if (failureCount < threshold) { return 0; } final int diff = (failureCount - threshold) > 16 ? 16 : (failureCount - threshold); int blackOutSeconds = (1 << diff) * timeoutFactor; if (blackOutSeconds > circuitTimeout) { blackOutSeconds = circuitTimeout; } return blackOutSeconds * 1000L; }
当loadBalancerCommand对服务请求做出处理之后,根据返回的状态对serverStats做出记录。
AvailabilityFilteringRule通过一定的规则选择合适的server实例。
首先它用roundRobinRule加权轮询算法选取一个sevrer实例。
接着对选出的server实例应用上述的断路算法判断是否应该断路,如果是断路状态,那么将会重新通过roundRobinRule去选择server实例,这里最多重试10次。
如果经过了10次,还没有选出合适的实例,意味着也许所有实例都被熔断了。那么死活还是得挑一个的,这里会通过父类Rule去选择实例,而父类rule的断言器定义的是always true。public Server choose(Object key) { int count = 0; Server server = roundRobinRule.choose(key); while (count++ <= 10) { if (predicate.apply(new PredicateKey(server))) { return server; } server = roundRobinRule.choose(key); } return super.choose(key); }