• 09 Spring Cloud的集群保护框架Hystrix


    1.概述

      在很多系统架构中都需要考虑横向扩展、单点故障等问题,对于一个庞大的应用集群,部分服务或机器出现问题不可避免。在出现问题时,如何减少故障的影响、保障集群的高可用,成为一个重要的课题。在微服务集群中,不管是服务器,还是客户端,都支持集群部署,本节将介绍Spring Cloud中所用的集群保护框架:Hystrix.

     1.1实际问题

        假设有如下应用程序

      用户范围销售模块,服务通过Web接口或者其他方式访问会员模块,会员模块访问数据库。如果数据库因为某些原因变得不可用,会员模块就会得到“数据库无法访问”的信息,并且会将此信息告知销售模块。在实际问题中,用户会不断地向销售模块发请求,而销售模块这继续请求会员模块,会员模块会不断地请求连接有问题的数据库直到超时,但是还是会有大量的用户请求(包括重试的)会发过来,导致整个应用不堪重负。可能情况会比这个更糟糕,用户的请求不停的发送给销售模块,而由于数据库的原因,会员模块迟迟没有响应,有可能导致整个机房的网络阻塞,受害的不仅仅是这个应用程序,机房中的所有服务都有可能因为网络的原因而瘫痪。

     1.2传统的解决方式

        对于前面遇到的实际问题,可以选择在连接数据库的同时加上超时的配置,让会员模块快速响应。但这仅仅是解决了其中的一种情况,在实际情况中,会员模块有可能出现问题,例如部分线程阻塞、进程假死等,在这些情况下,对外的服务销售模块面对大量的用户与有故障的会员模块,仍然无法独善其身,前面所说的问题依旧会出现。

      在当今的互联网时代,面对大量的用户请求,传统或者单一的解决方式在复杂的急群中显得力不从心,我们需要跟优雅更完善的方案来解决这些问题。

    2.集群容错框架Hystrix

      在分布式环境中,总会有一些被依赖的服务会失效,例如像网络短暂无法访问、服务器宕机等情况。Hystrix是Netflix下的一个java库,Spring Cloud将Hystrix整合到Netflix项目中,Hystrix通过添加延迟阈值以及容错的逻辑,来帮助我们控制分布式系统间组件的交互。Hystrix通过隔离服务间的访问点、停止他们之间的级联故障、提供可回退操作来实现容错。

      例如我们之前讲到的问题,如果数据库层面出现问题,销售模块在访问会员模块时必然会出现超时的情况,此时可以将会员模块隔离开来,销售模块短时间内不再调用会员模块,并且会快速响应用户的请求,从而保证销售模块自身乃至整个集群的稳定性,这是Hystrix可以解决的问题。加入了容错机制,当会员模块或者数据库不可用时,销售模块将对其进行“熔断”,在一定时间内,销售模块不会再调用会员模块,以维持自身的稳定,结构图就变成下面的图了

            

      Hystrix主要实现以下的功能:

      > 当所依赖的网络服务发生延迟或者失败时,对访问的客户端程序进行保护,就像上面的例子对销售模块进行保护一样;

      > 在分布式系统中停止级联故障;

      > 网络服务恢复正常后,可以快速恢复客户端的访问能力;

      > 调用失败时执行服务回退;

      > 可支持实时监控、报警和其他操作。

     3.第一个Hystrix程序

      本例将编写一个简单的Hello World程序,展示Hystrix的基本功能。

     3.1 创建服务提供者

        使用SpringBoot的spring-boot-starter-web项目,创建一个普通的web项目,发布两个测试服务用于测试,项目目录及代码清单如下

    pom.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>com.triheart</groupId>
        <artifactId>hystrixserver</artifactId>
        <version>1.0-SNAPSHOT</version>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
                <version>1.5.4.RELEASE</version>
            </dependency>
        </dependencies>
    </project>
    View Code

    ServerApp.java

    package com.triheart.hystrixserver;
    
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.boot.builder.SpringApplicationBuilder;
    
    /**
     * @author 阿遠
     * Date: 2018/8/29
     * Time: 21:16
     */
    @SpringBootApplication
    public class ServerApp {
        public static void main(String[] args) {
            new SpringApplicationBuilder(ServerApp.class).run(args);
        }
    }
    View Code

        MyController.java

    package com.triheart.hystrixserver;
    
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import javax.servlet.http.HttpServletRequest;
    
    /**
     * @author 阿遠
     * Date: 2018/8/29
     * Time: 21:18
     */
    @RestController
    public class MyController {
    
        @GetMapping("/normalHello")
        public String normalHello(HttpServletRequest request) {
            return "normal Hello!";
        }
    
        @GetMapping("/errorHello")
        public String errorHello(HttpServletRequest request) throws Exception{
            // 模拟处理让线程睡眠10秒
            Thread.sleep(10000);
            return "error Hello";
        }
    }
    View Code

      在MyController控制器中,我们提供了一个正常的服务,提供了一个需要等待10秒才有返回的服务。

     3.2 创建客户端并使用Hystrix

      使用Hystri来请求Web服务,与原来的方式不太一样,新建项目hystrixclient,项目的目录结构如下

      

      添加相关的依赖,pom.xml的代码清单如下

      pom.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>com.triheart</groupId>
        <artifactId>hystrixclient</artifactId>
        <version>1.0-SNAPSHOT</version>
    
        <dependencies>
            <dependency>
                <groupId>com.netflix.hystrix</groupId>
                <artifactId>hystrix-core</artifactId>
                <version>1.5.12</version>
            </dependency>
            <dependency>
                <groupId>org.slf4j</groupId>
                <version>1.7.25</version>
                <artifactId>slf4j-log4j12</artifactId>
            </dependency>
            <dependency>
                <groupId>commons-logging</groupId>
                <artifactId>commons-logging</artifactId>
                <version>1.2</version>
            </dependency>
            <dependency>
                <groupId>org.apache.httpcomponents</groupId>
                <artifactId>httpclient</artifactId>
                <version>4.5.2</version>
            </dependency>
        </dependencies>
    </project>
    View Code

      客户端除了要使用Hystrix外,还会使用HttpClient模块来访问Web服务,因此要加入httpclient的依赖。新建命令类

      HelloCommand.java

    package com.triheart.hystrixclient;
    
    import com.netflix.hystrix.HystrixCommand;
    import com.netflix.hystrix.HystrixCommandGroupKey;
    import org.apache.http.HttpResponse;
    import org.apache.http.client.methods.HttpGet;
    import org.apache.http.impl.client.CloseableHttpClient;
    import org.apache.http.impl.client.HttpClients;
    import org.apache.http.util.EntityUtils;
    
    /**
     * @author 阿遠
     * Date: 2018/8/29
     * Time: 21:43
     */
    public class HelloCommand extends HystrixCommand<String> {
    
        private String url;
        CloseableHttpClient httpclient;
    
        public HelloCommand(String url) {
            // 调用父类的构造器,设置命令组的key,默认用来作为线程池的key
            super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
            // 创建HttpClient客户端
            this.httpclient = HttpClients.createDefault();
            this.url = url;
        }
    
        protected String getFallback() {
            System.out.println("执行 HelloCommand 的回退方法");
            return "error";
        }
    
        protected String run() throws Exception {
            try {
                // 调用 GET 方法请求服务
                HttpGet httpget = new HttpGet(url);
                // 得到服务响应
                HttpResponse response = httpclient.execute(httpget);
                // 解析并返回命令执行结果
                return EntityUtils.toString(response.getEntity());
            } catch (Exception e) {
                e.printStackTrace();
            }
            return "";
        }
    }
    View Code

      新建运行类,执行HelloCommand命令

      HelloMain.java

    package com.triheart.hystrixclient;
    
    /**
     * @author 阿遠
     * Date: 2018/8/29
     * Time: 21:49
     */
    public class HelloMain {
    
        public static void main(String[] args) {
            // 请求正常的服务
            String normalUrl = "http://localhost:8080/normalHello";
            HelloCommand command = new HelloCommand(normalUrl);
            String result = command.execute();
            System.out.println("请求正常的服务,结果:" + result);
        }
    }
    View Code

      正常情况下,直接调用HttpClient的API来请求Web服务,而此处命令类和运行类则通过执行命令来执行调用的工作。在命令类HelloCommond中,实现了父类的run方法,使用HttpClient调用服务的过程,都放到了该方法中。运行HelloMain类,可以看到,结果与平常调用Web服务无异。

      假设我们所调用的Hello服务发生故障,导致无法正常访问,那么对于客户端来说,该如何自保呢?下面将演示调用异常服务的情况

      在HelloCommand类中,加入回退方法。在运行类中,调用发生故障的服务

      HelloErrorMain.java

    package com.triheart.hystrixclient;
    
    /**
     * @author 阿遠
     * Date: 2018/8/29
     * Time: 21:49
     */
    public class HelloErrorMain {
    
        public static void main(String[] args) {
            // 请求异常的服务
            String normalUrl = "http://localhost:8080/errorHello";
            HelloCommand command = new HelloCommand(normalUrl);
            String result = command.execute();
            System.out.println("请求异常的服务,结果:" + result);
        }
    }
    View Code

      运行对应的类,可以看到控制台输入如下

      根据结果可知,回退方法被执行了。本例中调用的errorHello服务,会阻塞10秒才有返回。默认情况下,如果调用Web服务无法再一秒内完成,那么将触发回退。

      回退更像是一个备胎,当请求的服务无法正常返回时,就调用该备胎来实现。这样就可以很好的保护客户端,服务端所提供的服务受网络等条件的制约,如果有服务真的需要10秒才能返回结果,而客户端有没有容错的机制,后果就是客户端将一直等待返回,直到网络超时活着服务有响应,而外界会一直不停地发送请求给客户端,最终导致的结果就是客户端因为请求过多而瘫痪。

    4.Hystrix的运作流程

      在前面的例子中,我们使用Hystrix时仅仅是创建命令并予以执行。这一步骤看是简单,实际上,Hystrix有一套较为复杂的执行逻辑,下面简单说明一下运作流程。

                          

      第一步:在命令执行开始时会做一些准备工作,例如为命令创建相应的线程池等。

      第二步:判断是否打开了缓存,打开了缓存就直接查找缓存并返回结果。

      第三步:判断断路器是否打开,如果打开就表明该链路不可用,直接执行回退方法。

      第四步:判断线程池、信号量(计数器)等条件,例如线程池超负荷,则执行回退方法,否则就去执行命令的内容。

      第五步:执行命令,计算是否要对断路器进行处理,执行完成后,如果满足一定条件,这需要开启断路器。如果执行成功,则返回结果,反之则执行回退。

      整个流程的关键最主要的地方在于断路器是否打开。我们的客户端在使用Hystrix时表面上只是创建了一个命令来执行,实际上Hystrix已经为客户端添加了几层保护。

  • 相关阅读:
    Pandas的高级操作
    Pandas的拼接操作
    Matplotlib基础使用
    股票分析案例
    Pandas处理缺失的数据
    Pandas的基础使用
    python前端之CSS基础--常用样式
    python前端之CSS介绍--选择器
    python实现网站用户名密码自动登录
    Python前端HTML介绍
  • 原文地址:https://www.cnblogs.com/a-yuan/p/9557313.html
Copyright © 2020-2023  润新知