Hystrix使用

Hello Wrold!


以下是一个最基本的”Hello World”实现,基于HystrixCommand

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 + "!";
    }
}
HystrixObservableCommand 等价的

一个等价的Hello Wrold实现是使用HystrixObservableCommand代替HystrixCommand

重构代码如下:

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 construct() {
        return Observable.create(new Observable.OnSubscribe() {
            @Override
            public void call(Subscribersuper 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());
    }
}

同步执行(Synchronous Execution)


你可以执行HystrixCommand的同步方法execute(),如下所示:

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没有简单的对等的execute,但是你知道HystrixObservableCommand的调用总会得到一个Observable,因此你可以通过Observable调用.toBlocking().toFuture().get()来模仿execute发生。

异步执行(Asynchronous Execution)


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

Future 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 fWorld = new CommandHelloWorld("World").queue();
    Future 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 类似

HystrixObservableCommand没有queue,可以通过返回的Observable调用.toBlocking().toFuture()模仿queue行为。

反应性执行(Reactive Execution)


你还可以使用如下方法将HystrixCommand的结果,作为Observable进行观察:

  • observe() —— 返回一个’hot’Observable它会立即执行,即使Observable通过ReplaySubject进行过滤,在您有机会订阅之前,您不会丢失它所发出的任何项目

  • toObservable() —— 返回一个’cold’Observable,不会立即执行,直到你订阅Observable才会开始发布结果 。

Observable ho = new CommandHelloWorld("World").observe();
// or Observable co = new CommandHelloWorld("World").toObservable()

然后,通过订阅Observable来检索命令的值:

ho.subscribe(new Action1() {

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

});

通过单元测试进行演示:

@Test
public void testObservable() throws Exception {

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

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

    // 非阻塞
    // - 这是一个冗余的匿名内部类,不主张推荐
    fWorld.subscribe(new Observer() {

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

    });

    // 非足赛
    // - 也是使用匿名内部类
    // - 忽视 errors 和 onCompleted 信号
    fBob.subscribe(new Action1() {

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

    });
}

使用JAVA 8 lambdas/closures 更紧凑,如下所示:

fWorld.subscribe((v) -> {
        System.out.println("onNext: " + v);
    })

    // - 可以包含错误处理

    fWorld.subscribe((v) -> {
        System.out.println("onNext: " + v);
    }, (exception) -> {
        exception.printStackTrace();
    })

有关Observable的更多信息,请访问:
http://reactivex.io/documentation/observable.html

反映命令(Reactive Commands)


您可以创建一个HystrixObservableCommand,它是HystrixCommand的专用版本,用于包装Observables,而不是使用上述方法将HystrixCommand转换为ObservableHystrixObservableCommand能够包装发出多个项目的Observable,而普通的HystrixCommands,即使转换为Observables,也不会发出多个项目。

在这种情况下,不要使用命令逻辑覆盖run方法(就像使用普通的HystrixCommand一样),而是覆盖construct方法,以便返回要包装的Observable

获取HystrixObservableCommand的Observable,可以使用下面两种方法之一:

  • observe() —— 返回一个’hot’Observable它会立即订阅底层的Observable,即使Observable通过ReplaySubject进行过滤,在您有机会订阅之前,您不会丢失它所发出的任何项目

  • toObservable() —— 返回一个’cold’Observable,不会立即订阅底层的Observable,直到你订阅Observable才会开始发布结果 。

Fallback


您可以通过在Hystrix添加将调用的回退方法来支持Hystrix命令中的优雅降级,以便在主命令失败时获取默认值。您可能希望为大多数可能导致失败的Hystrix命令实现回退,但有几个例外:

  1. 执行写操作的命令

    • 如果你的Hystrix命令被设计为执行写操作而不是返回一个值(在HystrixCommand的情况下,这样的命令通常会返回一个空格,如果是HystrixObservableCommand,则返回一个空的Observable),实施回退没有多大意义。如果写入失败,您可能希望失败传播回调用者。
  2. 批处理系统/离线计算
    • 如果您的Hystrix命令正在填充缓存,或生成报告,或执行任何类型的离线计算,将错误传播回调用者通常更合适,然后调用者可以稍后重试命令,而不是向调用者发送静默降级的响应

无论您的命令是否具有回退,都会更新所有常见的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在已经发出一个或多个项目后可能会失败,所以您的fallback不应该假设它将发出观察者将看到的唯一值。

在内部,Hystrix使用RxJava onErrorResumeNext运算符在发生错误时在主要和fallback Observable之间无缝过渡。

序列图

sequence diagram演示如何超时,然后回退工作。

### Error Propagation(错误传播)

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

您可以在HystrixBadRequestException中包装要抛出的异常并通过getCause()检索它.HystrixBadRequestException适用于报告非法参数或非系统故障等用例,这些故障不应计入故障指标,也不应触发回退逻辑。

HystrixObservableCommand 类似

在使用HystrixObservableCommand时,通过来自生成的Observable的onError通知返回不可恢复的错误,并通过回退到Hystrix通过您实现的resumeWithFallback方法获得的第二个Observable来完成回退逻辑。

Execution Exception types(执行异常类型)

Failure Type Exception class Exception.cause(异常原因) subject to fallback(触发回退)
FAILURE HystrixRuntimeException 底层exception(用户控制) YES
TIMEOUT HystrixRuntimeException j.u.c.TimeoutException YES
SHORT_CIRCUITED(短路) HystrixRuntimeException j.l.RuntimeException YES
THREAD_POOL_REJECTED(线程池拒绝) HystrixRuntimeException j.u.c.RejectedExecutionException YES
SEMAPHORE_REJECTED(信号量拒绝) HystrixRuntimeException j.l.RuntimeException YES
BAD_REQUEST HystrixBadRequestException 底层exception(用户控制) No

注意:j.u.c 代表并发异常,j.l代表java.lang包的异常。

Command Name(命令名称)

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

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是一个接口,可以实现为枚举或常规类,但是它必须借助工厂Factory来进行构造实例,例如:

HystrixCommandKey.Factory.asKey("HelloWorld")

Command Group(命令组)

Hystrix使用command group key将命令组合在一起,例如报告,警报,仪表板或团队/库所有权.

默认情况下,Hystrix使用内部定义命令线程池(Command Thread-Pool),除非定义了你自定义线程池。

HystrixCommandKey是一个接口,可以实现为枚举或常规类,但是它必须借助工厂Factory来进行构造实例,例如:

HystrixCommandGroupKey.Factory.asKey("ExampleGroup")

Command Thread-Pool(命令线程池)

Thread-Pool key 代表一个HystrixThreadPool主要用于监视,指标发布,缓存和其他此类用途。HystrixCommand与注入其中的HystrixThreadPoolKey检索到的单个HystrixThreadPool相关联,或者它使用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是一个接口,可以实现为枚举或常规类,但是它必须借助工厂Factory来进行构造实例,例如:

HystrixThreadPoolKey.Factory.asKey("HelloWorldPool")

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

这里有一些简单的例子:

  • 用于访问视频元数据的两个命令
  • 组名是“VideoMetadata”
  • command A 针对的是资源 resource #1
  • command B 针对的是资源 resource #2

如果command A变得潜伏并使其线程池(thread-pool)饱和,它不应该阻止如果command B执行请求,因为它们各自命中不同的后端资源。

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

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());
        // 第一次执行,缓存中不存在
        assertFalse(command2a.isResponseFromCache());

        assertTrue(command2b.execute());
        // 第二次执行,直接从缓存中获取
        assertTrue(command2b.isResponseFromCache());
    } finally {
        context.shutdown();
    }

    // 启动一个新的上下文
    context = HystrixRequestContext.initializeContext();
    try {
        CommandUsingRequestCache command3b = new CommandUsingRequestCache(2);
        assertTrue(command3b.execute());
        //由于是一个新的请求,不会走缓存。
        assertFalse(command3b.isResponseFromCache());
    } finally {
        context.shutdown();
    }
}

Request Collapsing(请求压缩)

请求压缩开启后,允许将多个请求批处理到单个HystrixCommand实例中执行。

一个压缩可以使用批次大小和自创建批次以来经过的时间来触发一个批处理的执行。

Hystrix支持两种请求压缩样式:请求范围和全局范围。这是在collapser构造中配置的,默认为请求范围(request-scoped)。

请求范围的压缩通过每个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> createCommand(final Collection> requests) {
        return new BatchCommand(requests);
    }

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

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

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

        @Override
        protected List run() {
            ArrayList response = new ArrayList();
            for (CollapsedRequest request : requests) {
                // 对批次中收到的每个参数进行人工响应
                response.add("ValueForKey: " + request.getArgument());
            }
            return response;
        }
    }
}

View Source

以下单元测试显示如何使用collapser自动将CommandCollapserGetValueForKey的四次执行批量处理为单个HystrixCommand执行:

@Test
public void testCollapser() throws Exception {
    HystrixRequestContext context = HystrixRequestContext.initializeContext();
    try {
        Future f1 = new CommandCollapserGetValueForKey(1).queue();
        Future f2 = new CommandCollapserGetValueForKey(2).queue();
        Future f3 = new CommandCollapserGetValueForKey(3).queue();
        Future 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();
    }
}

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>HystrixRequestContextServletFilterdisplay-name>
  <filter-name>HystrixRequestContextServletFilterfilter-name>
  <filter-class>com.netflix.hystrix.contrib.requestservlet.HystrixRequestContextServletFilterfilter-class>
filter>
<filter-mapping>
  <filter-name>HystrixRequestContextServletFilterfilter-name>
  <url-pattern>/*url-pattern>
filter-mapping>

Common Patterns(常见模式)

以下部分是HystrixCommandHystrixObservableCommand的常用用法和使用模式。

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的等效Fail-Fast解决方案将涉及重写resumeWithFallback方法,如下所示:

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

Fail Silent

静默失败相当于返回空响应或删除功能,它可以通过返回null,空Map,空List或其他此类响应来完成。

您可以通过在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 getFallback() {
    return Collections.emptyList();
}

HystrixObservableCommand Equivalent

HystrixObservableCommand的等效Fail-Silently解决方案将涉及重写resumeWithFallback()方法,如下所示:

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

Fallback: Static

回退可以返回静态嵌入代码中的默认值。这不会导致以“fail silent”的方式删除功能或服务,相反会导致默认行为发生。

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

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

HystrixObservableCommand Equivalent

HystrixObservableCommand的等效静态解决方案将涉及重写resumeWithFallback方法,如下所示:

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

Fallback: Stubbed

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

在你可能觉得适合的地方使用这些存根值的示例:

  • cookie
  • 请求参数和请求头
  • 返回当前服务请求失败之前的一个服务请求的响应

您的回退可以从请求范围静态检索存根值,但通常建议在命令实例化时注入它们以便在需要时使用,例如下面的示例演示了它处理countryCodeFromGeoLookup字段的方式:

ublic class CommandWithStubbedFallback extends HystrixCommand {

    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 Equivalent

HystrixObservableCommand的等效Stubbed解决方案将涉及重写resumeWithFallback方法以返回发出存根响应的Observable。与前一个示例等效的版本如下所示:

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

但是如果您希望从Observable中发出多个项目,那么您可能更感兴趣的是仅为原始Observable在失败之前尚未发出的项目生成存根。 下面是一个简单的示例,说明如何实现此目的 - 它跟踪主Observable发出的最后一项,以便fallback知道在哪里拾取以继续序列:

@Override
protected Observable construct() {
    return Observable.just(1, 2, 3)
            .concatWith(Observable. error(new RuntimeException("forced error")))
            .doOnNext(new Action1() {
                @Override
                public void call(Integer t1) {
                    lastSeen = t1;
                }

            })
            .subscribeOn(Schedulers.computation());
}

@Override
protected Observable resumeWithFallback() {
    if (lastSeen < 4) {
        return Observable.range(lastSeen + 1, 4 - lastSeen);
    } else {
        return Observable.empty();
    }
}

Fallback: Cache via Network

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

由于回退将通过网络,这是另一个可能的失败点,因此它也需要由HystrixCommand或HystrixObservableCommand包装.

Hystrix使用_第1张图片

在单独的线程池上执行fallback命令很重要。否则,如果主命令变为潜在并填充线程池,如果两个命令共享同一个池,这将阻止fallback运行。

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

请注意如果fallback失败,它还有一个fallback,它执行返回null的“fail silent”方法。

要将FallbackViaNetwork命令配置为在与从HystrixCommandGroupKey派生的默认RemoteServiceX不同的线程池上运行,它会将HystrixThreadPoolKey.Factory.asKey("RemoteServiceXFallback")注入构造函数。

这意味着CommandWithFallbackViaNetwork将在名为RemoteServiceX的线程池上运行,FallbackViaNetwork将在名为RemoteServiceXFallback的线程池上运行。

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

Primary + Secondary with Fallback

某些系统具有双模行为 - 主和副,或主要和故障转移。

有时,次要或故障转移被视为失败状态,仅适用于fallback;在那些场景中,它或许适用于类似上面提到的 “Cache via Network”

但是,如果切换到副系统是常见的,例如推出新代码的正常部分(有时这是有状态系统如何处理代码推送的一部分)那么每次使用副系统时,主服务器将处于故障状态 ,断路器短路和发射警报。

这不是理想的行为,如果没有其他原因,除了避免“cry wolf”疲劳,这将导致在发生真正问题时忽略警报。

所以在这种情况下,策略是将主要和次要之间的转换视为正常,健康的模式,并在它们面前放置一个外观。

Hystrix使用_第2张图片

主要和次要HystrixCommand实现是线程隔离的,因为它们正在执行网络拦截和业务逻辑,它们可能各自具有非常不同的性能特征(通常,辅助系统是静态缓存)所以每个单独命令的另一个好处是它们可以单独调整。

您不需要暴露这两个命令,而是将它们隐藏在另一个信号量隔离的HystrixCommand后面,并实现关于是否调用主命令或辅助命令的条件逻辑,如果主要和次要失败,则控制切换到façade命令本身的façade。

外观HystrixCommand可以使用信号量隔离,因为它正在做的所有工作都是通过另外两个已经线程隔离的HystrixCommands来完成。只要façade的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

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

Get-Set-Get with Request Cache Invalidation

如果您正在实现Get-Set-Get用例,其中Get接收到足以请求缓存的流量,但有时会在另一个命令上发生Set,该命令应该使同一请求中的缓存无效,你可以调用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();
    }
}

Migrating a Library to Hystrix

在迁移现有客户端库以使用Hystrix时,应使用HystrixCommand替换每个“service methods”。

服务方法应该将调用转发到HystrixCommand,并且不会在其中包含任何其他业务逻辑。

因此,在迁移之前,服务库可能如下所示:

Hystrix使用_第3张图片

迁移后,库的用户将能够通过委派给·HystrixCommands·的服务外观直接或间接访问HystrixCommands

image

你可能感兴趣的:(SpringCloud)