简易RPC框架-熔断降级机制

熔断与降级

为何在RPC环节中有熔断以及降级的需求,详细的缘由这里很少解释,从网上搜索一张图作示意。javascript

熔断

我理解熔段主要解决以下几个问题:css

  • 当所依赖的对象不稳定时,可以起到快速失败的目的
  • 快速失败后,可以根据必定的算法动态试探所依赖对象是否恢复

好比产品详细页获取产品的好评总数时,因为后端服务异常致使客户端每次都须要等到超时。若是短期内服务不能恢复,那么这段时间内的全部请求时间都将是最大的超时时间,这类消费时间又得不到正确结果的现象是不能容忍的。因此遇到这类状况,就须要根据必定的算法断定服务短期不可用,将后面的请求进行快速失败处理,这样能够节省服务等待时间。java

同时,后端服务是有可能自主或者人为在必定时间内恢复的,因此以前被断定为快速失败的服务,须要有能力去试探服务是否已经恢复。git

上面提到的快速失败以及自主恢复现象就是熔断github

降级

降级是指本身的待遇降低了,从RPC调用环节来说,就是去访问一个本地的假装者而不是真实的服务,但这对调用端来讲是没有区别的。拿电商展现某个产品的详细页来讲:web

  • 当加载评论时,因为评论服务不可用,此时能够返回一些默认的评论
  • 当加载产品库存,因为库存服务不可用,此时能够固定显示一个库存数

上面提供返回默认评论,固定库存的服务就是假装服务,这类服务通常不依赖其它服务,稳定性最高。由假装者提供服务给客户端的现象就是服务降级。算法

RPC如何支持熔断与降级

一种最简单的办法就是借用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)

初始化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

文中代码是依赖上述项目的,若是有不明白的可下载源码

相关文章
相关标签/搜索