# 102. 为商品服务接口调用增加 stubbed fallback 降级机制

注意

在这章节中,可能已经注意到了,和视频中内容可能有不一致的地方, 是因为我发现视频中其实也是跟着官网教程走的,这个还不是最重要的。

最重要的问题是:视频中根本就没有讲解在商品缓存这个背景下的一个比较合理的场景来演示, 只是简单的把官网的例子用商品信息来表述了;

使用框架最重要的是场景,可惜在 hystrix 这章节中失望了,场景太少太少了

所以在 hystrix 中,本人觉得没有什么价值的演示不会做,跟着翻译官网的教程视乎更有意义, 当然还是会与视频内容主题对应

官网直达 (opens new window)

stubbed:译为残缺的

什么意思?首先要明白,stubbed fallback 不是一种新的功能,只是降级机制的一种使用/应用方式, 如 java 中的注解功能,spring 就用来做各种功能。这只是一种使用方式

当你返回包含多个字段的复合对象时,通常使用 stubbed fallback,其中一些字段可以从其他请求状态确定, 而其他字段则设置为默认值。这些值可以再以下地方获取到:

  • cookies
  • 请求参数和请求头
  • 在当前服务请求失败之前,来自以前服务请求的响应;也就是本地的一些缓存

比如下面这个降级机制:

# 返回单个值的 HystrixCommand stubbed 使用方式

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 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;
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54

HystrixObservableCommand 则可以使用下面的方式

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

# 批量获取 HystrixObservableCommand 的 stubbed 使用方式

如果在批量获取中,你可能想知道在失败之前获取到了哪条数据?并在失败降级处理时接着这条数据处理

// 请忽略类名,为了快速随意复制的一个类修改的
public class CommandThatFailsFast2 extends HystrixObservableCommand<Integer> {

    private int lastSeen = 0;

    public CommandThatFailsFast2() {
        super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
    }

    @Override
    protected Observable<Integer> construct() {
        // 这句代码的意思是:产生了 1,2,3 个数值(模拟请求),在第 4 个请求时抛出了一个异常
        return Observable.just(1, 2, 3)
                .concatWith(Observable.error(new RuntimeException("forced error")))
                .doOnNext(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();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

测试代码

@Test
public void fun4() {
    CommandThatFailsFast2 commandThatFailsFast2 = new CommandThatFailsFast2();
    Iterator<Integer> iterator = commandThatFailsFast2.observe().toBlocking().getIterator();
    while (iterator.hasNext()) {
        System.out.println("响应结果:" + iterator.next());
    }
}
1
2
3
4
5
6
7
8

输出日志如下,可以看出来 HystrixObservableCommand 是一条一条消费的,已经消费过的不影响,没有消费过的可以通过降级机制进行续上

响应结果:1
响应结果:2
响应结果:3
22:42:30.964 [RxComputationScheduler-2] DEBUG com.netflix.hystrix.AbstractCommand - Error executing HystrixCommand.run(). Proceeding to fallback logic ...
java.lang.RuntimeException: forced error
	at cn.mrcode.cachepdp.eshop.cache.ha.CommandThatFailsFast2.construct(CommandThatFailsFast2.java:25)
...
响应结果:4
1
2
3
4
5
6
7
8

# 简述视频示例

视频中修改了之前获取单个商品详情信息的 GetProductCommand, 在降级机制中使用了传递进来的商品 id 参数,从本地缓存获取了其他的商品信息,返回的