五、Hystrix特性之降级

Fallback相当于是降级操作。所谓降级,就是指在Hystrix执行非核心链路功能失败的情况下,该如何处理,比如返回默认值或者从缓存中取值。
五、Hystrix特性之降级


触发降级的情况
1、hystrix调用各种接口,或者访问外部依赖(如mysql、redis等等)时,执行方法中抛出了异常

2、对每个外部依赖,无论是服务接口,中间件,资源隔离,对外部依赖只能用一定量的资源去访问,线程池/信号量,如果资源池已满,则后续的请求将会被 reject,即进行限流

3、访问外部依赖的时候,访问时间过长,可能就会导致超时,报一个TimeoutException异常,即Timeout机制

上述三种情况,都是常见的异常情况,对外部依赖的东西访问的时候出现了异常,发送异常事件到断路器中去进行统计。

4、如果断路器发现异常事件的占比达到了一定的比例,直接开启断路器

上述四种情况,都会去调用fallback降级机制


注意
降级的处理方式,返回默认值,返回缓存里面的值(包括远程缓存比如redis和本地缓存比如jvmcache)。
但回退的处理方式也有不适合的场景:
1、写操作
2、批处理
3、计算
以上几种情况如果失败,则程序就要将错误返回给调用者。


如果要实现回退或者降级处理,代码上需要实现HystrixCommand.getFallback()方法或者是HystrixObservableCommand. HystrixObservableCommand()。
例如:
public class CommandHelloFailure extends HystrixCommand<String> {

private final String name;

public CommandHelloFailure(String name) {
super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
this.name = name;
}

@Override
protected String run() {
throw new RuntimeException("this command always fails");
}

@Override
protected String getFallback() {
return "Hello Failure " + name + "!";
}
}


Hystrix的降级回退方式

1、Fail Fast 快速失败
就是不给fallback降级逻辑,运行过程中如果有报错,直接会把这个报错抛出来,给tomcat调用线程。

HystrixCommand的实现方式:
@Override
    protected String run() {
        if (throwException) {
            throw new RuntimeException("failure from CommandThatFailsFast");
        } else {
            return "success";
        }
    }

HystrixObservableCommand的实现方式:
@Override
    protected Observable<String> resumeWithFallback() {
        if (throwException) {
            return Observable.error(new Throwable("failure from CommandThatFailsFast"));
        } else {
            return Observable.just("success");
        }
    }

2、Fail Silent 无声失败
给一个fallback降级逻辑。如果是HystrixCommand,则返回null、空Map、空List 等;如果是HystrixObservableCommand,则返回 Observable.empty()。
五、Hystrix特性之降级

@Override
    protected String getFallback() {
        return null;
    }

@Override
    protected List<String> getFallback() {
        return Collections.emptyList();
    }

@Override
    protected Observable<String> resumeWithFallback() {
        return Observable.empty();
    }

3、Static Fallback 默认值降级
回退的时候返回静态嵌入代码中的默认值,这样就不会导致功能以Fail Silent的方式被清除,也就是用户看不到任何功能了;而是按照一个默认的方式显示。
@Override
    protected Boolean getFallback() {
        return true;
    }

@Override
    protected Observable<Boolean> resumeWithFallback() {
        return Observable.just( true );
    }

4、Stubbed Fallback 残缺降级
用请求中的部分数据拼装成结果,然后再填充一些默认值返回。比如说,当发起了一个请求,然后请求中可能本身就附带了一些信息,如果主请求失败了,走到降级逻辑,在降级逻辑里面,可以将这个请求中的数据,以及部分本地缓存有的数据拼装在一起,再给数据填充一些简单的默认值,然后尽可能将自己有的数据返回到请求方。
public class CommandWithStubbedFallback extends HystrixCommand<UserAccount> {

protected CommandWithStubbedFallback(int customerId, String countryCodeFromGeoLookup) {
        super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
        this.customerId = customerId;
        this.countryCodeFromGeoLookup = countryCodeFromGeoLookup;
    }

    @Override
    protected UserAccount getFallback() {
        /**
         * Return stubbed fallback with some static defaults, placeholders,
         * and an injected value 'countryCodeFromGeoLookup' that we'll use
         * instead of what we would have retrieved from the remote service.
         */
        return new UserAccount(customerId, "Unknown Name",
                countryCodeFromGeoLookup, true, true, false);
    }
}

5、多层嵌套Fallback
多层嵌套Fallback是指,在第一次请求失败的情况下,再发起第二次 remote 请求,这次请求的是一个缓存比如Redis;在第二次请求又失败的情况下,再发起最后一次请求,这次请求是去本地ehcache缓存中去获取数据。由于重新发起了远程调用,所以会重新封装一次 command,这个时候要注意,执行 fallback 的线程一定要跟主线程区分开,以防止因主线程池已满而无法运行降级的command,也就是重新命名一个 ThreadPoolKey
五、Hystrix特性之降级

public class CommandWithFallbackViaNetwork extends HystrixCommand<String> {
    private final int id;

    protected CommandWithFallbackViaNetwork(int id) {
        super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("RemoteServiceX"))
                .andCommandKey(HystrixCommandKey.Factory.asKey("GetValueCommand")));
        this.id = id;
    }

    @Override
    protected String run() {
        //        RemoteServiceXClient.getValue(id);
        throw new RuntimeException("force failure for example");
    }

    @Override
    protected String getFallback() {
        return new FallbackViaNetwork(id).execute();
    }

    private static class FallbackViaNetwork extends HystrixCommand<String> {
        private final int id;

        public FallbackViaNetwork(int id) {
            super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("RemoteServiceX"))
                    .andCommandKey(HystrixCommandKey.Factory.asKey("GetValueFallbackCommand"))
                    // use a different threadpool for the fallback command
                    // so saturating the RemoteServiceX pool won't prevent
                    // fallbacks from executing
                    .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("RemoteServiceXFallback")));
            this.id = id;
        }

        @Override
        protected String run() {
            MemCacheClient.getValue(id);
        }

        @Override
        protected String getFallback() {
            // the fallback also failed
            // so this fallback-of-a-fallback will 
            // fail silently and return null
            return null;
        }
    }
}

6、Facade Command 手动降级
一般来说,程序都是去执行一个主流程的command,如果发现主流程中出现了问题,需要可以通过设置一个标志为来让程序去执行备用command,又或者,在日常开发中需要上线一个新功能,但为了防止新功能上线失败可以回退到老的代码。此时可以通过做一个开关比如使用zookeeper做一个配置开关,可以动态切换到备用command或者老代码功能。那么Hystrix它是使用通过一个配置来在两个command中进行切换。
五、Hystrix特性之降级

/**
 * Sample {@link HystrixCommand} pattern using a semaphore-isolated command
 * that conditionally invokes thread-isolated commands.
 */
public class CommandFacadeWithPrimarySecondary extends HystrixCommand<String> {

    private final static DynamicBooleanProperty usePrimary = DynamicPropertyFactory.getInstance().getBooleanProperty("primarySecondary.usePrimary", true);

    private final int id;

    public CommandFacadeWithPrimarySecondary(int id) {
        super(Setter
                .withGroupKey(HystrixCommandGroupKey.Factory.asKey("SystemX"))
                .andCommandKey(HystrixCommandKey.Factory.asKey("PrimarySecondaryCommand"))
                .andCommandPropertiesDefaults(
                        // we want to default to semaphore-isolation since this wraps
                        // 2 others commands that are already thread isolated
                        // 采用信号量的隔离方式
                        HystrixCommandProperties.Setter()
                                .withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE)));
        this.id = id;
    }

    //通过DynamicPropertyFactory来路由到不同的command
    @Override
    protected String run() {
        if (usePrimary.get()) {
            return new PrimaryCommand(id).execute();
        } else {
            return new SecondaryCommand(id).execute();
        }
    }

    @Override
    protected String getFallback() {
        return "static-fallback-" + id;
    }

    @Override
    protected String getCacheKey() {
        return String.valueOf(id);
    }

    private static class PrimaryCommand extends HystrixCommand<String> {

        private final int id;

        private PrimaryCommand(int id) {
            super(Setter
                    .withGroupKey(HystrixCommandGroupKey.Factory.asKey("SystemX"))
                    .andCommandKey(HystrixCommandKey.Factory.asKey("PrimaryCommand"))
                    .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("PrimaryCommand"))
                    .andCommandPropertiesDefaults(
                            // we default to a 600ms timeout for primary
                            HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(600)));
            this.id = id;
        }

        @Override
        protected String run() {
            // perform expensive 'primary' service call
            return "responseFromPrimary-" + id;
        }

    }

    private static class SecondaryCommand extends HystrixCommand<String> {

        private final int id;

        private SecondaryCommand(int id) {
            super(Setter
                    .withGroupKey(HystrixCommandGroupKey.Factory.asKey("SystemX"))
                    .andCommandKey(HystrixCommandKey.Factory.asKey("SecondaryCommand"))
                    .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("SecondaryCommand"))
                    .andCommandPropertiesDefaults(
                            // we default to a 100ms timeout for secondary
                            HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(100)));
            this.id = id;
        }

        @Override
        protected String run() {
            // perform fast 'secondary' service call
            return "responseFromSecondary-" + id;
        }

    }

    public static class UnitTest {

        @Test
        public void testPrimary() {
            HystrixRequestContext context = HystrixRequestContext.initializeContext();
            try {
                //将属性"primarySecondary.usePrimary"设置为true,则走PrimaryCommand;设置为false,则走SecondaryCommand
                ConfigurationManager.getConfigInstance().setProperty("primarySecondary.usePrimary", true);
                assertEquals("responseFromPrimary-20", new CommandFacadeWithPrimarySecondary(20).execute());
            } finally {
                context.shutdown();
                ConfigurationManager.getConfigInstance().clear();
            }
        }

        @Test
        public void testSecondary() {
            HystrixRequestContext context = HystrixRequestContext.initializeContext();
            try {
                //将属性"primarySecondary.usePrimary"设置为true,则走PrimaryCommand;设置为false,则走SecondaryCommand
                ConfigurationManager.getConfigInstance().setProperty("primarySecondary.usePrimary", false);
                assertEquals("responseFromSecondary-20", new CommandFacadeWithPrimarySecondary(20).execute());
            } finally {
                context.shutdown();
                ConfigurationManager.getConfigInstance().clear();
            }
        }
    }
}