• 微服务之路(八)spring cloud netflix hystrix


    前言

    Hystrix是什么

    ​ 在分布式系统中,每个服务都可能会调用很多其他服务,被调用的那些服务就是依赖服务,有的时候某些依赖服务出现故障也是很常见的。

    Hystrix 可以让我们在分布式系统中对服务间的调用进行控制,加入一些调用延迟或者依赖故障的容错机制。Hystrix 通过将依赖服务进行资源隔离,进而阻止某个依赖服务出现故障时在整个系统所有的依赖服务调用中进行蔓延;同时Hystrix 还提供故障时的 fallback 降级机制。

    总而言之,Hystrix 通过这些方法帮助我们提升分布式系统的可用性和稳定性。

    Hystrix 的设计原则

    • 对依赖服务调用时出现的调用延迟和调用失败进行控制和容错保护。
    • 在复杂的分布式系统中,阻止某一个依赖服务的故障在整个系统中蔓延。比如某一个服务故障了,导致其它服务也跟着故障。
    • 提供 fail-fast(快速失败)和快速恢复的支持。
    • 提供 fallback 优雅降级的支持。
    • 支持近实时的监控、报警以及运维操作。

    举个例子:

    有一个分布式系统,服务A依赖于服务B,服务B依赖于服务C/D/E。在这样一个成熟的系统内,比如说最多可能只有100个线程资源。正常情况下,40个线程并发调用服务C,各30个线程并发调用 D/E。

    调用服务 C,只需要 20ms,现在因为服务C故障了,比如延迟,或者挂了,此时线程会吊住2s左右。40个线程全部被卡住,由于请求不断涌入,其它的线程也用来调用服务 C,同样也会被卡住。这样导致服务B的线程资源被耗尽,无法接收新的请求,甚至可能因为大量线程不断的运转,导致自己宕机。服务A也挂了。

    Hystrix可以对其进行资源隔离,比如限制服务B只有40个线程调用服务C。当此40个线程被hang住时,其它60个线程依然能正常调用工作。从而确保整个系统不会被拖垮。

    Hystrix 更加细节的设计原则

    • 阻止任何一个依赖服务耗尽所有的资源,比如 tomcat 中的所有线程资源。
    • 避免请求排队和积压,采用限流和 fail fast 来控制故障。
    • 提供 fallback 降级机制来应对故障。
    • 使用资源隔离技术,比如 bulkhead(舱壁隔离技术)、swimlane(泳道技术)、circuit breaker(断路技术)来限制任何一个依赖服务的故障的影响。
    • 通过近实时的统计/监控/报警功能,来提高故障发现的速度。
    • 通过近实时的属性和配置热修改功能,来提高故障处理和恢复的速度。
    • 保护依赖服务调用的所有故障情况,而不仅仅只是网络故障情况。

    主要议题

    • 服务短路(CircuitBreaker)
    • Spring Cloud Hystrix Client
    • Spring Cloud Hystrix Dashboard
    • 整合NetFlixTurbine
    • 问题总结

    主体内容

    一、服务短路(CircuitBreaker)

    以前我们请求响应是这样的:

    加入熔断器(短路,也叫服务降级),就变成这样:

    补充:介绍个小概念:

    • QPS:Query Per Second
      • 计算:经过全链路压测,计算单机极限QPS,集群QPS=单机QPS 乘以 机器数 乘以 可靠性比率。全链路压测除了压极限QPS,还有错误数量。全链路指的是一个完成的业务流程操作。
    • TPS:Transaction Per Second

    一般压测我们采用JMeter,可调整型比较灵活。

    二、Spring Cloud Hystrix Client

    官网:https://github.com/Netflix/Hystrix

    Reactive Java框架:

    • Java9 Flox API
    • Reactor
    • RXJava(Reactive X)

    1.那么我们先从start.spring.io构建一个hystrix客户端项目。

    我检验发现spring boot 2.3.0.RELEASE版本配合spring cloud Hoxton.SR5版本如果不加入以下依赖则会报出org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'org.springframework.cloud.netflix.archaius.ArchaiusAutoConfiguration': ... 错误。

    <dependency>
       <groupId>commons-lang</groupId>
       <artifactId>commons-lang</artifactId>
       <version>2.6</version>
    </dependency>
    

    2.在启动类通过@EnableHystrix激活hystrix。

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.hystrix.EnableHystrix;
    
    @EnableHystrix
    @SpringBootApplication
    public class SpringcloudClientHystrixApplication {
    
       public static void main(String[] args) {
          SpringApplication.run(SpringcloudClientHystrixApplication.class, args);
       }
    
    }
    

    3.然后在项目下创建controller包,包下一个类DemoController.java。

    import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     * @ClassName DemoController
     * @Describe Hystrix Demo Controller
     * @Author 66477
     * @Date 2020/6/423:43
     * @Version 1.0
     */
    @RestController
    public class DemoController {
    
        /**
         * 当{@link #helloWorld()}方法调用超时或失败时,fallback方法{@link #errorContent()}作为替代返回
         * @return
         */
        @GetMapping("helloWorld")
        @HystrixCommand(fallbackMethod = "errorContent")
        public String helloWorld(){
            return "Hello,World";
        }
    
        public String errorContent(){
            return "Fault";
        }
    }
    

    3.然后以debug模式启动,端点位置如下:

    为什么这么打呢?当第一个断点停留导致程序一直无响应,于是它就找到errorContent方法替代。

    我现在访问http://localhost:8080/helloWorld,这时到第一个断点,按下F9放行到下一个断点。这时你会发现页面返回的是“Fault”,而不是“Hello,World”。这就说明方法helloWorld因为超时无响应而由errorContent方法替代了。这个还是自己动手试下印象才会深刻。

    当我不走debug,正常启动时发现它正常输出“Hello,World”。

    那么我们要是想控制helloWorld方法的超时时间,也就是策略方面的东西,该如何实现?先去@HystrixCommand注解中看一下,有个叫做

    HystrixProperty[] commandProperties() default {};
    

    的东西,这个属性可以更改策略。

    那么关于Hystrix配置信息的详细配置可以在:https://github.com/Netflix/Hystrix/wiki/Configuration中找到。我们这里要用到的是超时策略,于是拿出execution.isolation.thread.timeoutInMilliseconds加入到属性配置即可。如下所示,让helloWorld方法超时100的话就进行fallback操作,现在用一个随机数(0-200)来代替helloWorld的睡眠时间以此替代响应时间,每次打印睡眠时间。那么当超过100ms(当然,严格来讲,helloWorld方法本身执行有需要时间),就执行fallback,否则输出helloWorld,这种是注解的方式:

    import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
    import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    import java.util.Random;
    
    /**
     * @ClassName DemoController
     * @Describe Hystrix Demo Controller
     * @Author 66477
     * @Date 2020/6/423:43
     * @Version 1.0
     */
    @RestController
    public class DemoController {
    
        private final static Random random = new Random();
        /**
         * 当{@link #helloWorld()}方法调用超时或失败时,fallback方法{@link #errorContent()}作为替代返回
         * @return
         */
        @GetMapping("helloWorld")
        @HystrixCommand(fallbackMethod = "errorContent",
                        commandProperties = {
                            @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",
                                            value = "100")
        })
        public String helloWorld() throws  Exception{
            //如果随机的时间大于100ms,那么就触发容错
           int value= random.nextInt(200);
           System.out.println("helloWorld() costs" +value+"ms");
           Thread.sleep(value);
            return "Hello,World";
        }
    
        public String errorContent(){
            return "Fault";
        }
    }
    

    执行第一次

    浏览器结果:

    控制台打印:

    helloWorld() costs131ms
    

    执行第二次

    浏览器结果:

    控制台打印:

    helloWorld() costs34ms
    

    OK,证明了以上结果是正确的。

    顺带一提,它的内部大概也是用Future实现的,演示一下demo(这里仅做了解)

    import java.util.Random;
    import java.util.concurrent.*;
    
    /**
     * @ClassName
     * @Describe Future Thread Demo
     * @Author 66477
     * @Date 2020/6/715:55
     * @Version 1.0
     */
    public class FutrueDemo {
        private static final Random random = new Random();
    
        public static void main(String[] args){
            ExecutorService service = Executors.newFixedThreadPool(1);
            Future<String> future = service.submit(()-> {
                int value= random.nextInt(200);
                System.out.println("helloWorld() costs" +value+"ms");
                Thread.sleep(value);
                return "Hello World";
            });
            try {
                future.get(100, TimeUnit.MILLISECONDS);
            } catch (Exception e) {
                //超时流程
                System.out.println("超时保护!");
            }
        }
    }
    

    Reactive X Java执行方式

    import rx.Observer;
    import rx.Single;
    import rx.schedulers.Schedulers;
    import java.util.Random;
    
    /**
     * @ClassName
     * @Describe Reactive X java Demo
     * @Author 66477
     * @Date 2020/6/716:03
     * @Version 1.0
     */
    public class ReactiveXDemo {
        public static void main(String[] args) {
            Random random = new Random();
    
            Single.just("Hello,World")//just发布数据
                     .subscribeOn(Schedulers.immediate())//订阅的线程池 immedate = Thread.currentThread
                     .subscribe(new Observer<String>(){
    
                         @Override
                         public void onCompleted() {
                             System.out.println("执行结束!");//正常执行流程
                         }
    
                         @Override
                         public void onError(Throwable throwable) {
                             System.out.println("熔断保护!");//异常流程(结束)
                         }
    
                         @Override
                         public void onNext(String s) {//数据消费 s=  “Hello ,World”
                            //如果随机时间大于100ms,那么触发容错
                             int value = random.nextInt(200);
    
                             if(value>100){
                                 throw  new RuntimeException("Timeout");
                             }
    
                             System.out.println("helloWorld() costs "+ value +" ms");
                         }
                     });
        }
    }
    

    那么编程方式如何做,这里我直接写在DemoController中了:

    package com.example.springcloudclienthystrix.controller;
    
    import com.netflix.hystrix.HystrixCommandGroupKey;
    import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
    import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    import java.util.Random;
    
    /**
     * @ClassName DemoController
     * @Describe Hystrix Demo Controller
     * @Author 66477
     * @Date 2020/6/423:43
     * @Version 1.0
     */
    @RestController
    public class DemoController {
    
        private final static Random random = new Random();
        /**
         * 当{@link #helloWorld()}方法调用超时或失败时,fallback方法{@link #errorContent()}作为替代返回
         * @return
         */
        @GetMapping("helloWorld")
        @HystrixCommand(fallbackMethod = "errorContent",
                        commandProperties = {
                            @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",
                                            value = "100")
        })
        public String helloWorld() throws  Exception{
            //如果随机的时间大于100ms,那么就触发容错
           int value= random.nextInt(200);
           System.out.println("helloWorld() costs" +value+"ms");
           Thread.sleep(value);
            return "Hello,World";
        }
    
        public String errorContent(){
            return "Fault";
        }
    
    
    
        @GetMapping("helloWorld2")
        public String helloWorld2(){
           return new HelloWorldCommand().execute();
        }
    
        /**
         * 编程的方式
         */
        private class HelloWorldCommand extends com.netflix.hystrix.HystrixCommand<String>{
    
            protected  HelloWorldCommand(){
                super(HystrixCommandGroupKey.Factory.asKey("HelloWorld"),100);
            }
    
            @Override
            protected String run() throws Exception {
                //如果随机的时间大于100ms,那么就触发容错
                int value= random.nextInt(200);
                System.out.println("helloWorld() costs" +value+"ms");
                Thread.sleep(value);
                return "Hello,World";
            }
    
            protected  String getFallback(){
                return DemoController.this.errorContent();
            }
        }
    
    }
    

    我们此刻想要打开/health端点,老样子,springboot2.0以上采用以下方式创建一个类继承WebSecurityConfigurerAdapter,复写它的configure方法,打开安全配置:

    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.config.annotation.web.builders.WebSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    
    @Configuration
    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
         
            @Override
            public void configure(WebSecurity web) throws Exception {
                web.ignoring().antMatchers("/**");
            }
    }
    

    要想看到Health端点详细信息,则需要application.properties文件中加入以下配置:

    sever.port=8080
    management.endpoint.health.show-details=always
    #开启所有端点
    management.endpoints.web.exposure.include=*
    

    那么这里还要提到一个端点,那就是Hystrix端点(/hystrix.stream),要激活这个端点,我们需要把启动类上的@EnableHystrix改成@EnableCircuitBreaker。(这两个区别就是@EnableHystrix是激活springboot的Hystrix,没有一些spring cloud功能,而@EnableCircuitBreaker是激活netflix的熔断机制)重启项目,访问http://localhost:8080/actuator/hystrix.stream,结果如下:

    现在看到ping无值,让我访问刚刚的http://localhost:8080/helloWorld接口,不停地刷几遍,你会发现ping有值了。

    取一个data格式化后:

    {
        "type":"HystrixCommand",
        "name":"helloWorld",
        "group":"DemoController",
        "currentTime":1591521746251,
        "isCircuitBreakerOpen":false,
        "errorPercentage":0,
        "errorCount":0,
        "requestCount":0,
        "rollingCountBadRequests":0,
        "rollingCountCollapsedRequests":0,
        "rollingCountEmit":0,
        "rollingCountExceptionsThrown":0,
        "rollingCountFailure":0,
        "rollingCountFallbackEmit":0,
        "rollingCountFallbackFailure":0,
        "rollingCountFallbackMissing":0,
        "rollingCountFallbackRejection":0,
        "rollingCountFallbackSuccess":0,
        "rollingCountResponsesFromCache":0,
        "rollingCountSemaphoreRejected":0,
        "rollingCountShortCircuited":0,
        "rollingCountSuccess":0,
        "rollingCountThreadPoolRejected":0,
        "rollingCountTimeout":0,
        "currentConcurrentExecutionCount":0,
        "rollingMaxConcurrentExecutionCount":0,
        "latencyExecute_mean":0,
        "latencyExecute":{
            "0":0,
            "25":0,
            "50":0,
            "75":0,
            "90":0,
            "95":0,
            "99":0,
            "100":0,
            "99.5":0
        },
        "latencyTotal_mean":0,
        "latencyTotal":{
            "0":0,
            "25":0,
            "50":0,
            "75":0,
            "90":0,
            "95":0,
            "99":0,
            "100":0,
            "99.5":0
        },
        "propertyValue_circuitBreakerRequestVolumeThreshold":20,
        "propertyValue_circuitBreakerSleepWindowInMilliseconds":5000,
        "propertyValue_circuitBreakerErrorThresholdPercentage":50,
        "propertyValue_circuitBreakerForceOpen":false,
        "propertyValue_circuitBreakerForceClosed":false,
        "propertyValue_circuitBreakerEnabled":true,
        "propertyValue_executionIsolationStrategy":"THREAD",
        "propertyValue_executionIsolationThreadTimeoutInMilliseconds":100,
        "propertyValue_executionTimeoutInMilliseconds":100,
        "propertyValue_executionIsolationThreadInterruptOnTimeout":true,
        "propertyValue_executionIsolationThreadPoolKeyOverride":null,
        "propertyValue_executionIsolationSemaphoreMaxConcurrentRequests":10,
        "propertyValue_fallbackIsolationSemaphoreMaxConcurrentRequests":10,
        "propertyValue_metricsRollingStatisticalWindowInMilliseconds":10000,
        "propertyValue_requestCacheEnabled":true,
        "propertyValue_requestLogEnabled":true,
        "reportingHosts":1,
        "threadPool":"DemoController"
    }
    

    这玩意有什么用呢?我们下面的dashboard即将使用。

    三、Spring Cloud Hystrix Dashboard

    以上的指标都是可以读的,我们去start.spring.io重新构建一个项目,这次采用Hystrix Dashboard。

    同样的导入后,我们需要在pom文件加入以下依赖。

    <dependency>
       <groupId>commons-lang</groupId>
       <artifactId>commons-lang</artifactId>
       <version>2.6</version>
    </dependency>
    

    然后启动类加上@EnableHystrixDashboard

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
    
    @SpringBootApplication
    @EnableHystrixDashboard
    public class SpringcloudDashboardHystrixApplication {
    
       public static void main(String[] args) {
          SpringApplication.run(SpringcloudDashboardHystrixApplication.class, args);
       }
    
    }
    

    application.properties这次就采用7070端口。

    server.port=7070
    

    启动项目,浏览器访问http://localhost:7070/hystrix。结果则如下:

    然后呢,我们把刚刚上面的http://localhost:8080/actuator/hystrix.stream放到红框中:

    随后点击Monitor Stream.出现以下loading,这个有待以后解决:

    四、整合NetFlix Turbine

    首先,了解是什么是Turbine?

    Turbine是聚合服务器发送事件流数据的一个工具,用来监控集群下hystrix的metrics情况。

    要想监控集群的熔断情况,我们则需要将客户端hystrix中的application.properties修改一下:

    sever.port=8080
    #管理端口
    management.port=8081
    management.endpoint.health.show-details=always
    management.endpoints.web.exposure.include=*
    #Eureka配置
    eureka.client.sreviceUrl.defaultZone=http://localhost:9090/eureka
    #定义eureka实例映射管理端口
    eureka.instance.metadate-map.management.port=${management.port}
    #Turbine配置
    turbine.aggregator.cluster-config=CUSTOMERS
    turbine.app-config=customers
    

    然后在仪表盘hystrix dashboard项目的application.properties文件下更改如下:

    server.port=7070
    management.port=7071
    #Eureka配置
    eureka.client.sreviceUrl.defaultZone=http://localhost:9090/eureka
    #定义eureka实例映射管理端口
    eureka.instance.metadate-map.management.port=${management.port}
    

    客户端hystrix启动类上加上@EnableTurbine注解。

    @SpringBootApplication
    @EnableCircuitBreaker
    @EnableTurbine
    public class SpringcloudClientHystrixApplication {
    
       public static void main(String[] args) {
          SpringApplication.run(SpringcloudClientHystrixApplication.class, args);
       } 
    }
    

    重启两个项目,浏览器访问:http://localhost:8080/turbine.stream?cluster=CUSTOMERS,CUSTOMERS就是我们在client中定义的集群名字。结果如下:

    同样的我们交给仪表盘处理,访问http://localhost:7070/hystrix。输入http://localhost:8080/turbine.stream?cluster=CUSTOMERS后点击Monitor Stream,你会发现依然loading,这是因为没有服务与服务的调用,没有数据,同样等之后再研究。

    五、问题总结

    1.ribbon是用来做什么的?只能用作负载均衡吗?

    解答:主要用户客户端负载均衡

    2.Kafka与ActiveMQ?

    解答:ActiveMQ相对来说是比较完善的消息中间件,Kafka在能力上比较灵活,它放弃了不少约束,性能相对比较好。

    3.要提高对java基础的提高,有什么推荐的书籍?

    解答:其实基本经典的书要读。《Java编程思想》、《Effective Java》I 和II 。相关类目,比如集合,要选择性的读。

    4.注释{@link}怎么用,什么作用啊?怎么弄出来的?

    解答:JavaDoc的一部分,通过Java注释生成HTML文档。{@link引用到某个类,比如{@link String}

    @since从哪个版本开始

    @version表示当前版本

    @author作者

    里面嵌入Java代码

    5.spring cloud的config配置,获取到的git中的properties文件的一些属性(比如:my.name),可以直接在其他的spring的xml中使用吗?需要怎么配置?

    解答:利用注解@ImportResource(“abcd.xml”)

    abcd.xml

    <bean id="pserson" class="com.gupao.domain.Person">
    	<property name ="name" value="${my.name}"
    </bean>
    

    启动类:

    @ImportResource("abcd.xml")
    @SpringBootApplication
    public class Main{
    	public static void main(String args[]){
    		SpringApplication.run(Main.class,args);
    	}
    }
    

    6.将实时数据缓存Redis,再由storm去消费,这种做法号码,有没有更好的方案?

    解答:Storm消费的数据如果需要强持久性,Redis相对比DB逊色。

    7.Spring boot 中使用new SpringApplicationBuilder().sources(AppConfig.class)方式启动,是先加载Appconfig还是先加载配置文件?

    解答:AppConfig是一个配置@Configuration Class,那么配置文件是一个外部资源,其实不会相互影响。如果AppConfig增加了@PropertySource或者@PropertySources的话,会优先加载@PropertySource中的配置资源。

  • 相关阅读:
    Java 8新特性探究(九)跟OOM:Permgen说再见吧
    Java 堆内存(Heap)[转]
    hashmap源码
    Java8学习笔记----Lambda表达式 (转)
    双重检查锁定与延迟初始化(转自infoq)
    kvm的live-snapshot
    8.25考试总结
    CF1151FSonya and Informatics
    CF1151div2(Round 553)
    BZOJ3728 PA2014Final Zarowki
  • 原文地址:https://www.cnblogs.com/xusp/p/13063113.html
Copyright © 2020-2023  润新知