4、断路器 Netflix Hystrix 之 操作指南

本文介绍了如何使用Hystrix库实现服务的熔断和降级,包括同步和异步执行、回退逻辑、请求缓存、线程池配置以及错误处理。Hystrix提供了多种策略,如同步执行、异步执行、观察模式、请求缓存、请求合并等,以确保服务的稳定性和容错性。
摘要由CSDN通过智能技术生成

1、Hello World!

以下是 HystrixCommand 的最简单的 “Hello World” 实现:

public class CommandHelloWorld extends HystrixCommand<String> {

    private final String name;

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

    @Override
    protected String run() {
        // a real example would do work like a network call here
        return "Hello " + name + "!";
    }
}

View Source

HystrixObservableCommand 等效操作:

一个等效的Hello World 解决方案是使用 HystrixObservableCommand 代替 HystrixCommand 来覆盖 construct 方法,如下所示:

public class CommandHelloWorld extends HystrixObservableCommand<String> {

    private final String name;

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

    @Override
    protected Observable<String> construct() {
        return Observable.create(new Observable.OnSubscribe<String>() {
            @Override
            public void call(Subscriber<? super String> observer) {
                try {
                    if (!observer.isUnsubscribed()) {
                        // a real example would do work like a network call here
                        observer.onNext("Hello");
                        observer.onNext(name + "!");
                        observer.onCompleted();
                    }
                } catch (Exception e) {
                    observer.onError(e);
                }
            }
         } ).subscribeOn(Schedulers.io());
    }
}

2、Synchronous Execution

您可以与 execute() 方法同步执行HystrixCommand,如下例所示:

String s = new CommandHelloWorld("World").execute();

此表单的执行通过以下测试:

    @Test
    public void testSynchronous() {
        assertEquals("Hello World!", new CommandHelloWorld("World").execute());
        assertEquals("Hello Bob!", new CommandHelloWorld("Bob").execute());
    }

使用 HystrixObservableCommand 可以达到同样的效果:

HystrixObservableCommand 没有简单的执行等价,但是如果你知道由这样一个命令产生的 Observable 对象必须总是只产生一个值,你可以通过对这个 Observable 对象应用 .toBlocking().toFuture().get() 来模仿 execute 的行为。

3、Asynchronous Execution

你可以使用 queue() 方法异步执行 HystrixCommand,如下所示:

Future<String> fs = new CommandHelloWorld("World").queue();

你可以使用 Future 来检索命令的结果:

String s = fs.get();

以下单元测试演示了该行为:

    @Test
    public void testAsynchronous1() throws Exception {
        assertEquals("Hello World!", new CommandHelloWorld("World").queue().get());
        assertEquals("Hello Bob!", new CommandHelloWorld("Bob").queue().get());
    }

    @Test
    public void testAsynchronous2() throws Exception {

        Future<String> fWorld = new CommandHelloWorld("World").queue();
        Future<String> fBob = new CommandHelloWorld("Bob").queue();

        assertEquals("Hello World!", fWorld.get());
        assertEquals("Hello Bob!", fBob.get());
    }

以下两者是等价的:

String s1 = new CommandHelloWorld("World").execute();
String s2 = new CommandHelloWorld("World").queue().get();

HystrixObservableCommand 相等的:

没有简单的等同于 HystrixObservableCommandqueue,但是如果你知道生产的 Observable 这种命令必须始终只产生一个值,你可以模仿的行为队列通过应用 RxJava 操作 .toBlocking().toFuture() 返回的Observable

4、Reactive Execution

你也可以使用以下方法之一来观察 HystrixCommandObservable 结果:

  • observe()——返回一个立即执行命令的“hot” Observable 对象,不过由于 Observable 对象是通过ReplaySubject 进行过滤的,所以在你有机会订阅之前,不会丢失它发出的任何项
  • toObservable() ——返回一个“cold”的Observable对象,它不会执行这个命令,直到你订阅了这个Observable 对象才会开始发送它的结果
Observable<String> ho = new CommandHelloWorld("World").observe();
// or Observable<String> co = new CommandHelloWorld("World").toObservable();

然后你可以通过订阅 Observable 来获取该命令的值:

ho.subscribe(new Action1<String>() {

    @Override
    public void call(String s) {
         // value emitted here
    }

});

以下单元测试演示了该行为:

@Test
public void testObservable() throws Exception {

    Observable<String> fWorld = new CommandHelloWorld("World").observe();
    Observable<String> fBob = new CommandHelloWorld("Bob").observe();

    // blocking
    assertEquals("Hello World!", fWorld.toBlockingObservable().single());
    assertEquals("Hello Bob!", fBob.toBlockingObservable().single());

    // non-blocking 
    // - this is a verbose anonymous inner-class approach and doesn't do assertions
    fWorld.subscribe(new Observer<String>() {

        @Override
        public void onCompleted() {
            // nothing needed here
        }

        @Override
        public void onError(Throwable e) {
            e.printStackTrace();
        }

        @Override
        public void onNext(String v) {
            System.out.println("onNext: " + v);
        }

    });

    // non-blocking
    // - also verbose anonymous inner-class
    // - ignore errors and onCompleted signal
    fBob.subscribe(new Action1<String>() {

        @Override
        public void call(String v) {
            System.out.println("onNext: " + v);
        }

    });
}

使用Java 8 lambdas/闭包更紧凑;它看起来是这样的:

    fWorld.subscribe((v) -> {
        System.out.println("onNext: " + v);
    })
    
    // - or while also including error handling
    
    fWorld.subscribe((v) -> {
        System.out.println("onNext: " + v);
    }, (exception) -> {
        exception.printStackTrace();
    })

更多关于Observable的信息可以在 http://reactivex.io/documentation/observable.html 上找到

5、Reactive Commands

与其使用上述方法将 HystrixCommand 转换为 Observable,还不如创建一个 HystrixObservableCommand ,它是 HystrixCommand 的专用版本,用来包装 ObservableHystrixObservableCommand 能够包装发出多个项的 Observable,而普通的 HystrixCommands ,即使转换成 Observable ,也不会发出多个项。

在这种情况下,与其用命令逻辑覆盖 run 方法(就像你用普通的 HystrixCommand 那样),不如覆盖construct 方法,这样它就会返回你想包装的 Observable 对象。

  • observe()——返回一个立即执行命令的“hot” Observable 对象,不过由于 Observable 对象是通过ReplaySubject 进行过滤的,所以在你有机会订阅之前,不会丢失它发出的任何项
  • toObservable() ——返回一个“cold”的Observable对象,它不会执行这个命令,直到你订阅了这个Observable 对象才会开始发送它的结果

6、Fallback

您可以通过添加一个 fallback 方法来支持 Hystrix 命令的优雅降级,Hystrix 将调用这个回退方法来获取默认值或主命令失败时的值。你会想要实现一个 fallback ,大多数 Hystrix 命令可能会失败,有几个例外:

  • 执行写操作的命令:
    如果你Hystrix命令是为了做一个写操作,而不是返回一个值(一个命令通常会返回一个空的HystrixCommand或空可观测HystrixObservableCommand)的情况下,实现回退并没有太多意义。如果写入失败,您可能希望将失败传播回调用者。
  • 批处理系统/离线计算
    如果Hystrix命令正在填充缓存,或生成报告,或执行任何类型的脱机计算,通常更合适的做法是将错误传播回调用者,然后调用者可以稍后重试命令,而不是向调用者发送一个无声的响应。

无论您的命令是否有 fallback 方法,所有通常的 Hystrix 状态和断路器 状态/指标 都会更新,以指示命令失败。

在普通的 HystrixCommand 中,您可以通过 getFallback() 实现来实现回退。Hystrix 将对所有类型的失败执行这种回退,比如run()失败超时线程池或信号量拒绝,以及断路器短路。以下示例包含了这样的回退:

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 + "!";
    }
}

View Source

这个命令的 run()方法在每次执行时都会失败。然而,调用者将始终接收命令的 getFallback() 方法返回的值,而不是接收异常:

    @Test
    public void testSynchronous() {
        assertEquals("Hello Failure World!", new CommandHelloFailure("World").execute());
        assertEquals("Hello Failure Bob!", new CommandHelloFailure("Bob").execute());
    }

HystrixObservableCommand 等效:

对于 HystrixObservableCommand ,你可以重写 resumeWithFallback 方法,这样它就会返回第二个Observable ,如果它失败了,这个 Observable 就会接管主 Observable。注意,由于 Observable对象可能会在已经发射了一个或多个项之后失败,所以你的回退不应该假定它会发射 Observable看到的唯一值。

在内部,Hystrix 使用RxJava的 onErrorResumeNext 操作符,在出现错误的情况下,实现主 Observable和备 Observable 之间的无缝转换。

时序图:

@adrianb11好心地提供了一个序列图,演示了超时然后回退是如何工作的。

7、Error Propagation

除了HystrixBadRequestException之外,run() 方法抛出的所有异常都算作失败,并触发 getFallback() 和断路器逻辑。

你可以包装你想在 HystrixBadRequestException 中抛出的异常,并通过 getCause() 检索它。HystrixBadRequestException 用于报告非法参数或非系统故障的用例,这些故障不应该计算在故障度量上,也不应该触发回退逻辑。

HystrixObservableCommand 等效:

HystrixObservableCommand的例子中,不可恢复的错误是通过产生的 ObservableonError 通知返回的,而回退是通过回退到 Hystrix 通过你实现的 resumeWithFallback 方法获得的第二个 Observable 来完成的。

执行异常类型

Failure TypeException classException.causesubject to fallback
FAILUREHystrixRuntimeExceptionunderlying exception (user-controlled)YES
TIMEOUTHystrixRuntimeExceptionj.u.c.TimeoutExceptionYES
SHORT_CIRCUITEDHystrixRuntimeExceptionj.l.RuntimeExceptionYES
THREAD_POOL_REJECTEDHystrixRuntimeExceptionj.u.c.RejectedExecutionExceptionYES
SEMAPHORE_REJECTEDHystrixRuntimeExceptionHystrixRuntimeExceptionYES
BAD_REQUESTHystrixBadRequestExceptionunderlying exception (user-controlled)NO

8、Command Name

默认情况下,Command 名称是从类名派生的:

getClass().getSimpleName();

通过 HystrixCommandHystrixObservableCommand 构造函数来显式定义这个名字:

    public CommandHelloWorld(String name) {
        super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
                .andCommandKey(HystrixCommandKey.Factory.asKey("HelloWorld")));
        this.name = name;
    }

为了保存每个命令分配的 Setter 分配,你也可以像这样缓存 Setter:

    private static final Setter cachedSetter = 
        Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
            .andCommandKey(HystrixCommandKey.Factory.asKey("HelloWorld"));    

    public CommandHelloWorld(String name) {
        super(cachedSetter);
        this.name = name;
    }

HystrixCommandKey是一个接口,可以被实现为enum或常规类,但它也有一个要构造的 helper 工厂类和实习实例,例如:

HystrixCommandKey.Factory.asKey("HelloWorld")

9、Command Group

Hystrix使用 command group 键将报告、警报、仪表板或团队/库所有权等命令分组在一起。

默认情况下,Hystrix使用这个来定义命令线程池,除非定义了单独的线程池。

HystrixCommandGroupKey 是一个接口,可以实现为 enum 或常规类,但它也有帮助工厂类构造和实习实例,如:

HystrixCommandGroupKey.Factory.asKey("ExampleGroup")

10、Command Thread-Pool

线程池键表示用于监视、度量发布、缓存和其他此类用途的 HystrixThreadPoolHystrixCommand 与单个HystrixThreadPool相关联,通过注入HystrixThreadPoolKey来获取,或者默认使用 HystrixCommandGroupKey 创建。

通过 HystrixCommandHystrixObservableCommand 构造函数来显式定义这个名字:

    public CommandHelloWorld(String name) {
        super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
                .andCommandKey(HystrixCommandKey.Factory.asKey("HelloWorld"))
                .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("HelloWorldPool")));
        this.name = name;
    }

HystrixThreadPoolKey是一个接口,可以实现为 enum 或常规类,但它也有帮助工厂类构造和实习实例,如:

HystrixThreadPoolKey.Factory.asKey("HelloWorldPool")

使用 HystrixThreadPoolKey 而不是不同的 HystrixCommandGroupKey 的原因是,多个命令可能属于同一所有权或逻辑功能的“组”,但某些命令可能需要相互隔离。

下面是一个简单的例子:

  • 访问视频元数据的两个命令
  • 组名是" VideoMetadata "
  • 命令A反对资源#1
  • 命令B与资源#2相反

如果命令A变得潜在并饱和其线程池,则不应该阻止命令B执行请求,因为它们每个都碰到不同的后端资源。

因此,从逻辑上讲,我们希望将这些命令组合在一起,但希望它们以不同的方式隔离,并使用HystrixThreadPoolKey 为每个命令提供不同的线程池。

11、Request Cache

通过在 HystrixCommandHystrixObservableCommand 对象上实现 getCacheKey() 方法来启用请求缓存,如下所示:

public class CommandUsingRequestCache extends HystrixCommand<Boolean> {

    private final int value;

    protected CommandUsingRequestCache(int value) {
        super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
        this.value = value;
    }

    @Override
    protected Boolean run() {
        return value == 0 || value % 2 == 0;
    }

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

View Source

因为这取决于请求上下文,所以我们必须初始化 HystrixRequestContext

在一个简单的单元测试中,你可以这样做:

   @Test
   public void testWithoutCacheHits() {
       HystrixRequestContext context = HystrixRequestContext.initializeContext();
       try {
           assertTrue(new CommandUsingRequestCache(2).execute());
           assertFalse(new CommandUsingRequestCache(1).execute());
           assertTrue(new CommandUsingRequestCache(0).execute());
           assertTrue(new CommandUsingRequestCache(58672).execute());
       } finally {
           context.shutdown();
       }
   }

通常,这个上下文将通过封装用户请求或其他生命周期钩子的 ServletFilter 进行初始化和关闭。

下面是一个例子,展示了命令如何从缓存中获取它们的值(以及你如何查询一个对象来知道它的值是否来自缓存)在一个请求上下文中:

   @Test
   public void testWithCacheHits() {
       HystrixRequestContext context = HystrixRequestContext.initializeContext();
       try {
           CommandUsingRequestCache command2a = new CommandUsingRequestCache(2);
           CommandUsingRequestCache command2b = new CommandUsingRequestCache(2);

           assertTrue(command2a.execute());
           // this is the first time we've executed this command with
           // the value of "2" so it should not be from cache
           assertFalse(command2a.isResponseFromCache());

           assertTrue(command2b.execute());
           // this is the second time we've executed this command with
           // the same value so it should return from cache
           assertTrue(command2b.isResponseFromCache());
       } finally {
           context.shutdown();
       }

       // start a new request context
       context = HystrixRequestContext.initializeContext();
       try {
           CommandUsingRequestCache command3b = new CommandUsingRequestCache(2);
           assertTrue(command3b.execute());
           // this is a new request context so this 
           // should not come from cache
           assertFalse(command3b.isResponseFromCache());
       } finally {
           context.shutdown();
       }
   }

12、Request Collapsing

请求合并允许多个请求被批处理成一个 HystrixCommand 实例执行。

折叠器可以使用批处理大小和创建批处理后的运行时间作为执行批处理的触发器。

Hystrix 支持两种类型的请求合并 :请求作用域和全局作用域。这是在折叠器构造时配置的,默认为请求范围。

请求作用域的折叠器每个HystrixRequestContext 收集一个批处理,而全局作用域的折叠器跨多个HystrixRequestContext收集一个批处理。因此,如果您的下游依赖项不能在单个命令调用中处理多个HystrixRequestContexts,那么请求作用域崩溃是合适的选择。

在 Netflix,我们只使用请求范围的折叠器,因为所有当前的系统都是建立在假设单个 HystrixRequestContext 将在每个命令中使用的基础上的。因为这些批只针对每个请求,所以当命令在同一个请求中同时发生且带有不同参数时,折叠是有效的。

下面是一个如何实现请求范围的 HystrixCollapser 的简单示例:

public class CommandCollapserGetValueForKey extends HystrixCollapser<List<String>, String, Integer> {

    private final Integer key;

    public CommandCollapserGetValueForKey(Integer key) {
        this.key = key;
    }

    @Override
    public Integer getRequestArgument() {
        return key;
    }

    @Override
    protected HystrixCommand<List<String>> createCommand(final Collection<CollapsedRequest<String, Integer>> requests) {
        return new BatchCommand(requests);
    }

    @Override
    protected void mapResponseToRequests(List<String> batchResponse, Collection<CollapsedRequest<String, Integer>> requests) {
        int count = 0;
        for (CollapsedRequest<String, Integer> request : requests) {
            request.setResponse(batchResponse.get(count++));
        }
    }

    private static final class BatchCommand extends HystrixCommand<List<String>> {
        private final Collection<CollapsedRequest<String, Integer>> requests;

        private BatchCommand(Collection<CollapsedRequest<String, Integer>> requests) {
                super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
                    .andCommandKey(HystrixCommandKey.Factory.asKey("GetValueForKey")));
            this.requests = requests;
        }

        @Override
        protected List<String> run() {
            ArrayList<String> response = new ArrayList<String>();
            for (CollapsedRequest<String, Integer> request : requests) {
                // artificial response for each argument received in the batch
                response.add("ValueForKey: " + request.getArgument());
            }
            return response;
        }
    }
}

View Source

下面的单元测试展示了如何使用一个折叠器自动批处理 CommandCollapserGetValueForKey 的四个执行到一个单一的 HystrixCommand 执行:

@Test
public void testCollapser() throws Exception {
    HystrixRequestContext context = HystrixRequestContext.initializeContext();
    try {
        Future<String> f1 = new CommandCollapserGetValueForKey(1).queue();
        Future<String> f2 = new CommandCollapserGetValueForKey(2).queue();
        Future<String> f3 = new CommandCollapserGetValueForKey(3).queue();
        Future<String> f4 = new CommandCollapserGetValueForKey(4).queue();

        assertEquals("ValueForKey: 1", f1.get());
        assertEquals("ValueForKey: 2", f2.get());
        assertEquals("ValueForKey: 3", f3.get());
        assertEquals("ValueForKey: 4", f4.get());

        // assert that the batch command 'GetValueForKey' was in fact
        // executed and that it executed only once
        assertEquals(1, HystrixRequestLog.getCurrentRequest().getExecutedCommands().size());
        HystrixCommand<?> command = HystrixRequestLog.getCurrentRequest().getExecutedCommands().toArray(new HystrixCommand<?>[1])[0];
        // assert the command is the one we're expecting
        assertEquals("GetValueForKey", command.getCommandKey().name());
        // confirm that it was a COLLAPSED command execution
        assertTrue(command.getExecutionEvents().contains(HystrixEventType.COLLAPSED));
        // and that it was successful
        assertTrue(command.getExecutionEvents().contains(HystrixEventType.SUCCESS));
    } finally {
        context.shutdown();
    }
}

13、Request Context Setup

要使用请求范围的特性(请求缓存、请求崩溃、请求日志),您必须管理 HystrixRequestContext 生命周期(或实现一个替代的 HystrixConcurrencyStrategy )。

这意味着在请求之前必须执行以下操作:

HystrixRequestContext context = HystrixRequestContext.initializeContext();

然后这个在请求的最后:

context.shutdown();

在一个标准的 Java web 应用程序中,您可以使用Servlet 过滤器来初始化这个生命周期,方法是实现类似这样的过滤器:

public class HystrixRequestContextServletFilter implements Filter {

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
     throws IOException, ServletException {
        HystrixRequestContext context = HystrixRequestContext.initializeContext();
        try {
            chain.doFilter(request, response);
        } finally {
            context.shutdown();
        }
    }
}

您可以通过在 web.xml 中添加如下的部分来启用所有传入流量的过滤器:

    <filter>
      <display-name>HystrixRequestContextServletFilter</display-name>
      <filter-name>HystrixRequestContextServletFilter</filter-name>
      <filter-class>com.netflix.hystrix.contrib.requestservlet.HystrixRequestContextServletFilter</filter-class>
    </filter>
    <filter-mapping>
      <filter-name>HystrixRequestContextServletFilter</filter-name>
      <url-pattern>/*</url-pattern>
   </filter-mapping>

14、Common Patterns:

下面的章节是 HystrixCommandHystrixObservableCommand 的常见用法和使用模式。

15、Fail Fast

最基本的执行是只做一件事,没有回退行为。如果发生任何类型的失败,它将抛出异常。

public class CommandThatFailsFast extends HystrixCommand<String> {

    private final boolean throwException;

    public CommandThatFailsFast(boolean throwException) {
        super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
        this.throwException = throwException;
    }

    @Override
    protected String run() {
        if (throwException) {
            throw new RuntimeException("failure from CommandThatFailsFast");
        } else {
            return "success";
        }
    }

View Source

这些单元测试显示了它的行为:

@Test
public void testSuccess() {
    assertEquals("success", new CommandThatFailsFast(false).execute());
}

@Test
public void testFailure() {
    try {
        new CommandThatFailsFast(true).execute();
        fail("we should have thrown an exception");
    } catch (HystrixRuntimeException e) {
        assertEquals("failure from CommandThatFailsFast", e.getCause().getMessage());
        e.printStackTrace();
    }
}

HystrixObservableCommand 等效:

HystrixObservableCommand 的等效快速失败解决方案包括覆盖 resumeWithFallback 方法,如下所示:

    @Override
    protected Observable<String> resumeWithFallback() {
        if (throwException) {
            return Observable.error(new Throwable("failure from CommandThatFailsFast"));
        } else {
            return Observable.just("success");
        }
    }

16、Fail Silent

静默失败等同于返回空响应或删除功能。它可以通过返回null、空映射、空列表或其他此类响应来实现。

你可以通过在 HystrixCommand 实例上实现 getFallback() 方法来做到这一点:

在这里插入图片描述

public class CommandThatFailsSilently extends HystrixCommand<String> {

    private final boolean throwException;

    public CommandThatFailsSilently(boolean throwException) {
        super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
        this.throwException = throwException;
    }

    @Override
    protected String run() {
        if (throwException) {
            throw new RuntimeException("failure from CommandThatFailsFast");
        } else {
            return "success";
        }
    }

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

View Source

@Test
public void testSuccess() {
    assertEquals("success", new CommandThatFailsSilently(false).execute());
}

@Test
public void testFailure() {
    try {
        assertEquals(null, new CommandThatFailsSilently(true).execute());
    } catch (HystrixRuntimeException e) {
        fail("we should not get an exception as we fail silently with a fallback");
    }
}

另一个返回空列表的实现如下所示:

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

HystrixObservableCommand 相应的操作:

HystrixObservableCommand 的等效失败静默解决方案包括覆盖 resumeWithFallback() 方法,如下:

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

17、Fallback: Static

回退可以返回静态嵌入在代码中的默认值。这不会像“fail silent”那样导致特性或服务被删除,而是导致默认行为的发生。

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

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

HystrixObservableCommand 相应的操作:

HystrixObservableCommand 的等效静态解决方案包括覆盖 resumeWithFallback 方法,如下所示:

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

18、Fallback: Stubbed

当命令返回包含多个字段的复合对象时,通常使用存根回退,其中一些字段可以从其他请求状态确定,而其他字段设置为默认值。

在这些存根值中,你可能会发现state适合使用的地方有:

  • Cookie
  • 请求参数和报头
  • 来自当前服务请求失败之前的响应

你的回退可以从请求范围中静态地获取存根值,但通常建议在命令实例化时注入存根值,以便在需要时使用。下面的例子演示了如何处理 countryCodeFromGeoLookup 字段:

public class CommandWithStubbedFallback extends HystrixCommand<UserAccount> {

    private final int customerId;
    private final String countryCodeFromGeoLookup;

    /**
     * @param customerId
     *            The customerID to retrieve UserAccount for
     * @param countryCodeFromGeoLookup
     *            The default country code from the HTTP request geo code lookup used for fallback.
     */
    protected CommandWithStubbedFallback(int customerId, String countryCodeFromGeoLookup) {
        super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
        this.customerId = customerId;
        this.countryCodeFromGeoLookup = countryCodeFromGeoLookup;
    }

    @Override
    protected UserAccount run() {
        // fetch UserAccount from remote service
        //        return UserAccountClient.getAccount(customerId);
        throw new RuntimeException("forcing failure for example");
    }

    @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);
    }

    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;
        }
    }
}

View Source

下面的单元测试演示了它的行为:

    @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 的等效 stub 解决方案包括覆盖 resumeWithFallback 方法,以返回一个发出 stub响应的 Observable 。与前面例子等价的版本如下所示:

@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();
    }
}

19、Fallback: Cache via Network

有时,如果后端服务失败,可以从缓存服务(如memcached)检索过时的数据。

因为回退将通过网络,这是另一个可能的故障点,所以它也需要用 HystrixCommandHystrixObservableCommand 包装。
在这里插入图片描述
在单独的线程池上执行回退命令非常重要,否则,如果主命令成为潜在的并且填充了线程池,那么如果两个命令共享同一个线程池,就会阻止回退运行。

下面的代码显示了 CommandWithFallbackViaNetwork 如何在其 getFallback() 方法中执行FallbackViaNetwork

请注意,如果回退失败,它也有一个回退,它做“失败沉默”的方法返回null。

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;
        }
    }
}

View Source

20、Primary + Secondary with Fallback

一些系统具有双模式行为 —— 主和辅助,或主和故障转移。

有时,辅助或故障转移被认为是一种故障状态,它仅用于后备;在这些场景中,它将符合上述“通过网络缓存”的相同模式。

然而,如果抛到辅助系统是常见的,如推出新代码的正常组成部分(有时这是有状态系统如何处理的一部分代码推)然后每次使用辅助系统主要处于故障状态,脱扣断路器和解雇警报。

这不是我们想要的行为,如果仅仅是为了避免“狼来了”的疲劳,那么当真正的问题发生时,警报就会被忽略。

因此,在这种情况下,我们的策略是将主次之间的切换视为正常、健康的模式,并在它们面前放置一个立面。

在这里插入图片描述
您不公开这两个命令,而是将它们隐藏在另一个与信号量隔离的 HystrixCommand 后面,该命令实现了调用主命令还是辅助命令的条件逻辑。如果主要的和次要的都发生故障,那么控制将切换到 facade 命令本身的回退。

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

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;
    }

    @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 {
                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 {
                ConfigurationManager.getConfigInstance().setProperty("primarySecondary.usePrimary", false);
                assertEquals("responseFromSecondary-20", new CommandFacadeWithPrimarySecondary(20).execute());
            } finally {
                context.shutdown();
                ConfigurationManager.getConfigInstance().clear();
            }
        }
    }
}

View Source

21、Client Doesn’t Perform Network Access

当包装不执行网络访问,但存在延迟问题或线程开销不可接受的行为时,可以将 executionIsolationStrategy属性设置为 executionIsolationStrategy.SEMAPHORE 和 Hystrix 将使用信号量隔离。

下面展示了如何通过代码将此属性设置为命令的默认属性(您也可以在运行时通过动态属性覆盖它)。

public class CommandUsingSemaphoreIsolation extends HystrixCommand<String> {

    private final int id;

    public CommandUsingSemaphoreIsolation(int id) {
        super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
                // since we're doing an in-memory cache lookup we choose SEMAPHORE isolation
                .andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
                        .withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE)));
        this.id = id;
    }

    @Override
    protected String run() {
        // a real implementation would retrieve data from in memory data structure
        return "ValueFromHashMap_" + id;
    }

}

View Source

22、Get-Set-Get with Request Cache Invalidation

如果你正在实现一个 Get-Set-Get 用例,在这个用例中,Get 接收到足够的请求缓存所需的流量,但有时在另一个命令上发生了一个集合,该集合应该使同一请求中的缓存失效,你可以通过调用HystrixRequestCache.clear() 来使缓存失效。

下面是一个示例实现:

public class CommandUsingRequestCacheInvalidation {

    /* represents a remote data store */
    private static volatile String prefixStoredOnRemoteDataStore = "ValueBeforeSet_";

    public static class GetterCommand extends HystrixCommand<String> {

        private static final HystrixCommandKey GETTER_KEY = HystrixCommandKey.Factory.asKey("GetterCommand");
        private final int id;

        public GetterCommand(int id) {
            super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("GetSetGet"))
                    .andCommandKey(GETTER_KEY));
            this.id = id;
        }

        @Override
        protected String run() {
            return prefixStoredOnRemoteDataStore + id;
        }

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

        /**
         * Allow the cache to be flushed for this object.
         * 
         * @param id
         *            argument that would normally be passed to the command
         */
        public static void flushCache(int id) {
            HystrixRequestCache.getInstance(GETTER_KEY,
                    HystrixConcurrencyStrategyDefault.getInstance()).clear(String.valueOf(id));
        }

    }

    public static class SetterCommand extends HystrixCommand<Void> {

        private final int id;
        private final String prefix;

        public SetterCommand(int id, String prefix) {
            super(HystrixCommandGroupKey.Factory.asKey("GetSetGet"));
            this.id = id;
            this.prefix = prefix;
        }

        @Override
        protected Void run() {
            // persist the value against the datastore
            prefixStoredOnRemoteDataStore = prefix;
            // flush the cache
            GetterCommand.flushCache(id);
            // no return value
            return null;
        }
    }
}

View Source

确认该行为的单元测试是:

        @Test
        public void getGetSetGet() {
            HystrixRequestContext context = HystrixRequestContext.initializeContext();
            try {
                assertEquals("ValueBeforeSet_1", new GetterCommand(1).execute());
                GetterCommand commandAgainstCache = new GetterCommand(1);
                assertEquals("ValueBeforeSet_1", commandAgainstCache.execute());
                // confirm it executed against cache the second time
                assertTrue(commandAgainstCache.isResponseFromCache());
                // set the new value
                new SetterCommand(1, "ValueAfterSet_").execute();
                // fetch it again
                GetterCommand commandAfterSet = new GetterCommand(1);
                // the getter should return with the new prefix, not the value from cache
                assertFalse(commandAfterSet.isResponseFromCache());
                assertEquals("ValueAfterSet_1", commandAfterSet.execute());
            } finally {
                context.shutdown();
            }
        }
    }

23、Migrating a Library to Hystrix

当您迁移现有的客户端库以使用 Hystrix 时,您应该用 HystrixCommand 替换每个“服务方法”。

然后,服务方法应该将调用转发到 HystrixCommand,其中不包含任何额外的业务逻辑。

因此,在迁移之前,服务库可能是这样的:

在这里插入图片描述
迁移之后,库的用户将能够通过委托给 HystrixCommands 的服务 facade 直接或间接地访问HystrixCommands

在这里插入图片描述
原文地址:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值