2021-01-25实习日报

Hystrix的工作流程

  1. 构造一个HystrixCommand或HystrixObservableCommand对象,用于封装请求,并在构造方法配置请求被执行需要的参数
  2. 执行命令,Hystrix提供了四种执行命令的方式
  3. 判断是否使用缓存响应请求,若启用了缓存,直接使用缓存响应请求。Hystrix支持请求缓存,但需要用户自定义启动;
  4. 判断熔断器是否打开,如果打开,跳到第8步;
  5. 判断线程池/队列/信号量是否已满,已满则跳到第8步;
  6. 执行HystrixObservableCommand.construct()或HystrixCommand.run(),如果执行失败或者超时,跳到第8步;否则,跳到第9步;
  7. 统计熔断器监控指标
  8. 走Fallback备用逻辑
  9. 返回请求响应

Hystrix的容错处理

启动类上添加一个@EnableHystrix注解

package com.xfgg.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;

@SpringBootApplication
@EnableHystrix
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}

在调用接口的方法上添加一个@HystrixCommand 注解,用于指定依赖服务调用延迟或失败时调用的方法

package com.xfgg.demo.controller;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
public class Usercontroller {
    @Autowired
    RestTemplate restTemplate;
    @GetMapping("/callHello")
    @HystrixCommand(fallbackMethod = "defaultCallHello")
    public String callHello() {
        String result = restTemplate.getForObject("http://localhost:8088/house/hello", String.class);
        return result;
    }
    
    public String defaultCallHello(){
        return "fail";
    }
}

调用失败触发熔断时会用defaultCallHello方法来回退具体的内容
在这里插入图片描述

去掉启动类上的@EnableHystrix注解,重启服务,再次调用接口返回的是500的错误信息

 {
  code: 500,
  message: "I/O error on GET request for
    "http://localhost:8088/house/hello": Connection refused; nested
      exception is java.net.ConnectException: Connection refused
        ", data:
        null
}

Feign整合Hystrix实现容错处理

在属性文件中开启feign对Hystrix的支持

feign.hystrix.enabled=true

两种方式来整合

  1. Fallback方式
    在 Feign 的客户端类上的 @FeignClient 注解中指定 fallback 进行回退
    创建一个 Feign 的客户端类 UserRemoteClient,为其配置 fallback
package com.xfgg.demo.feign;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;

@FeignClient(value = "eureka-client-user-service",fallback = UserRemoteClientFallback.class)
public interface UserRemoteClient {
    @GetMapping("/user/hello")
    String hello();
}

UserRemoteClientFallback 类需要实现 UserRemoteClient 类中所有的方法,返回回退时的内容

package com.xfgg.demo.feign;

import org.springframework.stereotype.Component;

@Component
public class UserRemoteClientFallback implements UserRemoteClient{

    @Override
    public String hello() {
        return "fail";
    }
}

停掉所有 eureka-client-user-service 服务,然后访问 /callHello 接口,这个时候 eureka-client-user-service 服务是不可用的,必然会触发回退,返回的是fail字符
在这种情况下,如果你的接口调用了多个服务的接口,那么只有 eureka-client-user-service 服务会没数据,不会影响别的服务,如果不用 Hystrix 回退处理,整个请求都将失败

{
    code:200,
    message:"",
    data:{
        id:1,
        money:100.12,
        name:"fail"
    }
}
  1. FallbackFactory方式
    fallback已经可以实现服务不可用时回退的功能,如果想知道回退的原因可以使用FallbackFactory来实现回退功能
package com.xfgg.demo.feign;

import feign.hystrix.FallbackFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class UserRemoteClientFallbackFactory implements FallbackFactory<UserRemoteClient> {
    private Logger logger = LoggerFactory.getLogger(UserRemoteClientFallbackFactory.class);

    @Override
    public UserRemoteClient create(final Throwable throwable) {
        logger.error("UserRemoteClient回退",throwable);
        return new UserRemoteClient() {
            @Override
            public String hello() {
                return "fail";
            }
        };
    }
}

FallbackFactory的使用是在@FeignClient注解中用fallbackFactory指定回退类

package com.xfgg.demo.feign;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;

@FeignClient(value = "eureka-client-user-service",fallback = UserRemoteClientFallback.class,fallbackFactory = UserRemoteClientFallbackFactory.class)
public interface UserRemoteClient {
    @GetMapping("/user/hello")
    String hello();
}

Feign中禁用Hystrix

properties文件中

feign.hystrix.enabled=false

或者添加配置类

    @Configuration
    public class FeignConfiguration {
        @Bean 
        @Scope("prototype")
        public Feign.Builder feignBuilder() {
            return Feign.builder();
        }
    }
  

Hystrix的实时监控

添加Actuator的依赖

 <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

启动类中添加 @EnableHystrix 开启 Hystrix
启动过程中出现了sl4j日志文件依赖冲突
在这里插入图片描述
去掉其中一个以来就可以运行了

运行成功将暴露actuator端点
在这里插入图片描述
看到一直在输出“ping:”,出现这种情况是因为还没有数据,等到 HystrixCommand 执行了之后就可以看到具体数据

调用一下 /callHello 接口 http://localhost:8086/callHello,访问之后就可以看到 http://localhost:8086/actuator/hystrix.stream 这个页面中输出的数据了
在这里插入图片描述
使用Dashboard对监控进行图形化展示

在 pom.xml 中添加 dashboard 的依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>

在启动类头部添加注解@EnbaleHystrixDashBoard注解

package com.xfgg.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;

@SpringBootApplication
@EnableHystrixDashboard
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}

属性配置文件中配置服务名称和服务端口

spring.application.name=hystrix-dashboard-demo
server.port=9011

启动服务,访问 http://localhost:9011/hystrix 就可以看到 dashboard 的主页
在这里插入图片描述
在主页中有 3 个地方需要我们填写,第一行是监控的 stream 地址,也就是将之前文字监控信息的地址输入到第一个文本框中。第二行的 Delay 是时间,表示用多少毫秒同步一次监控信息,Title 是标题,这个可以随便填写
在这里插入图片描述
输入完成后就可以点击 Monitor Stream 按钮以图形化的方式查看监控的数据了
在这里插入图片描述
界面监控参数
在这里插入图片描述

完整的Hystrix熔断降级实例

创建eureka注册中心

在这里插入图片描述
配置文件

spring.application.name=spring-cloud-eureka
server.port=8071
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false

eureka.client.service-url.defaultZone=http://master:8071/eureka/
创建服务提供者

在这里插入图片描述
配置文件

spring.application.name=spring-cloud-producer
server.port=8072
eureka.client.service-url.defaultZone=http://localhost:8071/eureka/
创建服务消费者

在这里插入图片描述

ExampleRemote

package com.xfgg.demo.remote;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

@FeignClient(name = "spring-cloud-producer",fallback = ExampleRemoteHystrix.class)
public interface ExampleRemote {
    @RequestMapping("/")
    String echo(@RequestParam(value = "str")String str);
}

ExampleRemoteHystrix

package com.xfgg.demo.remote;

import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestParam;

@Component
public class ExampleRemoteHystrix implements ExampleRemote{
    @Override
    public String echo(@RequestParam(value = "str") String str){
        return "hello"+str+", this message send failed";
    }
}

效果图
在这里插入图片描述在这里插入图片描述

关闭服务提供者然后调用,触发fallback
在这里插入图片描述

通过修改参数了解相关接口性能指标

hystrix的各种配置和各种参数

HystrixCommand
配置方式

通过在方法上添加@HystrixCommand注解并配置注解的参数来配置,但有的时候会有多个Hystrix方法,会出现重复代码所以我们使用@DefaultProperties注解来给整个类的Hystrix方法设置一个默认值

@DefaultProperties(defaultFallback = "fallBack")

在需要返回错误信息的服务调用方法上直接加入注解即可

@HystrixComman

完整代码

@RestController
@RequestMapping("consumer")
@DefaultProperties(defaultFallback = "fallBack")
public class UserController {

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("{id}")
    @HystrixCommand
    public String queryById(@PathVariable("id") Long id){
        String url = "http://user-server/user/"+id;
        String user = restTemplate.getForObject(url, String.class);
        return user;
    }

    public String fallBack(){
        return "服务太拥挤了,请稍后再试!";
    }
}
配置项
  • commandKey:标识一个Hystrix命令,默认会取被注解的方法名,取名要唯一,防止多个方法之间因为键重复而互相影响
  • groupKey:一组Hystrix命令的集合,可不配置
  • threadPoolKey:用来标识一个线程池,没设置的话会取groupkey,第一个方法执行后线程池的属性就固定了,
  • commandProperties:与此命令相关的属性
  • threadPoolProperties:与线程池相关的属性
  • observableExecutionMode:当 Hystrix 命令被包装成 RxJava 的 Observer 异步执行时,此配置指定了 Observable 被执行的模式,默认是 ObservableExecutionMode.EAGER,Observable 会在被创建后立刻执行,而 ObservableExecutionMode.EAGER模式下,则会产生一个 Observable 被 subscribe 后执行。我们常见的命令都是同步执行的,此配置项可以不配置。
  • ignoreExceptions:默认 Hystrix 在执行方法时捕获到异常时执行回退,并统计失败率以修改熔断器的状态,而被忽略的异常则会直接抛到外层,不会执行回退方法,也不会影响熔断器的状态。
  • raiseHystrixExceptions:当配置项包括 HystrixRuntimeException 时,所有的未被忽略的异常都会被包装成 HystrixRuntimeException,配置其他种类的异常好像并没有什么影响。
  • fallbackMethod:方法执行时熔断、错误、超时时会执行的回退方法,需要保持此方法与 Hystrix 方法的签名和返回值一致。
  • defaultFallback:默认回退方法,当配置 fallbackMethod 项时此项没有意义,另外,默认回退方法不能有参数,返回值要与 Hystrix方法的返回值相同。
CommandProperties

Hystrix 的命令属性是由 @HystrixProperty 注解数组构成的,HystrixProperty 由 name 和 value 两个属性,数据类型都是字符串。

线程隔离
  • execution.isolation.strategy:配置请求隔离的方式,有 threadPool(线程池,默认)和 semaphore(信号量)两种,信号量方式高效但配置不灵活,我们一般采用 Java 里常用的线程池方式。
  • execution.timeout.enabled:是否给方法执行设置超时,默认为true
  • execution.isolation.thread.timeoutInMilliseconds:方法执行超时时间,默认值是1000,即1秒,此值根据业务场景配置
  • execution.isolation.thread.interruptOnTimeout: execution.isolation.thread.interruptOnCancel:execution.isolation.thread.interruptOnCancel:是否在方法执行超时/被取消时中断方法。需要注意在 JVM 中我们无法强制中断一个线程,如果 Hystrix 方法里没有处理中断信号的逻辑,那么中断会被忽略。
  • execution.isolation.semaphore.maxConcurrentRequests:默认值是 10,此配置项要在 execution.isolation.strategy 配置为 semaphore 时才会生效,它指定了一个 Hystrix 方法使用信号量隔离时的最大并发数,超过此并发数的请求会被拒绝。信号量隔离的配置就这么一个,也是前文说信号量隔离配置不灵活的原因。
统计器(看得我一愣一愣的)

滑动窗口:Hystrix 的统计器是由滑动窗口来实现的。
桶:bucket 是 Hystrix 统计滑动窗口数据时的最小单位。

  • metrics.rollingStats.timeInMilliseconds:此配置项指定了窗口的大小,单位是 ms,默认值是 1000,即一个滑动窗口默认统计的是 1s 内的请求数据。
  • metrics.healthSnapshot.intervalInMilliseconds:它指定了健康数据统计器(影响 Hystrix 熔断)中每个桶的大小,默认是 500ms,在进行统计时,Hystrix 通过 metrics.rollingStats.timeInMilliseconds / metrics.healthSnapshot.intervalInMilliseconds 计算出桶数,在窗口滑动时,每滑过一个桶的时间间隔时就统计一次当前窗口内请求的失败率。
  • metrics.rollingStats.numBuckets:Hystrix 会将命令执行的结果类型都统计汇总到一块,给上层应用使用或生成统计图表,此配置项即指定了,生成统计数据流时滑动窗口应该拆分的桶数。此配置项最易跟上面的 metrics.healthSnapshot.intervalInMilliseconds 搞混,认为此项影响健康数据流的桶数。 此项默认是 10,并且需要保持此值能被 metrics.rollingStats.timeInMilliseconds 整除。
  • metrics.rollingPercentile.enabled:是否统计方法响应时间百分比,默认为 true 时,Hystrix 会统计方法执行的 1%,10%,50%,90%,99% 等比例请求的平均耗时用以生成统计图表。
  • metrics.rollingPercentile.timeInMilliseconds:统计响应时间百分比时的窗口大小,默认为 60000,即一分钟。
  • metrics.rollingPercentile.numBuckets:统计响应时间百分比时滑动窗口要划分的桶用,默认为6,需要保持能被metrics.rollingPercentile.timeInMilliseconds 整除。
  • metrics.rollingPercentile.bucketSize:统计响应时间百分比时,每个滑动窗口的桶内要保留的请求数,桶内的请求超出这个值后,会覆盖最前面保存的数据。默认值为 100,在统计响应百分比配置全为默认的情况下,每个桶的时间长度为 10s = 60000ms / 6,但这 10s 内只保留最近的 100 条请求的数据
  • circuitBreaker.errorThresholdPercentage:在通过滑动窗口获取到当前时间段内 Hystrix 方法执行的失败率后,就需要根据此配置来判断是否要将熔断器打开了。 此配置项默认值是 50,即窗口时间内超过 50% 的请求失败后会打开熔断器将后续请求快速失败。
  • circuitBreaker.sleepWindowInMilliseconds:熔断器打开后,所有的请求都会快速失败,但何时服务恢复正常就是下一个要面对的问题。熔断器打开时,Hystrix 会在经过一段时间后就放行一条请求,如果这条请求执行成功了,说明此时服务很可能已经恢复了正常,那么会将熔断器关闭,如果此请求执行失败,则认为服务依然不可用,熔断器继续保持打开状态。此配置项指定了熔断器打开后经过多长时间允许一次请求尝试执行,默认值是 5000。
熔断器
  • circuitBreaker.enabled:是否启用熔断器,默认为 true;
  • circuitBreaker.forceOpen: circuitBreaker.forceClosed:是否强制启用/关闭熔断器,保持默认值,不配置即可
  • circuitBreaker.requestVolumeThreshold:启用熔断器功能窗口时间内的最小请求数。试想如果没有这么一个限制,我们配置了 50% 的请求失败会打开熔断器,窗口时间内只有 3 条请求,恰巧两条都失败了,那么熔断器就被打开了,5s 内的请求都被快速失败。此配置项的值需要根据接口的 QPS 进行计算,值太小会有误打开熔断器的可能,值太大超出了时间窗口内的总请求数,则熔断永远也不会被触发。建议设置为 QPS * 窗口秒数 * 60%
    实例
    在服务调用方法中调整该服务调用方法的默认超时时长:
@HystrixCommand(commandProperties = {
        @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000")
})

或者在配置文件中修改

hystrix.commend.default.execution.isolation.thread.timeoutInMilliseconds=3000
hystrix.commend.user-service.execution.isolation.thread.timeoutInMilliseconds=5000
hystrix.commend.queryById.execution.isolation.thread.timeoutInMilliseconds=2000

default部分为更改全局的默认超时时长
user-service部分为设置访问user-service服务器的超时时长
queryById部分为设置queryById方法的超时时长

实例
熔断:当大量请求无法正常访问时(触发服务降级中的超时时长),Hystrix则认为服务有问题,此时将会关闭服务,所有访问该服务的请求全部直接返回错误,不再等待超时时长

即:触发服务降级达到熔断器的阈值则开启熔断
熔断的三种状态
Open、Half Open、Closed
即:
熔断器打开:当所有请求失败的比例到达阀值(默认为50%,请求次数不低于20次),则会出发熔断器打开状态,所有请求直接回复错误

熔断器半开:熔断器打开状态(默认5S),之后进入半开状态,放部分请求通过,测试访问是否成功,如成功则熔断器转入关闭状态,如依旧不成功则熔断器继续进入打开状态并再次进行倒计时

熔断器关闭:所有请求可以正常访问服务
配置参数

circuitBreaker.requestVolumeThreshold=20 //触发熔断的最小请求次数,默认为20
circuitBreaker.sleepWindowInMilliseconds=5000 //休眠时长,默认为5秒
circuitBreaker.errorThresholdPercentage=50 //触发熔断的失败请求最小占比,默认50%

当主类上使用@EnableCircuitBreaker注解,即熔断器方式生效

threadPoolProperties
配置方式

线程池的配置也是由HystrixProperty数组构成,配置方式与命令属性一致

配置项
  • coresize:核心线程池的大小,默认值是 10,一般根据 QPS * 99% cost + redundancy count 计算得出。
  • allowMaximumSizeToDivergeFromCoreSize:是否允许线程池扩展到最大线程池数量,默认为 false;
  • maximumSize:线程池中线程的最大数量,默认值是 10,此配置项单独配置时并不会生效,需要启用 allowMaximumSizeToDivergeFromCoreSize 项。
  • maxQueueSize:作业队列的最大值,默认值为 -1,设置为此值时,队列会使用 SynchronousQueue,此时其 size 为0,Hystrix 不会向队列内存放作业。如果此值设置为一个正的 int 型,队列会使用一个固定 size 的 LinkedBlockingQueue,此时在核心线程池内的线程都在忙碌时,会将作业暂时存放在此队列内,但超出此队列的请求依然会被拒绝。
  • queueSizeRejectionThreshold:由于 maxQueueSize 值在线程池被创建后就固定了大小,如果需要动态修改队列长度的话可以设置此值,即使队列未满,队列内作业达到此值时同样会拒绝请求。此值默认是 5,所以有时候只设置了 maxQueueSize 也不会起作用。
  • keepAliveTimeMinutes:由上面的 maximumSize,我们知道,线程池内核心线程数目都在忙碌,再有新的请求到达时,线程池容量可以被扩充为到最大数量,等到线程池空闲后,多于核心数量的线程还会被回收,此值指定了线程被回收前的存活时间,默认为 2,即两分钟。
实例

编写代码模拟外部接口

    @HystrixCommand(commandKey = "testCoreSizeCommand",groupKey = "testGroup",fallbackMethod = "TimeOutFallBack"
    ,threadPoolProperties = {
            @HystrixProperty(name = "coreSize",value = "2"),
            @HystrixProperty(name = "allowMaximumSizeToDivergeFromCoreSize",value = "true"),
            @HystrixProperty(name = "maximumSize",value = "2"),
            @HystrixProperty(name = "maxQueueSize",value = "2")
    },
    commandProperties = {
            @HystrixProperty(name = "execution.isolation.strategy",value = "THREAD"),
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "16000")
    })
    @Override
    public String coreSizeTest() throws InterruptedException{
        Thread.sleep(5000);
        System.out.println("进来一次");
        return "coreSizeTest finished";
    }
    public String TimeOutFallBack(){
        return "降级sorry,the request is timeout";
    }

每个请求会sleep5秒后返回
编写一个controller,调用service

    @RequestMapping(value = "test_coresize",method = RequestMethod.GET)
    public String getHystrixSizeTest() throws InterruptedException{
        String test = exampleRemote.coreSizeTest();
        return test;
    }

发送请求访问localhost:8080/test_coresize
编写三个线程同时访问接口

public class MyThread extends Thread{
    @Override
    public void run(){
        RestTemplate restTemplate = new RestTemplate();

        HttpHeaders headers = new HttpHeaders();

        headers.add("Content-Type", "application/json; charset=UTF-8");
        headers.add("Accept", "*/*");

        HttpEntity<String> requestEntity = new HttpEntity<>("", headers);

        ResponseEntity<String> exchange = restTemplate.exchange("http://localhost:8080/test_coresize", HttpMethod.GET, requestEntity,String.class);

        String body = exchange.getBody();

        System.out.println("请求结果:"+body);
    }

    public static void main(String[] args) {
        MyThread myThred1 = new MyThread();
        MyThread myThred2 = new MyThread();
        MyThread myThred3 = new MyThread();
        myThred1.start();
        myThred2.start();
        myThred3.start();
    }
}

根据对参数的修改分析

  1. 设置coresize=2,maximumSize=2,未定义降级方法
    两个请求成功,第三个请求返回false,超过了最大线程数之后的请求会被hystrix降级处理,即调用hystrxi中的fallback属性指定的降级方法。如果没有指定降级方法,则会默认返回null
  2. 设置coresize=2,maximumSize=2,定义降级方法:返回信息降级sorry, the request is timeout
    打印了降级方法返回的信息
  3. 设置coresize=2,maximumSize=4,未定义降级方法
    三个请求是一起返回,说明:请求数小于最大线程数,却大于核心线程数的时候,会一起处理所有的请求,当所有请求处理完毕的时候,会将多余核心数量的线程释放。
  4. 设置coresize=2,maxQueueSize=2,maximumSize=4,未定义降级方法
    两个请求先返回,还有一个请求在5秒后返回

总结

  • 如果请求量少,达不到 coreSize,通常会使用核心线程来执行任务。

  • 如果设置了 maxQueueSize,当请求数超过了 coreSize, 通常会把请求放到 queue 里,待核心线程有空闲时消费。

  • 如果 queue 长度无法存储请求,则会创建新线程执行直到达到 maximumSize 最大线程数,多出核心线程数的线程会在空闲时回收。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值