教程:一起学习Hystrix--Hystrix常用场景--降级(回退)

孤人 提交于 2019-12-01 12:13:20

目录

  • Hystrix本系列博文
  • 静态降级(返回默认值)
  • 自定义降级
  • 网络缓存降级
  • 主备降级
  • 声明

Hystrix本系列博文

    以下为博主写Hystrix系列的文章列表,顺便骗个赞,觉得写的还可以的,不要吝啬你的赞哟

     点击查看 Hystrix入门

    点击查看 Hystrix命令执行

    点击查看 Hystrix处理异常机制(降级方法)

    点击查看 Hystrix命令名称、分组、线程池

    点击查看 Hystrix命令名称、Hystrix请求处理

    点击查看 Hystrix请求处理

    点击查看 Hystrix常用场景--失败

静态降级(返回默认值)

    可以在代码中静态的返回默认值进行降级, 这不会导致功能或服务以“静默失败”的方式被删除,而是导致默认行为发生。

    例如,如果一个命令基于用户凭据返回true/false,如果命令执行失败,它可以默认为true:

    @Override
    protected Boolean getFallback() {
        return true;
    }

HystrixObservableCommand 等价

     对于 HystrixObservableCommand 的静默失败解决方案是调用重写 resumeWithFallback 方法,示例如下:

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

自定义降级

     当命令返回的是一个包含多个字段的复合对象时,通常会使用自定义降级,其中一些字段可以由其他请求状态确定,而其他字段设置为默认值。

     适合使用自定义降级的例子有:

  • cookies
  • 请求参数和请求头
  • 之前的服务请求在当前失败之前的响应

    降级时会静态的从请求范围检索出存根, 但是如果需要的话,比如下面这个例子演示了它如何处理countryCodeFromGeoLookup字段,通常建议在命令实例化时注入它们。

public class HystrixStubbedFallback extends HystrixCommand<HystrixStubbedFallback.UserAccount> {

    private final int customerId;
    private final String countryCodeFromGeoLookup;

    /**
     * @param customerId 用于检索UserAccount
     * @param countryCodeFromGeoLookup
     *            来自HTTP请求geo代码查找的默认国家代码用于回退。
     */
    protected HystrixStubbedFallback(int customerId, String countryCodeFromGeoLookup) {
        super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
        this.customerId = customerId;
        this.countryCodeFromGeoLookup = countryCodeFromGeoLookup;
    }

    @Override
    protected UserAccount run() {
        // 从远程服务获取UserAccount
        //    返回  UserAccountClient.getAccount(customerId);
        throw new RuntimeException("forcing failure for example"); //模拟异常用于触发回滚
    }

    @Override
    protected UserAccount getFallback() {
        /**
         * 返回有一些静态默认值,占位符,还有一个注入的我们将使用的值'countryCodeFromGeoLookup'。
         * 而不是我们从远程服务检索出来的
         */
        return new UserAccount(customerId, "Unknown Name",
                countryCodeFromGeoLookup, true, true, false);
    }

    public static class UserAccount {
        private final int customerId;
        private final String name;
        private final String countryCode;
        private final boolean isFeatureXPermitted;
        private final boolean isFeatureYPermitted;
        private final boolean isFeatureZPermitted;

        UserAccount(int customerId, String name, String countryCode,
                    boolean isFeatureXPermitted,
                    boolean isFeatureYPermitted,
                    boolean isFeatureZPermitted) {
            this.customerId = customerId;
            this.name = name;
            this.countryCode = countryCode;
            this.isFeatureXPermitted = isFeatureXPermitted;
            this.isFeatureYPermitted = isFeatureYPermitted;
            this.isFeatureZPermitted = isFeatureZPermitted;
        }
    }
}

 点击查看完整源码

下面是单元测试示范上面代码的功能:

    @Test
    public void test() {
        CommandWithStubbedFallback command = new CommandWithStubbedFallback(1234, "ca");
        UserAccount account = command.execute();
        assertTrue(command.isFailedExecution());
        assertTrue(command.isResponseFromFallback());
        assertEquals(1234, account.customerId);
        assertEquals("ca", account.countryCode);
        assertEquals(true, account.isFeatureXPermitted);
        assertEquals(true, account.isFeatureYPermitted);
        assertEquals(false, account.isFeatureZPermitted);
    }

HystrixObservableCommand 等价

     对于 HystrixObservableCommand 的静默失败解决方案是调用重写 resumeWithFallback 方法, 用于返回一个可观察者,发送自一定的响应。与前面的例子等价的版本是这样的:   

@Override
protected Observable<Boolean> resumeWithFallback() {
    return Observable.just( new UserAccount(customerId, "Unknown Name",
                                            countryCodeFromGeoLookup, true, true, false) );
}

     但是,如果您期望从 Observable 中发出多个数据项,您可能更感兴趣的是为那些在失败前还没发射原始的 Observable 生成存根。这里有一个简单的例子来说明如何实现这一点——跟踪主Observable 中发出的最后一个数据项,从而使回退知道在何处继续这个顺序:

@Override
protected Observable<Integer> construct() {
    return Observable.just(1, 2, 3)
            .concatWith(Observable.<Integer> error(new RuntimeException("forced error")))
            .doOnNext(new Action1<Integer>() {
                @Override
                public void call(Integer t1) {
                    lastSeen = t1;
                }
                
            })
            .subscribeOn(Schedulers.computation());
}
@Override
protected Observable<Integer> resumeWithFallback() {
    if (lastSeen < 4) {
        return Observable.range(lastSeen + 1, 4 - lastSeen);
    } else {
        return Observable.empty();
    }
}

通过网络缓存降级

     有时如果后端服务失败,可以从缓存服务(如memcached)检索过时的数据版本。 由于回退会超出网络,所以它是另一个可能的失败点,因此它也需要被一个HystrixCommand或HystrixObservableCommand包裹。

(执行示意图)

    在单独的线程池上执行降级(回退)命令是很重要的,否则,如果主命令被隐藏并填充线程池,因为两个命令共享同一个池,将阻止降级(回退)。

    下面将举例怎样使用 CommandWithFallbackViaNetwork 中的 getFallback() 方法执行通过网络缓存降级。

注意:如果回滚失败,将会通过返回null执行“静默失败”的回滚操作。

    通过配置 FallbackViaNetwork 命令,使它运行在一个不同的线程池中,而不是来源于HystrixCommandGroupKey的默认RemoteServiceX它将 HystrixThreadPoolKey.Factory.asKey("RemoteServiceXFallback") 注入到构造函数中。

     这样子 CommandWithFallbackViaNetwork将在RemoteServiceX的线程池上运行,而FallbackViaNetwork将运行在 RemoteServiceXFallback 线程池上。

    示例如下:

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

    protected HystrixFallbackViaNetwork(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"))
                    // 回滚命令使用一个不同的线程池,这样不会被饱和的RemoteServiceX池子阻止执行降级
                    .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("RemoteServiceXFallback")));
            this.id = id;
        }

        @Override
        protected String run() {
            // MemCacheClient.getValue(id);
            // 正常情况应该执行 从缓存(MemCache或者redis)中读取结果
            throw new RuntimeException("the fallback also failed"); //模拟异常,执行降级操作
        }

        @Override
        protected String getFallback() {
            // 如果降级操作失败则会触发这里进行静态失败降级
            return null;
        }
    }
}

点击查看完整源码

主备降级

    很多系统都有双模型--主备或者故障转移。

    有时候备机或故障转移可能回事失败的状态,这种情况下,就会像通过网络缓存降级那种模式。然而如果切换到备机或故障转移是常见的。 例如,推出扩展新功能(有时这是有状态系统和处理代码推送的一部分),然后每次使用辅助系统时,主服务器将处于故障状态、跳闸断路器和触发警报。

    这不是我们希望的情况,如果没有其他原因的话,那就是避免“狼来了”的疲劳,当真正的问题发生时,它会导致警报被忽略。 所以这种情况,解决策略是将主备的转换视为正常和健康的模式并放一个表象。

(流程示意图)

    主服务器和备用服务器的HystrixCommand实现是线程隔离的,因为它们可能正在执行网络流量和业务逻辑。 它们可能都有非常不同的性能特征(通常次要系统是静态缓存),所以隔离的另一个好处是它们可以单独调优。

     您不会公开的显示这两个命令,而是将它们隐藏在另一个HystrixCommand后面,该命令是信号隔离的,它实现了是否调用主服务器命令还是备机命令的条件逻辑。如果主命令和备命令都失败了,那么控制切换到本身正面的降级。

     正面的HystrixCommand可以使用信号隔离,因为它所做的所有工作都是通过另外两个已经线程隔离的hystrix命令进行的。 只要正面的HystrixCommand中 run() 方法不执行任何其他网络调用、重试逻辑或其他“容易出错”的事情,就没有必要再使用另一层线程了。

public class HystrixFacadeWithPrimarySecondary extends HystrixCommand<String> {

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

    private final int id;

    public HystrixFacadeWithPrimarySecondary(int id) {
        super(Setter
                .withGroupKey(HystrixCommandGroupKey.Factory.asKey("SystemX"))
                .andCommandKey(HystrixCommandKey.Factory.asKey("PrimarySecondaryCommand"))
                .andCommandPropertiesDefaults(
                        // 默认想要的是信号隔离 因为已经包装了两个已经线程隔离的命令
                        HystrixCommandProperties.Setter()
                                .withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE)));
        this.id = id;
    }

    @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(
                            // 主服务器超时时间默认为600ms
                            HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(600)));
            this.id = id;
        }

        @Override
        protected String run() {
            // 执行重的“主”服务调用
            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(
                            // 备服务器超时时间默认为100ms
                            HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(100)));
            this.id = id;
        }

        @Override
        protected String run() {
            // 执行快速的备服务调用
            return "responseFromSecondary-" + id;
        }

    }
}

点击查看完整源码

声明

    转帖请注明原贴地址:https://my.oschina.net/u/2342969/blog/1817652

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!