Hystrix 使用例子

转载自爱编码真是太好了

在Hystrix上篇中我们介绍了Hystrix一些基本的特性,在下篇中我们主要来介绍它的一些高级特性Request Context、Request Cache和Request Collapsing。
RequestCache和RequestCollapsing是Hystrix高级的特性,一般来说,我们会在Request范围内使用这些高级特性,需要先初始化HystrixRequestContext进行生命周期的管理,在标准Web应用中我们可以借助过滤器来解决这个问题。

引入依赖

<dependency>
    <groupId>com.netflix.hystrix</groupId>
    <artifactId>hystrix-core</artifactId>
    <version>1.5.9</version>
</dependency>

HystrixRequestContext对象初始化、释放方式

在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>

增加自定义过滤器

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

在应用中使用过滤器的目的是在执行每次请求的时候能及时对HystrixRequestContext进行初始化,在当前请求结束时关闭HystrixRequestContext,及时释放资源。

RequestCache实现及使用方式


假设两个线程发起相同的HTTP请求,Hystrix会把请求参数初始化到ThreadLocal中,两个Command异步执行,每个Command会把请求参数从ThreadLocal中拷贝到Command所在自身的线程中,Command在执行的时候会通过CacheKey优先从缓存中尝试获取是否已有缓存结果,如果命中,直接从HystrixRequestCache返回,如果没有命中,那么需要进行一次真实调用,然后把结果回写到缓存中,在请求范围内共享响应结果。
RequestCache主要有三个优点:
1.在当次请求内对同一个依赖进行重复调用,只会真实调用一次。
2.在当次请求内数据可以保证一致性。
3.可以减少不必要的线程开销。
具体使用:

public classHelloHystrixRequestCacheCommand extends HystrixCommand<Boolean> {
    private final int value;
    public HelloHystrixRequestCacheCommand(final int value) {
       super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("hello")));
        this.value = value;
    }
    protected Boolean run() throws Exception {
        return value == 0 || value % 2 == 0;
    }
    @Override
    protected String getCacheKey() {
        return String.valueOf(value) ;
    }
}

单元测试

@Test
public void testRequestCacheCommand() {
    HystrixRequestContext context =HystrixRequestContext.initializeContext();
    try {
        HelloHystrixRequestCacheCommand command2a = new HelloHystrixRequestCacheCommand(2);
        HelloHystrixRequestCacheCommandcommand2b = new HelloHystrixRequestCacheCommand(2);
        assertTrue(command2a.execute());
        assertFalse(command2a.isResponseFromCache());

        assertTrue(command2b.execute());
        assertTrue(command2b.isResponseFromCache());
    } finally {
        context.shutdown();
    }
    //重新初始化HystrixRequestContext
    context = HystrixRequestContext.initializeContext();
    try {
        HelloHystrixRequestCacheCommand command3b = newHelloHystrixRequestCacheCommand(2);
        assertTrue(command3b.execute());
        assertFalse(command3b.isResponseFromCache());
    } finally {
        context.shutdown();
    }
}

Hystrix通过实现getCacheKey方法来激活缓存机制,我们通过测试用例可以看出,command2a执行时没有命中缓存,而command2b命中缓存,直接从缓存中获取结果,接着对HystrixRequestContext对象执行shutdown,本次请求的生命周期也就随之结束了,因此缓存失效,command3b对象已经不能命中缓存结果。

RequestCollapser的实现及使用方式

使用和不使用请求合并两种场景下线程和网络连接情况:



为什么要进行请求合并?举个例子,有个矿山,每过一段时间都会生产一批矿产出来(质量为卡车载重量的1/100),卡车可以一等到矿产生产出来就马上运走矿产,也可以等到卡车装满再运走矿产,前者一次生产对应卡车一次往返,卡车需要往返100次,而后者只需要往返一次,可以大大减少卡车往返次数。显而易见,利用请求合并可以减少线程和网络连接,开发人员不必单独提供一个批量请求接口就可以完成批量请求。
在Hystrix中进行请求合并也是要付出一定代价的,请求合并会导致依赖服务的请求延迟增高,延迟的最大值是合并时间窗口的大小,默认为10ms,当然我们也可以通过hystrix.collapser.default.timerDelayInMilliseconds属性进行修改,如果请求一次依赖服务的平均响应时间是20ms,那么最坏情况下(合并窗口开始是请求加入等待队列)这次请求响应时间就会变成30ms。在Hystrix中对请求进行合并是否值得主要取决于Command本身,高并发度的接口通过请求合并可以极大提高系统吞吐量,从而基本可以忽略合并时间窗口的开销,反之,并发量较低,对延迟敏感的接口不建议使用请求合并。
请求合并执行流程:



可以看出Hystrix会把多个Command放入Request队列中,一旦满足合并时间窗口周期大小,Hystrix会进行一次批量提交,进行一次依赖服务的调用,通过充写HystrixCollapser父类的mapResponseToRequests方法,将批量返回的请求分发到具体的每次请求中。
具体使用:
public class HelloHystrixCollapserCommand extends HystrixCollapser<List<String>, String, Integer> {
    private final Integer key;
    public HelloHystrixCollapserCommand(Integer key) {
        this.key = key;
    }
    @Override
    public Integer getRequestArgument() {
        return key;
    }
    @Override
    protected HystrixCommand<List<String>> createCommand(Collection<CollapsedRequest<String, Integer>> collapsedRequests) {
        return new BatchCommand(collapsedRequests);
    }
    @Override
    protected void mapResponseToRequests(List<String> batchResponse, Collection<CollapsedRequest<String, Integer>> collapsedRequests) {
        int count = 0;
        for (CollapsedRequest<String, Integer> collapsedRequest : collapsedRequests) {
            collapsedRequest.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("hello"))
                    .andCommandKey(HystrixCommandKey.Factory.asKey("hello_collapser")));
            this.requests = requests;
        }
        @Override
        protected List<String> run() {
            ArrayList<String> response = new ArrayList<String>();
            for (CollapsedRequest<String, Integer> request : requests) {
                response.add("ValueForKey: " + request.getArgument());
            }
            return response;
        }
    }
}

单元测试:

@Test
public void testHelloHystrixCollapser() throws ExecutionException, InterruptedException {
    HystrixRequestContext context = HystrixRequestContext.initializeContext();
    try {
        Future<String> f1 = new HelloHystrixCollapserCommand(1).queue();
        Future<String> f2 = new HelloHystrixCollapserCommand(2).queue();
        Future<String> f3 = new HelloHystrixCollapserCommand(3).queue();
        Future<String> f4 = new HelloHystrixCollapserCommand(4).queue();
        assertEquals("ValueForKey: 1", f1.get());
        assertEquals("ValueForKey: 2", f2.get());
        assertEquals("ValueForKey: 3", f3.get());
        assertEquals("ValueForKey: 4", f4.get());
        assertEquals(2, HystrixRequestLog.getCurrentRequest().getExecutedCommands().size());
        HystrixCommand<?> command = HystrixRequestLog.getCurrentRequest()
                .getExecutedCommands().toArray(new HystrixCommand<?>[1])[0];
        assertEquals("hello_collapser", command.getCommandKey().name());
        assertTrue(command.getExecutionEvents().contains(HystrixEventType.COLLAPSED));
        assertTrue(command.getExecutionEvents().contains(HystrixEventType.SUCCESS));
    } finally {
        context.shutdown();
    }
}

遇到的问题:
官方提供的单元测试是assertEquals(1, HystrixRequestLog.getCurrentRequest().getExecutedCommands().size()),但是在我执行的时候发现测试用例不通过,总是会生成两个Command,通过查看源码发现:


原来在RequestCollapser类中提交请求的时候,第一次请求被添加的时候Hystrix会为CollapserTimer注册一个CollapsedTask对象,立即触发一次CollapsedTask,之后每次达到定时器执行周期并且批量提交的请求数量不为0,那么会触发一次CollapsedTask,然后会调用开发者重写的createCommand方法进行Command的创建。
关于Hystrix的特性,差不多上下篇基本已经涵盖了,在实际生产环境中场景比较复杂,需要根据实际情况进行调整,达到一个最佳的使用效果。

参考

1.https://github.com/Netflix/Hystrix/wiki/How-it-Works#flow3

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,254评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,875评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,682评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,896评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,015评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,152评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,208评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,962评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,388评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,700评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,867评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,551评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,186评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,901评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,689评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,757评论 2 351

推荐阅读更多精彩内容