一、应用场景
故障隔离解决的是:当服务提供者provider出现异常时,消费者consumer就不再调用异常实例,而是选择好的实例,避免频繁出错。
故障恢复解决的是:一段时间过后,服务提供者provider可以正常提供服务时,可以自动加入到正常的服务列表。供消费者调用。
二、数据结构
使用到的数据结构包括:
-
providers:所有服务提供者,List类型;
-
isolatedProviders:被隔离的服务提供者列表,List类型;
-
halfOpenProviders:处于半开的服务提供者,用于自动探活,自动恢复,List类型;
-
SlidingTimeWindow:滑动窗口,用于统计在指定时间内发生的异常次数;
-
windows:一个实例对应一个滑动窗口,Map类型。
三、代码实现
在消费者端远程调用的动态代理类RpcInvocationHandler中,添加探活、故障隔离和故障恢复的实现逻辑。
探活
为了能够拿到故障的实例,使用了halfOpenProviders,里面存储的就是故障的实例,使用定时任务,10s后启动,每隔60s执行一次半开逻辑:从故障实例中添加到半开实例中,用于请求探活。
halfOpenProviders不为空,说明里面有处于故障中的实例,在请求中可以尝试使用该实例,进行一次远程调用。
如果为空,说明都是正常的实例,就走之前实现的负载均衡的逻辑。
使用synchronized防止并发异常。
故障隔离
当某次调用出现异常时,使用滑动窗口记录该实例的异常调用次数,每发生一次异常就记录一次,当发生的异常次数达到指定阈值后,就隔离该实例。当前默认参数是:在30s内发生两次异常就会被隔离。
隔离的逻辑其实很简单,就是将该实例从能够提供正常服务的实例providers中移除,添加到被隔离的提供者中。
故障恢复
假如能够提供正常服务的实例providers不包含当前请求的实例,就从故障实例isolatedProviders中移除,添加到正常实例providers中。
使用synchronized防止并发异常。
四、测试
- 在服务提供者provide端,添加方法isolate(),实现逻辑是当请求端口为8081时,使用除法,在测试时传入id=0,抛出异常。
- 在消费者consumer端提供调用接口。
- 启动zookeepr作为注册中心。
- 分别使用端口8081,8082,8083启动服务提供者provider。
- 启动consumer。
- 多次发起请求调用。
传入参数id,使用的是轮询负载均衡,消费者会依次调用端口为8081,8082, 8083 的提供者,当前id为0,端口是8081会抛出异常。
在30s内发生超过两次的异常就会被隔离,每隔60s会有定时任务添加半开故障实例。
- 观察日志。
先是8081发生故障次数达到指定阈值,加入到故障实例集合isolatedProviders当中。能够提供正常服务的providers就只有8082和8083
接下来的请求,就不会走故障实例8081,达到故障隔离的目的。轮询调用的是8082和8083.
60s后,定时任务执行,故障实例8081添加到半开实例 halfOpenProviders 中。
再进行一次请求时,会进行探活,尝试调用8081,传入参数id=1,调用成功,故障实例8081又重新添加到正常实例providers当中,故障实例成功恢复。