为何在RPC环节中有熔断以及降级的需求,详细的缘由这里很少解释,从网上搜索一张图作示意。javascript
我理解熔段主要解决以下几个问题:css
好比产品详细页获取产品的好评总数时,因为后端服务异常致使客户端每次都须要等到超时。若是短期内服务不能恢复,那么这段时间内的全部请求时间都将是最大的超时时间,这类消费时间又得不到正确结果的现象是不能容忍的。因此遇到这类状况,就须要根据必定的算法断定服务短期不可用,将后面的请求进行快速失败处理,这样能够节省服务等待时间。java
同时,后端服务是有可能自主或者人为在必定时间内恢复的,因此以前被断定为快速失败的服务,须要有能力去试探服务是否已经恢复。git
上面提到的快速失败以及自主恢复现象就是熔断github
降级是指本身的待遇降低了,从RPC调用环节来说,就是去访问一个本地的假装者而不是真实的服务,但这对调用端来讲是没有区别的。拿电商展现某个产品的详细页来讲:web
上面提供返回默认评论,固定库存的服务就是假装服务,这类服务通常不依赖其它服务,稳定性最高。由假装者提供服务给客户端的现象就是服务降级。算法
一种最简单的办法就是借用hystrix来实现。后端
因为示例未采用注解式方案,因此只须要引用下面两个包便可。app
<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图上去套。ide
建立一个新的类,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)
这里只是一个示例,因此参数设置比较随意,详细的可参考文档。
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()函数就是正常调用时所须要执行的方法,将调用远程通讯的逻辑迁移到此,因为此处不涉及今天讲的熔断降级,因此不用关内心面的代码。
@Override protected Object run() { // 执行远程调用 }
在以前的注解中增长一个属性,用来配置服务假装者所属的类对象
public @interface RpcReference { /** * 服务降级的假装者类对象 * @return */ Class<?> fallbackServiceClazz() default Object.class; }
当快速失败时,咱们但愿返回一些预先准备好的值给到客户端,实现这个需求就须要实现这个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"); }
代理的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
文中代码是依赖上述项目的,若是有不明白的可下载源码