从0到1实现rpc之故障隔离与恢复

一、应用场景

故障隔离解决的是:当服务提供者provider出现异常时,消费者consumer就不再调用异常实例,而是选择好的实例,避免频繁出错。

故障恢复解决的是:一段时间过后,服务提供者provider可以正常提供服务时,可以自动加入到正常的服务列表。供消费者调用。

二、数据结构

使用到的数据结构包括:

  • providers:所有服务提供者,List类型;

  • isolatedProviders:被隔离的服务提供者列表,List类型;

  • halfOpenProviders:处于半开的服务提供者,用于自动探活,自动恢复,List类型;

  • SlidingTimeWindow:滑动窗口,用于统计在指定时间内发生的异常次数;

  • windows:一个实例对应一个滑动窗口,Map类型。

三、代码实现

在消费者端远程调用的动态代理类RpcInvocationHandler中,添加探活、故障隔离和故障恢复的实现逻辑。

图片

探活

为了能够拿到故障的实例,使用了halfOpenProviders,里面存储的就是故障的实例,使用定时任务,10s后启动,每隔60s执行一次半开逻辑:从故障实例中添加到半开实例中,用于请求探活。

图片

halfOpenProviders不为空,说明里面有处于故障中的实例,在请求中可以尝试使用该实例,进行一次远程调用。

如果为空,说明都是正常的实例,就走之前实现的负载均衡的逻辑。

使用synchronized防止并发异常。

图片

故障隔离

当某次调用出现异常时,使用滑动窗口记录该实例的异常调用次数,每发生一次异常就记录一次,当发生的异常次数达到指定阈值后,就隔离该实例。当前默认参数是:在30s内发生两次异常就会被隔离。

图片

隔离的逻辑其实很简单,就是将该实例从能够提供正常服务的实例providers中移除,添加到被隔离的提供者中。

图片

故障恢复

假如能够提供正常服务的实例providers不包含当前请求的实例,就从故障实例isolatedProviders中移除,添加到正常实例providers中。

使用synchronized防止并发异常。

图片

四、测试

  1. 在服务提供者provide端,添加方法isolate(),实现逻辑是当请求端口为8081时,使用除法,在测试时传入id=0,抛出异常。

图片

  1. 在消费者consumer端提供调用接口。

图片

  1. 启动zookeepr作为注册中心。
  2. 分别使用端口8081,8082,8083启动服务提供者provider。

图片

  1. 启动consumer。
  2. 多次发起请求调用。

传入参数id,使用的是轮询负载均衡,消费者会依次调用端口为8081,8082, 8083 的提供者,当前id为0,端口是8081会抛出异常。

在30s内发生超过两次的异常就会被隔离,每隔60s会有定时任务添加半开故障实例。

图片

  1. 观察日志。

先是8081发生故障次数达到指定阈值,加入到故障实例集合isolatedProviders当中。能够提供正常服务的providers就只有8082和8083

图片

接下来的请求,就不会走故障实例8081,达到故障隔离的目的。轮询调用的是8082和8083.

图片

60s后,定时任务执行,故障实例8081添加到半开实例 halfOpenProviders 中。

再进行一次请求时,会进行探活,尝试调用8081,传入参数id=1,调用成功,故障实例8081又重新添加到正常实例providers当中,故障实例成功恢复。

图片