Java RPC框架熔断降级机制原理解析
作者:min.jiang 时间:2023-06-07 06:01:48
熔断与降级
为什么在RPC环节中有熔断以及降级的需求,详细的原因这里不多解释,从网上搜索一张图做示意。
熔断
我理解熔段主要解决如下几个问题:
当所依赖的对象不稳定时,能够起到快速失败的目的快速失败后,能够根据一定的算法动态试探所依赖对象是否恢复
比如产品详细页获取产品的好评总数时,由于后端服务异常导致客户端每次都需要等到超时。如果短时间内服务不能恢复,那么这段时间内的所有请求时间都将是最大的超时时间,这类消费时间又得不到正确结果的现象是不能容忍的。所以遇到这类情况,就需要根据一定的算法判定服务短时间不可用,将后面的请求进行快速失败处理,这样可以节省服务等待时间。
同时,后端服务是有可能自主或者人为在一定时间内恢复的,所以之前被判定为快速失败的服务,需要有能力去试探服务是否已经恢复。
上面提到的快速失败以及自主恢复现象就是熔断
降级
降级是指自己的待遇下降了,从RPC调用环节来讲,就是去访问一个本地的伪装者而不是真实的服务,但这对调用端来说是没有区别的。拿电商展示某个产品的详细页来说:
当加载评论时,由于评论服务不可用,此时可以返回一些默认的评论当加载产品库存,由于库存服务不可用,此时可以固定显示一个库存数
上面提供返回默认评论,固定库存的服务就是伪装服务,这类服务一般不依赖其它服务,稳定性最高。由伪装者提供服务给客户端的现象就是服务降级。
RPC如何支持熔断与降级
一种最简单的办法就是借用hystrix来实现。
引入包依赖
由于示例未采用注解式方案,所以只需要引用下面两个包即可。
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-core</artifactId>
<version>${hystrix-version}</version>
</dependency>
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-metrics-event-stream</artifactId>
<version>${hystrix-version}</version>
</dependency>
实现命令模式
hystrix遵循命令模式,这里可以往这个标准的UML图上去套。
创建一个新的类,RpcHystrixCommand,继承自HystrixCommand即可。
我这里采用线程隔离方式。
构造函数参数
由于需要远程调用,所以构造函数需要接收远程调用所需求必要参数
/**
* 远程目标方法
*/
private Method method;
/**
* 远程目标接口
*/
private Object obj;
/**
* 远程方法所需要的参数
*/
private Object[] params;
/**
* 远程接口客户端引用注解
*/
private RpcReference rpcReference;
/**
* RPC客户端配置
*/
private ReferenceConfig referenceConfig;
构造函数方法签名:
public RpcHystrixCommand(Object obj, Method method, Object[] params, RpcReference rpcReference, ReferenceConfig referenceConfig)
初始化hystrix
这里只是一个示例,所以参数设置比较随意,详细的可参考文档。
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("CircuitBreakerRpcHystrixCommandGroup"))
.andCommandKey(HystrixCommandKey.Factory.asKey("CircuitBreakerRpcHystrixCommandKey"))
.andCommandPropertiesDefaults(
HystrixCommandProperties.Setter()
.withCircuitBreakerEnabled(true)
.withCircuitBreakerRequestVolumeThreshold(1)
.withCircuitBreakerErrorThresholdPercentage(50)
.withCircuitBreakerSleepWindowInMilliseconds(5*1000)
.withMetricsRollingStatisticalWindowInMilliseconds(10*1000)
)
.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("CircuitBreakerRpcHystrixCommandPool"))
.andThreadPoolPropertiesDefaults(
HystrixThreadPoolProperties.Setter().withCoreSize(100)
)
);
run()函数
run()函数就是正常调用时所需要执行的方法,将调用远程通信的逻辑迁移到此,由于此处不涉及今天讲的熔断降级,所以不用关心里面的代码。
@Override
protected Object run() {
// 执行远程调用
}
扩展rpcReference注解以支持降级
在之前的注解中增加一个属性,用来配置服务伪装者所属的类对象
public @interface RpcReference {
/**
* 服务降级的伪装者类对象
* @return
*/
Class<?> fallbackServiceClazz() default Object.class;
}
getFallback()函数
当快速失败时,我们希望返回一些预先准备好的值给到客户端,实现这个需求就需要实现这个fallback函数。
伪装者的逻辑由于是客户端控制,所以我们通过参数来动态支持。 通过rpcReference注解可以获取配置的伪装者
protected Object getFallback() {
Method[] methods = this.rpcReference.fallbackServiceClazz().getMethods();
for (Method methodFallback : methods) {
if(this.method.getName().equals(methodFallback.getName())){
try {
Object fallbackServiceMock= ApplicationContextUtils.getApplicationContext().getBean(this.rpcReference.fallbackServiceClazz());
return methodFallback.invoke(fallbackServiceMock,this.params);
} catch (IllegalAccessException e) {
logger.error("RpcHystrixCommand.getFallback error",e);
} catch (InvocationTargetException e) {
logger.error("RpcHystrixCommand.getFallback error",e);
}
}
}
throw new RpcException("service fallback unimplement");
}
RpcProxy嵌入熔断降级机制
代理的invoke方法,将改调用命令模式的execute方法来代替。
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
RpcHystrixCommand rpcHystrixCommand=new RpcHystrixCommand(proxy,method,args,this.reference,this.referenceConfig);
return rpcHystrixCommand.execute();
}
客户端使用
dubbo有一个mock机制,功能有些弱,有兴趣可以自行研究。我这里更加倾向于根据逻辑来判断是否使用熔断降级,降级的逻辑需要有更多的支持。
Spring Cloud的熔断降级的做法与我的类似,它是通过注解在接口上
@FeignClient(value = "JIM-CLOUD-PROVIDER-SERVER",fallback = ProductServiceHystrix.class)
public interface ProductService {
@RequestMapping(value = "/product/{productId}",method = RequestMethod.GET)
String getById(@PathVariable("productId") final long productId);
}
创建伪装者接口
定义伪装者接口,约定成员方法的签名与真身相同。
public interface ProductCommentMockService {
Product getById(Long productId);
}
实现伪装者接口
实现伪装者接口,这里不光是简单的固定数据,可心任意编写伪装者业务逻辑,与普通的service bean 没有区别。
@Service
public class ProductCommentMockServiceImpl implements ProductCommentMockService {
@Override
public Product getById(Long productId) {
Product mockProduct=new Product();
mockProduct.setId(0L);
mockProduct.setName("mock product name");
return mockProduct;
}
}
服务引用使用熔断降级机制
在引用远程服务接口的注解上,配置伪装者接口的类即可。
@RpcReference(
maxExecutesCount = 1,
fallbackServiceClazz = ProductCommentMockService.class
)
private ProductService productService;
测试
故意不启动服务端,请求接口,此时出现mock数据说明组件功能正常。
{"id":0,"name":"mock product name"}
待解决问题
由于熔断器采用的是新线程执行,所以会影响Rpc上下文传递的参数传递。
本文源码
https://github.com/jiangmin168168/jim-framework
文中代码是依赖上述项目的,如果有不明白的可下载源码
来源:https://www.cnblogs.com/ASPNET2008/p/7954782.html
![](/images/zang.png)
![](/images/jiucuo.png)
猜你喜欢
Java线程池必知必会知识点总结
![](https://img.aspxhome.com/file/2023/4/113514_0s.png)
Java有哪些操作字符串的类?区别在哪?
描述C#多线程中lock关键字的使用分析
Android中使用TagFlowLayout制作动态添加删除标签
![](https://img.aspxhome.com/file/2023/1/93331_0s.gif)
C#隐式运行CMD命令(隐藏命令窗口)
![](https://img.aspxhome.com/file/2023/8/111218_0s.png)
WPF+SkiaSharp实现自绘弹幕效果
![](https://img.aspxhome.com/file/2023/6/98186_0s.gif)
MyBatis动态SQL如何实现前端指定返回字段
通过java.util.TreeMap源码加强红黑树的理解
![](https://img.aspxhome.com/file/2023/8/68548_0s.png)
Mybatis实现增删改查(CRUD)实例代码
Android微信SDK实现分享
Java Swing组件文件选择器JFileChooser简单用法示例
![](https://img.aspxhome.com/file/2023/8/87178_0s.png)
C#组合函数的使用详解
Java深入学习图形用户界面GUI之创建窗体
Android Oss上传图片的使用示例
Spring JPA配置文件Eclipse报错如何解决
使用maven自定义插件开发
![](https://img.aspxhome.com/file/2023/3/72653_0s.jpg)
Windows Zookeeper安装过程及启动图解
![](https://img.aspxhome.com/file/2023/8/83128_0s.png)
SpringBoot整合EasyExcel实现文件导入导出
Java数据结构与算法之稀疏数组与队列深入理解
![](https://img.aspxhome.com/file/2023/8/80978_0s.png)