通过前面一章的学习,我们已经了解了如何利用Hystrix来实现资源隔离,本章我将详细讲解一次请求调用在Hystrix底层的整个执行流程。
一、Command执行流程
我们来分析下下面这张图,它表述了一次Hystrix请求调用的完整底层逻辑:
1.1 创建请求
在Hystrix中,一个Command对象代表了对某个依赖服务的接口发起的一次请求,Command对象一共有两种:
- HystrixCommand: 用于仅仅返回一个结果的调用
- HystrixObservableCommand: 用于可能会返回多条结果的调用
1.2 请求调用
执行Command就可以发起一次对依赖服务接口的调用,一共有四个方法可以选择:execute()
、queue()
、observe()
、toObservable()
,其中execute()和queue()仅仅对HystrixCommand适用:
- execute(): 同步调用,直到接口返回结果,或者抛出异常:
K value = command.execute();
- queue(): 异步调用,返回一个Future,后面可以通过Future获取结果:
Future<K> fValue = command.queue();
- observe(): 订阅一个Observable对象,Observable代表的是依赖服务接口返回的结果,最终获取到的是代表结果的Observable对象的拷贝对象:
Observable<K> ohValue = command.observe();
- toObservable(): 返回一个Observable对象,如果我们订阅这个对象,就会执行command并且获取返回结果:
Observable<K> ocValue = command.toObservable();
execute()实际上会调用queue().get().queue(),接着会调用toObservable().toBlocking().toFuture(),也就是说,无论是哪种执行command的方式,最终都是依赖toObservable()去执行的。
1.3 检查请求缓存
如果这个command开启了请求缓存(request cache),且这个调用的结果在缓存中存在,那么直接从缓存中返回结果。
1.4 检查断路器
检查这个command对应的依赖服务是否开启了断路器(circuit breaker),如果断路器被打开了,那么hystrix就不会执行这个command,而是直接去执行fallback降级机制。
1.5 检查资源池
如果command对应的线程池/Semaphore已经满了,那么也不会执行command,而是直接去调用fallback降级机制。
1.6 执行请求
调用HystrixCommand.run()
或HystrixObservableCommand.construct()
来实际执行这个command,如果执行超时(timeout),那么command所在的线程就会抛出一个TimeoutException
,此时会去执行fallback降级机制(执行出现异常也会执行fallback)。
1.7 健康检查
Hystrix会将对每一个依赖服务的调用事件(成功、失败、拒绝、超时等)发送给断路器(circuit breaker)。断路器就会对调用结果的次数进行统计,并根据这些统计次数来决定是否要进行断路。如果打开了断路器,那么在一段时间内就会直接断路,然后后续某次检查发现调用成功后,又会自动关闭断路器。
1.8 fallback降级
在以下几种情况中,hystrix会调用fallback降级机制:
- 断路器处于打开状态,直接走降级流程;
- 线程池/队列/semaphore满了,请求被reject;
- 执行了请求,但是Command的run()或construct()方法抛出异常或超时;
一般在降级机制中,都建议给出一些默认的返回值,比如静态的一些代码逻辑,或者从内存中的缓存中提取一些数据,尽量在这里不要再进行网络请求了。即使在降级中,一定要进行网络调用,也应该将那个调用放在一个Command中,进行隔离。
二、请求缓存
Hystrix中有一个概念,叫做请求上下文(reqeust context)。通俗点讲,请求上下文就是用于保存一次用户请求的信息。通常一次请求可能涉及对多个依赖服务的调用,某些服务的接口还可能会调用好几次,这样在一次请求上下文中,如果有多个command接口/参数都是一样的,那么可以认为其结果也是一样的。
此时,就可以将第一次command执行后,返回的结果缓存在这个请求上下文中,后续的其它对这个依赖的调用全部从内存中取用缓存结果就可以了,不用在一次请求上下文中反复多次执行一样的command,提升整个请求的性能。
HystrixCommand
和HystrixObservableCommand
都可以指定一个缓存key,然后hystrix会自动进行缓存,接着在同一个request context内,再次访问的时候,就会直接取用缓存。
我们继续以整合服务为例子,看下请求缓存的使用。
2.1 创建HystrixRequestContext
首先,针对每一个请求,我们需要创建一个HystrixRequestContext,可以在web容器的Filter里面实现:
public class HystrixRequestContextServletFilter implements Filter {
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// 创建一个reqeust context对象
HystrixRequestContext context = HystrixRequestContext.initializeContext();
try {
chain.doFilter(request, response);
} finally {
// 关闭request context
context.shutdown();
}
}
}
注意要在Spring容器中注入
HystrixRequestContextServletFilter
对象。
2.2 改写商品批量查询接口
之前整合服务中的商品批量查询接口,如果商品id出现了重复,按照我们之前的业务逻辑,可能就会对重复的productId的商品查询多次。我们可以利用request cache做下优化,一次请求,就是一次request context,对相同的商品查询只能执行一次,其余的都走request cache。
/**
* 封装获取商品信息的请求,采用线程池隔离。
* HystrixCommand中的泛型是请求返回的类型,此处是商品信息ProductInfo
*/
public class GetProductInfoCommand extends HystrixCommand<ProductInfo> {
private final Long productId;
public GetProductInfoCommand(Long productId) {
// 将command与线程池关联
super(HystrixCommandGroupKey.Factory.asKey("GetProductInfoCommandGroup"));
this.productId = productId;
}
/**
* run方法里执行的是真正请求处理逻辑
*/
@Override
protected ProductInfo run() {
// 拿到一个商品id
// 调用商品服务的接口,获取商品id对应的商品的最新数据
// 用HttpClient去调用商品服务的http接口
String url = "http://127.0.0.1:8082/getProductInfo?productId=" + productId;
String response = HttpClientUtils.sendGetRequest(url);
return JSONObject.parseObject(response, ProductInfo.class);
}
/**
* 指定缓存key。
* 每次接口调用结果都会用这个key保存到内存中:<key,结果>
*/
@Override
protected String getCacheKey() {
return "product_info_" + productId;
}
}
上述代码的关键就是覆写了HystrixCommand.getCacheKey
方法,根据商品ID先去缓存中查找是否有结果,有的话直接返回。
三、总结
本章,我讲解了Hystrix中一个请求的整体执行流程,并对请求缓存进行了讲解,重点是Command执行流程。后续章节我将针对请求流程中的各个部分进行专门讲解,包括断路器、降级等。
Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。
它的内容包括:
- 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
- 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
- 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
- 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
- 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
- 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
- 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
- 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw
目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:
想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询
同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。