从0到1实现rpc之丰富测试案例

测试案例主要针对服务消费者consumer,复杂逻辑都在consumer端。

  1. 常规int类型,返回User对象

图片

参数类型转换,主要实现逻辑都在TypeUtils工具类中。

  1. 测试方法重载,同名方法,参数不同

图片

方法签名的实现,主要逻辑都在MethodUtils工具类中。

  1. 测试返回字符串

图片

参数类型转换,主要实现逻辑都在TypeUtils工具类中。

  1. 测试重载方法返回字符串

图片

参数类型转换,主要实现逻辑都在TypeUtils工具类中。

  1. 测试local toString方法

图片

本地方法不走远程调用,主要逻辑都在MethodUtils工具类中。

  1. 常规int类型,返回int

图片

基本参数类型转换,主要实现逻辑都在TypeUtils工具类中。

  1. 测试long+float类型

图片

基本参数类型转换,主要实现逻辑都在TypeUtils工具类中。

  1. 测试参数是User类型

图片

对象参数类型转换,主要实现逻辑都在TypeUtils工具类中。

  1. 测试返回long[]

图片

数组参数类型转换,主要实现逻辑都在TypeUtils工具类中。

  1. 测试参数和返回值都是long[]

图片

数组参数类型转换,主要实现逻辑都在TypeUtils工具类中。

  1. 测试参数和返回值都是List类型

图片

List参数类型转换,主要实现逻辑都在TypeUtils工具类中。

  1. 测试参数和返回值都是Map类型

图片

Map参数类型转换,主要实现逻辑都在TypeUtils工具类中。

  1. 测试参数和返回值都是Boolean/boolean类型

图片

基本参数类型转换,主要实现逻辑都在TypeUtils工具类中。

  1. 测试参数和返回值都是User[]类型

图片

对象数组参数类型转换,主要实现逻辑都在TypeUtils工具类中。

  1. 试参数为long,返回值是User类型

图片

基本参数类型转换,主要实现逻辑都在TypeUtils工具类中。

  1. 测试参数为boolean,返回值都是User类型

图片

基本参数类型转换,主要实现逻辑都在TypeUtils工具类中。

  1. 测试服务端抛出一个RuntimeException异常

图片

异常处理,主要实现逻辑都在RpcInvocationHandler工具类中。

  1. 测试服务端抛出一个超时重试后成功的场景

图片

异常超时重试,主要实现逻辑都在RpcInvocationHandler工具类中。

  1. 测试通过Context跨消费者和提供者进行传参

图片

跨线程传参

  • 使用ThreadLocal实现;
  • 在RpcRequest中携带传递的参数;
  • 在consumer端,使用ParameterFilter将参数从RpcContext传递到RpcRequest;
  • 在provider端,反射调用之前,从RpcRequest中获取到参数封装到当前RpcContext。调用完成之后,需要清除,防止内存泄漏和上下文污染

源码:

https://github.com/midnight2104/midnight-rpc/tree/lesson11

从0到1实现rpc之灰度发布

当发布新版本时,采用灰度发布的方式,把流量逐步发开到新新版本,可以有效保证发版的顺利。

一、灰度标识

  1. 使用特定的符号表示某个实例是否是灰度应用。

在配置信息中添加实例元信息,其中gray为灰度标识,值为true表示灰度版本,值为false表示正式版本。

图片

  1. 参数配置

在代码中使用Map接受服务提供者provider的配置参数。

图片

  1. 添加配置信息到实例元数据中

在服务提供者provider启动过程中读取配置信息,然后添加到元实例的参数中去。

图片

图片

  1. 添加到配置中心

在服务注册的时候,将服务原配置添加到注册中心上去。

图片

  1. 注册成功后,就可以看到每个实例的元信息已经放到配置中心,灰度标识也就完成了。

图片

二、灰度路由

  1. 在服务消费者consumer启动时,会向注册中心抓取服务提供者provider的元信息。

图片

在获取数据过程中,读取节点数据的值,通过反序列化将元信息保存到服务消费者consumer端。

  1. 添加灰度路由
  • 通过构造函数设置灰度流量比例;
  • 通过灰度标识gray识别实例是正常实例,还是灰度实例;
  • 根据灰度比例判断路由逻辑:小于0,表示没有灰度;大于100,表示全是灰度实例。在0-100之间就通过随机数模拟随机灰度,返回灰度节点信息。

图片

  1. 灰度请求

在消费者启动过程中,将灰度路由注册为bean。

图片

在动态代理类中执行时,会先从一堆实例中选择路由,在这里就有可能选择到灰度实例。

图片

三、测试

  1. 启动ZooKeeper作为注册中心。
  2. 使用端口8081,8082,,8083依次启动服务提供者provider。
  3. 在服务消费者consumer端新建立接口,并启动。

图片

  1. 发起请求

这个时候,看到的版本信息都是v1。

图片

  1. 模拟灰度发布

假如业务逻辑发生了变化,版本是v2。

图片

将8081的服务标识为灰度应用。

图片

  1. 再多次发起请求,观察日志。

总共三个实例,灰度应用是8081,正常应用是8082,8083,灰度比例是33%。

图片

在多次请求中,有概率走到灰度应用。

图片

在实际应用中,小流量严重发现没有问题,就会逐步放大灰度比例,热更新灰度应用表示,直到全部转为正式。

源码:

https://github.com/midnight2104/midnight-rpc/tree/lesson10

从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当中,故障实例成功恢复。

图片