之前我写过一篇博文, 通过案例了解 Hystrix 的各种基本使用方式, 在这篇文章里, 我们是通过 Hystrix 调用正常工作的服务, 也就是说, Hytrix 的保护机制并没有起作用, 这里我们将在 HystrixProtectDemo.java 里演示调用不可用的服务时, hystrix 启动保护机制的流程. 这个类是基于 NormalHystrixDemo.java 改写的, 只是在其中增加了 getFallback 方法, 代码如下.
- // 省略必要的 package 和 import 代码
- public class HystrixProtectDemo extends HystrixCommand<String> {
- RestClient client = null;
- HttpRequest request = null;
- // 构造函数很相似
- public HystrixDemoProtectDemo() {
- super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
- }
- //initRestClient 方法没变
- private void initRestClient(){
- // 和 NormalHystrixDemo.java 一样, 具体请参考代码
- }
- //run 方法也没变
- protected String run() {
- // 和 NormalHystrixDemo.java 一样, 具体请参考代码
- }
- // 这次多个了 getFallback 方法, 一旦出错, 会调用其中的代码
- protected String getFallback() {
- // 省略跳转到错误提示页面的动作
- return "Call Unavailable Service.";
- }
- //main 函数
- public static void main(String[] args) {
- HystrixDemoProtectDemo normalDemo = new HystrixDemoProtectDemo();
- normalDemo.initRestClient();
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- String result = normalDemo.execute();
- System.out.println("Call available function, result is:" + result);
- }
- }
这个类里的构造函数和 NormalHystrixDemo.java 很相似, 而 initRestClient 和 run 方法根本没变, 所以就不再详细给出了.
在第 18 行里, 我们重写了 HystrixCommand 类的 getFallback 方法, 在其中定义了一旦访问出错的动作, 这里仅仅是输出一段话, 在实际的项目里, 可以跳转到相应的错误提示页面.
而 main 函数里的代码和 NormalHystrixDemo.java 里的完全一样, 只是, 在运行这段代码前无需运行 HystrixServerDemo 项目的启动类, 这样服务一定是调用不到的. 运行本段代码后, 我们能看到如下的结果.
In run
Call available function, result is:Call Unavailable Service.
从第 2 行的输出上, 我们能确认, 一旦调用服务出错, Hystrix 处理类能自动地调用 getFallback 方法.
如果这里没有定义 getFallback 方法, 那么一旦服务不可用, 那么用户可能在连接超时之后, 在浏览器里看到一串毫无意义的内容, 这样用户体验就很差, 如果整个系统的其它容错措施也没到位, 甚至就有可能导致当前和下游模块瘫痪.
相反, 在这里由于我们在 hystirx 提供的 getFallback 方法里做了充分的准备, 那么一旦出现错误, 这段错误处理的代码能被立即触发, 其效果就相当于熔断后继的处理流程.
由 getFallback 出面, 友好地告知用户出问题了, 以及后继该如何处理, 这样一方面能及时熔断请求从而保护整个系统, 另一方面不会造成因体验过差而用户大规模流失的情况.
如果每次请求都要走后台应用程序乃至再到数据库检索一下数据, 这对服务器的压力太大, 有时候这一因素甚至会成为影响网站服务性能的瓶颈. 所以, 大多数网站会把一些无需实时更新的数据放入缓存, 前端请求是到缓存里拿数据.
Hystrix 在提供保护性便利的同时, 也能支持缓存的功能, 在下面的 HystrixCacheDemo.java 里, 我们将演示 Hystrix 从缓存中读取数据的步骤, 代码如下.
- // 省略必要的 package 和 import 代码
- public class HystrixCacheDemo extends HystrixCommand<String> {
- // 用户 id
- Integer id;
- // 用一个 HashMap 来模拟数据库里的数据
- private HashMap<Integer,String> userList = new HashMap<Integer,String>();
- // 构造函数
- public HystrixCacheDemo(Integer id) {
- super(HystrixCommandGroupKey.Factory.asKey("RequestCacheCommand"));
- this.id = id;
- userList.put(1, "Tom");
- }
在第 3 行里, 我们定义了一个用户 id, 并在第 6 行定义了一个存放用户信息的 HashMap.
在第 8 行的构造函数里, 我们在第 10 行里用参数 id 来初始化了本对象的 id 属性, 并在第 11 行里, 通过 put 方法模拟地构建了一个用户, 在项目里, 用户的信息其实是存在数据库里的.
- protected String run() {
- System.out.println("In run");
- return userList.get(id);
- }
如果不走缓存的话, 第 13 行定义 run 函数将会被 execute 方法触发, 在其中的第 15 行里, 我们通过 get 方法从 userList 这个 HashMap 里获得一条用户数据, 这里我们用 get 方法来模拟根据 id 从数据库里获取数据的诸多动作.
- protected String getCacheKey() {
- return String.valueOf(id);
- }
第 17 行定义的 getCacheKey 方法是 Hystrix 实现缓存的关键, 在其中我们可以定义 "缓存对象的标准", 具体而言, 我们在这里是返回 String.valueOf(id), 也就是说, 如果第二个 HystrixCacheDemo 对象和第一个对象具有相同的 String.valueOf(id)的值, 那么第二个对象在调用 execute 方法时, 就可以走缓存.
- public static void main(String[] args) {
- // 初始化上下文, 否则无法用缓存机制
- HystrixRequestContext context = HystrixRequestContext.initializeContext();
- // 定义两个具有相同 id 的对象
- HystrixCacheDemo cacheDemo1 = new HystrixCacheDemo(1);
- HystrixCacheDemo cacheDemo2 = new HystrixCacheDemo(1);
- // 第一个对象调用的是 run 方法, 没有走缓存
- System.out.println("the result for cacheDemo1 is:" + cacheDemo1.execute());
- System.out.println("whether get from cache:" + cacheDemo1.isResponseFromCache);
- // 第二个对象, 由于和第一个对象具有相同的 id, 所以走缓存
- System.out.println("the result for cacheDemo2 is:" + cacheDemo2.execute());
- System.out.println("whether get from cache:" + cacheDemo2.isResponseFromCache);
- // 销魂上下文, 以清空缓存
- context.shutdown();
- // 再次初始化上下文, 但由于缓存已清, 所以 cacheDemo3 没走缓存
- context = HystrixRequestContext.initializeContext();
- HystrixCacheDemo cacheDemo3 = new HystrixCacheDemo(1);
- System.out.println("the result for 3 is:" + cacheDemo3.execute());
- System.out.println("whether get from cache:" + cacheDemo3.isResponseFromCache);
- context.shutdown();
在第 20 行的 main 方法里, 我们定义了如下的主要逻辑.
第一, 在第 22 行, 通过 initializeContext 方法, 初始化了上下文, 这样才能启动缓存机制., 在第 24 和 25 行里, 我们创建了两个不同名的, 但相同 id 的 HystrixCacheDemo 对象.
第二, 在第 27 行里, 我们通过 cacheDemo1 对象的 execute 方法, 根据 id 查找用户, 虽然我们在这里是通过 run 方法里第 15 行的 get 方法从 HashMap 里取数据, 但大家可以把这想象成从数据表里取数据.
第三, 在第 30 行里, 我们调用了 cacheDemo2 对象的 execute 方法, 由于它和 cacheDemo1 对象具有相同的 id, 所以这里并没有走 execute 方法, 而是直接从保存 cacheDemo1.execute 的缓存里拿数据, 这就可以避免因多次访问数据库而造成了系统损耗.
第四, 我们在第 33 行销毁了上下文, 并在第 35 行里重新初始化了上下文, 之后, 虽然在第 36 行定义的 cacheDemo3 对象的 id 依然是 1, 但由于上下文对象被重置过, 其中的缓存也被清空, 所以在第 37 里执行的 execute 方法并没有走缓存.
运行上述代码, 我们能看到如下的输出, 这些打印结果能很好地验证上述对主要流程的说明.
- In run
- the result for cacheDemo1 is:Tom
- whether get from cache:false
- the result for cacheDemo2 is:Tom
- whether get from cache:true
- In run
- the result for 3 is:Tom
这里请大家注意, 在缓存相关的 getCacheKey 方法里, 我们不是定义 "保存缓存值" 的逻辑, 而是定义 "缓存对象的标准", 初学者经常会混淆这点. 具体而言, 在这里的 getCacheKey 方法里, 我们并没有保存 id 是 1 的 User 对象的值 (这里是 Tom), 而是定义了如下的标准: 只要两个(或多个)HystrixCacheDemo 对象具有相同的 String.valueOf(id) 的值, 而且缓存中也已经存有 id 的 1 的结果值, 那么后继对象则可以直接从缓存里读数据.
在上文里, 我们演示了通过 Hystrix 调用可用以及不可用服务的运行结果, 并在调用过程中引入了缓存机制, 这里, 我们将在上述案例的基础上归纳 Hystrix 的一般工作流程.
第一, 我们可以通过 extends HystrixCommand<T > 的方式, 让一个类具备 Hystrix 保护机制的特性, 其中 T 是泛型, 在上述案例中我们用到的是 String.
第二, 一旦继承了 HystrixCommand 之后, 我们就可以通过重写 run 方法和 getFallback 方法来定义调用 "可用" 和 "不可用" 服务的业务功能代码, 其中, 这两个方法的返回值需要和第一步里定义的泛型 T 一致. 而在项目里, 我们一般在 getFallback 方法里, 定义 "服务不可用" 时的保护措施(也就是后文里将要提到的降级措施).
第三, 我们还可以通过缓存机制来降低并发情况下对服务器的压力, 在 Hystrix 里, 我们可以在 getCacheKey 里定义 "判断可以走缓存对象的标准".
在使用缓存是, 请注意两点, 第一需要开启上下文, 第二, Hystrix 会根据定义在类里的属性判断多次调用的对象是否是同一个, 如果是, 而且之前被调用过, 则可以走缓存.
本文谢绝转载.
来源: https://www.cnblogs.com/JavaArchitect/p/9557991.html