hystrix结合dubbo框架实现服务熔断降级, 保证后台服务稳定性

一, 服务稳定性以及熔断降级的意义

需求的多样性, 业务的复杂程度也是不断上升的, 一个接口依赖的服务也会逐步增加, 各个服务的稳定性是需要着重考虑的; 例如一个电商订单场景, 不能因为评论服务异常超时, 而阻止用户浏览该商品的其他信息, 同样的也不能因为用户优惠券服务等这些非核心服务(弱依赖), 异常超时从而阻止用户下订单; 但是网络环境又是不可控的, 有可能在某一段时间内, 由于网络抖动, 请求被拒绝, 或者是高并发的大流量进来, 导致系统资源繁忙(线程池耗尽等), 请求被拒绝. 这时就需要使用熔断降级的方式提供有损服务, 确保核心业务不受影响.

二, 图示演示

一次用户请求过来, 需要后台多个服务A, B....提供支持, 但如果服务A一直失败, 而且服务A还是一个非核心服务(即是一个弱依赖的服务),可以采用熔断降级的方式, 使后续的请求直接走降级逻辑, 返回固定逻辑即可, 确保核心的业务不受到影响.

三, Hystrix框架

要想实现上面描述的过程, 那么hystrix就必须要实现下面这几样功能:

1 服务出现异常, 超时, 能够自动走降级逻辑;

2 当服务的失败率达到阈值时, 能够自动进行熔断;

3 在服务熔断期间, 所有对于该服务的请求, 全部走降级.

4 当服务恢复时, 后续请求能够正常访问该服务.

四, dubbo结合hystrix实现服务的熔断降级

1 通过在<dubbo:reference>下配置自定义<dubbo:parameters>参数, 决定是否启用hystrix

<!-- 配置打开hystrix -->
<dubbo:parameter key="isOpen" value="true"/>
/**
 * @Description:
 * @project dubbo-hystrix-filter
 * @class DubboHystrixFilter.java
 * @author xiongxianze
 * @version 1.0
 * @date 2019年2月10日 上午12:10:40
 */
@Activate(group = Constants.CONSUMER, order = 10002)
public class DubboHystrixFilter implements Filter {

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        String isOpen = invoker.getUrl().getMethodParameter(invocation.getMethodName(), "isOpen");
        // 是否打开hystrix
        if (isOpen != null && Boolean.parseBoolean(isOpen)) {
            DubboHystrixCommand dubboHystrixCommand = new DubboHystrixCommand(invoker, invocation);
            Result result = dubboHystrixCommand.execute();
            return result;
        } else {
            // 未打开的话, 直接走真正调用逻辑
            return invoker.invoke(invocation);
        }
    }

}

2 DubboHystrixCommand继承HystrixCommand类, 并重写其run()和getFallback()方法, 当执行dubboHystrixCommand.execute()时, 就会执行其run()方法, 执行run()方法报异常时, 就会调用getFallback()方法走降级逻辑, 代码如下:

DubboHystrixCommand类构造方法如下: 调用HystrixCommand_Setter()方法配置hystrix熔断的条件属性.

public DubboHystrixCommand(Invoker<?> invoker, Invocation invocation){
        // 构造HystrixCommand.Setter
        super(HystrixCommand_Setter(invoker, invocation));
        this.invoker = invoker;
        this.invocation = invocation;
        this.fallbackName = invoker.getUrl().getMethodParameter(invocation.getMethodName(), "fallback");
        ;
    }

①构造HystrixCommand.Setter

private static HystrixCommand.Setter HystrixCommand_Setter(Invoker<?> invoker, Invocation invocation) {
        // interfaceName.methodName
        String key = String.format("%s.%s", invoker.getInterface().getName(), invocation.getMethodName());
        // 1 根据interfaceName+methodName从缓存获取Setter
        if (setterHashMap.containsKey(key)) {
            return setterHashMap.get(key);
        } else {
            setterHashMap.put(key,
                              HystrixCommand.Setter
                              // 组名使用服务接口模块名称
                              .withGroupKey(HystrixCommandGroupKey.Factory.asKey(invoker.getInterface().getName()))
                              // 隔离粒度为接口方法, 但是同一个接口中的所有方法公用一个线程池, 各个服务接口的线程池是隔离的
                              // 配置到这里, 就说明, 相同的接口服务, 相同的方法, 拥有相同的熔断配置策略
                              .andCommandKey(HystrixCommandKey.Factory.asKey(invocation.getMethodName()))
                              // 熔断配置
                              .andCommandPropertiesDefaults(hystrixCommandProperties_Setter(invoker.getUrl(),invocation.getMethodName()))
                              // 线程池配置
                              .andThreadPoolPropertiesDefaults(hystrixThreadPoolProperties_Setter(invoker.getUrl())));
            return setterHashMap.get(key);
        }
    }

②配置hystrixCommandProperties.Setter熔断属性

public static HystrixCommandProperties.Setter hystrixCommandProperties_Setter(URL url, String method) {
        // 从URL获取熔断配置
        return HystrixCommandProperties.Setter()
                                       // 熔断触发后多久恢复half-open状态,
                                       // 熔断后sleepWindowInMilliseconds毫秒会放入一个请求,如果请求处理成功,熔断器关闭,否则熔断器打开,继续等待sleepWindowInMilliseconds
                                       .withCircuitBreakerSleepWindowInMilliseconds(url.getMethodParameter(method,
                                                                                                           "sleepWindowInMilliseconds",
                                                                                                           5000))
                                       // 熔断触发错误率阈值, 超过50%错误触发熔断
                                       .withCircuitBreakerErrorThresholdPercentage(url.getMethodParameter(method,
                                                                                                          "errorThresholdPercentage",
                                                                                                          50))
                                       // 熔断判断请求数阈值, 一个统计周期内(默认10秒)请求不少于requestVolumeThreshold才会进行熔断判断
                                       .withCircuitBreakerRequestVolumeThreshold(url.getMethodParameter(method,
                                                                                                        "requestVolumeThreshold",
                                                                                                        20))
                                       // 这里可以禁用超时, 而采用dubbo的超时时间, 默认为true
                                       // .withExecutionTimeoutEnabled(false)
                                       // 当隔离策略为THREAD时,当执行线程执行超时时,是否进行中断处理,默认为true。
                                       .withExecutionIsolationThreadInterruptOnTimeout(true)
                                       // 执行超时时间,默认为1000毫秒,如果命令是线程隔离,且配置了executionIsolationThreadInterruptOnTimeout=true,则执行线程将执行中断处理。
                                       // 如果命令是信号量隔离,则进行终止操作,因为信号量隔离与主线程是在一个线程中执行,其不会中断线程处理,所以要根据实际情况来决定是否采用信号量隔离,尤其涉及网络访问的情况。
                                       // 注意该时间和dubbo自己的超时时间不要冲突,以这个时间优先,比如consumer设置3秒,那么当执行时hystrix会提前超时, 因为这里设置的时间为1秒
                                       .withExecutionTimeoutInMilliseconds(url.getMethodParameter(method,
                                                                                                  "timeoutInMilliseconds",
                                                                                                  1000))
                                       // fallback方法的信号量配置,配置getFallback方法并发请求的信号量,如果请求超过了并发信号量限制,则不再尝试调用getFallback方法,而是快速失败,默认信号量为10
                                       .withFallbackIsolationSemaphoreMaxConcurrentRequests(url.getMethodParameter(method,
                                                                                                                   "fallbackMaxConcurrentRequests",
                                                                                                                   50))
                                       // 隔离策略, 默认thread线程池隔离
                                       .withExecutionIsolationStrategy(getIsolationStrategy(url))
                                       // 设置隔离策略为ExecutionIsolationStrategy.SEMAPHORE时,HystrixCommand.run()方法允许的最大请求数。如果达到最大并发数时,后续请求会被拒绝。
                                       .withExecutionIsolationSemaphoreMaxConcurrentRequests(url.getMethodParameter(method,
                                                                                                                    "maxConcurrentRequests",
                                                                                                                    10));

    }

③ 配置HystrixThreadPoolProperties.Setter线程池属性

public static HystrixThreadPoolProperties.Setter hystrixThreadPoolProperties_Setter(URL url) {
        // 从url获取线程池配置
        return HystrixThreadPoolProperties
                .Setter()
                .withCoreSize(url.getParameter("coreSize",10))
                .withAllowMaximumSizeToDivergeFromCoreSize(true)
                .withMaximumSize(url.getParameter("maximumSize",20))
                .withMaxQueueSize(-1)
                .withKeepAliveTimeMinutes(url.getParameter("keepAliveTimeMinutes",1));
    }

④ 设置隔离策略

 public static HystrixCommandProperties.ExecutionIsolationStrategy getIsolationStrategy(URL url) {
        String isolation = url.getParameter("isolation", THREAD);
        if (!isolation.equalsIgnoreCase(THREAD) && !isolation.equalsIgnoreCase(SEMAPHORE)) {
            isolation = THREAD;
        }
        if (isolation.equalsIgnoreCase(THREAD)) {
            return HystrixCommandProperties.ExecutionIsolationStrategy.THREAD;
        } else {
            return HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE;
        }
    }

⑥重写run()方法, 当远程调用异常时, 就会hystrix框架就会调用getFallback()方法执行降级逻辑.

protected Result run() throws Exception {
        Result result = invoker.invoke(invocation);
        // 如果远程调用异常,抛出异常就会调用getFallback()方法去执行降级逻辑
        if (result.hasException()) {
            throw new HystrixRuntimeException(HystrixRuntimeException.FailureType.COMMAND_EXCEPTION,
                                              DubboHystrixCommand.class, result.getException().getMessage(),
                                              result.getException(), null);
        }

        return result;
    }

⑦getFallback()方法, 执行降级逻辑; 基于SPI扩展机制让业务方实现Fallback接口, 执行降级逻辑.

protected Result getFallback() {
        // 如果没有fallback, 则抛出原本的异常:No fallback available.
        if (StringUtils.isEmpty(fallbackName)) {
            return super.getFallback();
        }
        try {
            // 基于SPI扩展加载fallback实现
            ExtensionLoader<Fallback> loader = ExtensionLoader.getExtensionLoader(Fallback.class);
            Fallback fallback = loader.getExtension(fallbackName);
            Object value = fallback.invoker();
            return new RpcResult(value);
        } catch (RuntimeException ex) {
            logger.error("fallback failed", ex);
            throw ex;
        }
    }

⑧业务方实现Fallback接口, 执行降级逻辑.

首先创建, META-INF/dubbo/com.xxz.dubbo.fallback.Fallback文件, 并添加其实现类fallbackImpl=com.xxz.dubbo.fallback.FallbackImpl

五, 使用

1 引入依赖

    <!-- 引入自定义dubbo-hystrix-filter熔断降级 -->
  	<dependency>
  	<groupId>com.xxz</groupId>
    	<artifactId>dubbo-hystrix-filter</artifactId>
    	<version>0.0.1-SNAPSHOT</version>
  	</dependency>

2 实现降级Fallback接口, 如上步骤⑧配置, 实现降级逻辑.

public class FallbackImpl implements Fallback {

    /**
     * @Description:
     * @author xiongxianze
     * @version 1.0
     * @date 2019年2月10日 上午12:28:17
     * @see com.xxz.dubbo.fallback.Fallback#invoker()
     */
    @Override
    public Object invoker() {
        return "返回默认值null";
    }

}

3 <dubbo:reference>配置自定义参数

<!-- 引用服务serviceA -->
	<dubbo:reference id="serviceA" interface="com.xxz.dubbo.ServiceA" 
					 protocol="dubbo" check="false" mock="false"
					 retries="0">
		<dubbo:method name="serviceA" timeout="3000"/>
		<dubbo:method name="serviceNum" timeout="2000"/>
		<!-- 配置打开hystrix -->
		<dubbo:parameter key="isOpen" value="true"/>
		<!-- 降级配置 -->
		<dubbo:parameter key="fallback" value="fallbackImpl"/>
		<!--核心线程数大小  -->
        <dubbo:parameter key="coreSize" value="10"/>
        <!--最大线程数大小  -->
        <dubbo:parameter key="maximumSize" value="20"/>
        <!--空闲线程持有时间(分钟) -->
        <dubbo:parameter key="keepAliveTimeMinutes" value="1"/>
        
        <!-- 熔断判断请求数阈值, 一个统计周期内(默认10秒)请求不少于requestVolumeThreshold才会进行熔断判断 -->
        <dubbo:parameter key="requestVolumeThreshold" value="5"/>
        <!-- 熔断触发后多久恢复half-open状态, 熔断后sleepWindowInMilliseconds毫秒会放入一个请求,如果请求处理成功,熔断器关闭,否则熔断器打开,继续等待sleepWindowInMilliseconds -->
        <dubbo:parameter key="sleepWindowInMilliseconds" value="20000"/>
        <!-- 熔断触发错误率阈值, 超过50%错误触发熔断 -->
        <dubbo:parameter key="errorThresholdPercentage" value="50"/>
        <!-- 任务执行超时时间, 注意该时间和dubbo自己的超时时间不要冲突,以这个时间优先,
        	比如consumer设置3秒,这里的超时时间为200ms的话, 则本次调用就会超时 -->
        <dubbo:parameter key="timeoutInMilliseconds" value="3000"/>
		
    </dubbo:reference>

六, 结果(基于上面的参数配置)

1 首先在一个统计周期(默认10s), 配置requestVolumeThreshold=5次请求, 有errorThresholdPercentage=50的错误异常率的话, hystrix就会自动熔断, 后续请求直接走降级逻辑getFallback()方法返回默认值.

2 同时, Hystrix也会在sleepWindowInMilliseconds=20000ms(即20s后), 会放入一个请求, 这时hystrix处于一个半开状态, 若这个请求能正常被处理, Hystrix熔断器就会关闭, 否则熔断器就会继续打开, 确保其他请求还是走降级, 从而继续等待下一个sleepWindowInMilliseconds.

七, 综上: 使用Hystrix确实是可以实现刚刚我们在上面描述的这四个功能点.

1 服务出现异常, 超时, 能够自动走降级逻辑;

2 当服务的失败率达到阈值时, 能够自动进行熔断;

3 在服务熔断期间, 所有对该服务的请求, 全部走降级.

4 当服务恢复时, 后续的请求能够正常访问到该服务.

  • 3
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值