09 Apr 2024 |
测试案例主要针对服务消费者consumer,复杂逻辑都在consumer端。
- 常规int类型,返回User对象
参数类型转换,主要实现逻辑都在TypeUtils工具类中。
- 测试方法重载,同名方法,参数不同
方法签名的实现,主要逻辑都在MethodUtils工具类中。
- 测试返回字符串
参数类型转换,主要实现逻辑都在TypeUtils工具类中。
- 测试重载方法返回字符串
参数类型转换,主要实现逻辑都在TypeUtils工具类中。
- 测试local toString方法
本地方法不走远程调用,主要逻辑都在MethodUtils工具类中。
- 常规int类型,返回int
基本参数类型转换,主要实现逻辑都在TypeUtils工具类中。
- 测试long+float类型
基本参数类型转换,主要实现逻辑都在TypeUtils工具类中。
- 测试参数是User类型
对象参数类型转换,主要实现逻辑都在TypeUtils工具类中。
- 测试返回long[]
数组参数类型转换,主要实现逻辑都在TypeUtils工具类中。
- 测试参数和返回值都是long[]
数组参数类型转换,主要实现逻辑都在TypeUtils工具类中。
- 测试参数和返回值都是List类型
List参数类型转换,主要实现逻辑都在TypeUtils工具类中。
- 测试参数和返回值都是Map类型
Map参数类型转换,主要实现逻辑都在TypeUtils工具类中。
- 测试参数和返回值都是Boolean/boolean类型
基本参数类型转换,主要实现逻辑都在TypeUtils工具类中。
- 测试参数和返回值都是User[]类型
对象数组参数类型转换,主要实现逻辑都在TypeUtils工具类中。
- 试参数为long,返回值是User类型
基本参数类型转换,主要实现逻辑都在TypeUtils工具类中。
- 测试参数为boolean,返回值都是User类型
基本参数类型转换,主要实现逻辑都在TypeUtils工具类中。
- 测试服务端抛出一个RuntimeException异常
异常处理,主要实现逻辑都在RpcInvocationHandler工具类中。
- 测试服务端抛出一个超时重试后成功的场景
异常超时重试,主要实现逻辑都在RpcInvocationHandler工具类中。
- 测试通过Context跨消费者和提供者进行传参
跨线程传参
- 使用ThreadLocal实现;
- 在RpcRequest中携带传递的参数;
- 在consumer端,使用ParameterFilter将参数从RpcContext传递到RpcRequest;
- 在provider端,反射调用之前,从RpcRequest中获取到参数封装到当前RpcContext。调用完成之后,需要清除,防止内存泄漏和上下文污染
源码:
https://github.com/midnight2104/midnight-rpc/tree/lesson11
07 Apr 2024 |
当发布新版本时,采用灰度发布的方式,把流量逐步发开到新新版本,可以有效保证发版的顺利。
一、灰度标识
- 使用特定的符号表示某个实例是否是灰度应用。
在配置信息中添加实例元信息,其中gray为灰度标识,值为true表示灰度版本,值为false表示正式版本。
- 参数配置
在代码中使用Map接受服务提供者provider的配置参数。
- 添加配置信息到实例元数据中
在服务提供者provider启动过程中读取配置信息,然后添加到元实例的参数中去。
- 添加到配置中心
在服务注册的时候,将服务原配置添加到注册中心上去。
- 注册成功后,就可以看到每个实例的元信息已经放到配置中心,灰度标识也就完成了。
二、灰度路由
- 在服务消费者consumer启动时,会向注册中心抓取服务提供者provider的元信息。
在获取数据过程中,读取节点数据的值,通过反序列化将元信息保存到服务消费者consumer端。
- 添加灰度路由
- 通过构造函数设置灰度流量比例;
- 通过灰度标识gray识别实例是正常实例,还是灰度实例;
- 根据灰度比例判断路由逻辑:小于0,表示没有灰度;大于100,表示全是灰度实例。在0-100之间就通过随机数模拟随机灰度,返回灰度节点信息。
- 灰度请求
在消费者启动过程中,将灰度路由注册为bean。
在动态代理类中执行时,会先从一堆实例中选择路由,在这里就有可能选择到灰度实例。
三、测试
- 启动ZooKeeper作为注册中心。
- 使用端口8081,8082,,8083依次启动服务提供者provider。
- 在服务消费者consumer端新建立接口,并启动。
- 发起请求
这个时候,看到的版本信息都是v1。
- 模拟灰度发布
假如业务逻辑发生了变化,版本是v2。
将8081的服务标识为灰度应用。
- 再多次发起请求,观察日志。
总共三个实例,灰度应用是8081,正常应用是8082,8083,灰度比例是33%。
在多次请求中,有概率走到灰度应用。
在实际应用中,小流量严重发现没有问题,就会逐步放大灰度比例,热更新灰度应用表示,直到全部转为正式。
源码:
https://github.com/midnight2104/midnight-rpc/tree/lesson10
29 Mar 2024 |
一、应用场景
故障隔离解决的是:当服务提供者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当中,故障实例成功恢复。