spring boot 2.0.3+spring cloud熔断器Hystrix

分布式系统中服务与服务之间的依赖错综复杂,一种不可避免的情况就是某些服务会出现故障,导致依赖于他们的其他服务出现远程调度的线程阻塞。某个服务的单个点的请求故障会导致用户的请求处于阻塞状态,最终的结果是整个服务的线程资源消耗殆尽。由于服务的依赖性,会导致依赖于该故障服务的其他服务也处于线程阻塞状态,最终导致这些服务的线程资源消耗殆尽,知道不可用,从而导致整个服务系统不可用,即雪崩效应。为了防止雪崩效应,产生了熔断器模型。

Hystrix是Netflix公司开源的一个项目,提供了熔断器功能,能阻止分布式系统中出现联动故障。Hystrix是通过隔离服务的访问点阻止联动故障的,并提供了故障解决方案,从而提高了整个分布式系统的弹性。

当服务的某个API接口的失败次数在一定时间内小于设定的阈值时,熔断器处于关闭状态,该API接口正常提供服务。当该API接口处理请求的失败次数大于设定的阈值时,hystrix判定该API接口出现了故障,打开熔断器,这时请求该API接口会执行快速失败的逻辑(即fallback回退的逻辑),不执行业务逻辑,请求的线程不会处于阻塞状态。处于打开状态的熔断器,一段时间后会处于半打开状态,并将一定数量的请求执行正常逻辑,剩余的请求会执行快速失败,若执行正常逻辑的请求失败了,则熔断器继续打开,若成功了,则关闭熔断器。这样熔断器就具有了自我修复的能力。

最开始集成的时候,发现springBoot1.5.6 和springBoot2.0.3包不兼容,导致后面一致报错,因为工作环境用的还是srpingBoot1.5.6版本,所以练习的这个是最新版本,Hystrix在1.5版本是要自己加载的,但是2.0.3版本已经包含在spring-cloud-starter-netflix-hystrix 包里面了,我是用maven仓库,工作空间都在一起,后面分离了工作空间和maven仓库,让两个版本的依赖仓库分开 ,果然之后一切顺利,这也是比较坑的地方,

先写三个项目springcloud-eureka-server 服务注册中心  springcloud-euraka-client生产者 euraka-consumer-ribbonAndfeign-hystrix 消费者调用 ,并且ribbon 和 feign负载均衡放在一个项目里

主要是euraka-consumer-ribbonAndfeign-hystrix 项目, 先贴出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.example.euraka</groupId>
    <artifactId>springCloudEurakaConsumer</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>

    <name>springCloudEurakaConsumer</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
         <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
         <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
         <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency> 
            <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
<!--         <dependency>
          <groupId>org.apache.commons</groupId>
          <artifactId>commons-lang3</artifactId>
          <version>3.8</version>
        </dependency> -->
    </dependencies>

     <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
  
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>com.spotify</groupId>
                <artifactId>docker-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>


</project>
在项目启动类加上@EnableHystrix注解,开启hystrix熔断器功能,当然我这里加的比较多,如果是springBootCloudApplicateion也可以,主要是在超时配置的时候,会有过服务重试的操作,这个操作研究了一段时间,基本上就是超时的策略,会去调用其它服务实例,再次请求服务等,详细介绍在配置文件里

@SpringBootApplication
@Configuration
@EnableEurekaClient
@EnableDiscoveryClient
@EnableFeignClients
@EnableHystrix
public class DemoServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoServerApplication.class, args);
    }
    
/*    SpringCloud重试机制配置
    首先声明一点,这里的重试并不是报错以后的重试,而是负载均衡客户端发现远程请求实例不可到达后,去重试其他实例。
    其实也是Ribbon+RestTemplate的重试机制,因为是SpringCloud默认方式 
         对于整合了Ribbon的RestTemplate,例如一个RestTemplate添加了@LoadBalanced 注解
    */
    @LoadBalanced
    @Bean
    public RestTemplate restTemplate() {
        /*HttpComponentsClientHttpRequestFactory httpRequestFactory = new HttpComponentsClientHttpRequestFactory();
          httpRequestFactory.setReadTimeout(3000);
          httpRequestFactory.setConnectTimeout(2000);
        return new RestTemplate(httpRequestFactory);*/
        return new RestTemplate();
    }
    //feign重试机制,feign默认是通过自己包下的Retryer进行重试配置,默认是5次,优先配置文件配置的重试策略
    //如果没有配置文件,下面这样设置至少也会重试两次,本实例之外的一次调用
    @Bean
    public Retryer feignRetryer(){
       // return new Retryer.Default(100,TimeUnit.SECONDS.toMillis(1),5);//默认是5次
        return Retryer.NEVER_RETRY; //feign取消重试
    }
    

然后在CustomerRibbonController里,实现Ribbon+RestTemplate 负载均衡实现 ,其实也比较简单,因为我之前测试别的原因,我写了两个方法

@RestController
public class CustomerRibbonController {

    @Autowired
    RestTemplate restTemplate;
    @Autowired
    RibbonService  ribbonService;
    
    //这里写了一个service 层来表示业务逻辑,当然与下面worldCustomer的方式调用也是可以的,简洁一些
    // hellolmc 方法调用超时之后, 调用fallbackMethod = "hiError" 方法,自动熔断或降级
    @GetMapping("/hilmc")
    public String hellolmc(){
        return ribbonService.consumer();
    }
    
    //此方法可以正常调用,生产者并没有超时
    @RequestMapping(value = "/helloCustomer",method = RequestMethod.GET)
    @HystrixCommand(fallbackMethod="fallback")
    public String worldCustomer(){
        return restTemplate.getForObject("http://springcloud-euraka-client/world", String.class);
    }
    
    public String fallback() {
        System.out.println("fallbackMethod worldCustomer");
        return "fallback Customer";
    } 
    }

@Service
public class RibbonService {

    @Autowired
    RestTemplate restTemplate;

    @HystrixCommand(fallbackMethod = "hiError")
    public String consumer() {
        return restTemplate.getForObject("http://springcloud-euraka-client/hello", String.class);
    }

    public String hiError() {
        return "fallbackCall";
    }

}

RibbonService 是因为把业务把控制层分开,第一个方法是这样调用 

在consumer()方法上加上@HystrixCommand注解。有了该注解consumer()方法就启用了Hystrix熔断器的功能,fallbackMethod为处理回退(fallback)逻辑的方法为hiError() ,没有异常,直接返回一个字符串,也可以用抛异常的方式执行,这样在日志里可以发现哪个服务出现问题,只需要在 hiError() 里加thrable 等方式,我这里为了简便就没有做,主要是不想处理复杂的逻辑,,这样方便执行快速失败,释放线程资源,项目结构是这样

加上配置文件,因为是ribbon 和 feign放在一起,还有重试策略配置等,

server:
  port: 5080
eureka:
  client:
    serviceUrl:
      defaultZone: http://peer1:5012/eureka/
    healthcheck:
      enabled: true
    lease:
      duration: 5
  instance:
    preferIpAddress: true
spring:
  application:
    name: springCloudEurakaConsumer
#使用如下配置开启,即可实现重试:再加Feign下面的ribbon配置,就是 Ribbon+RestTemplate的重试机制   
#  cloud:
#    loadbalancer:
#      retry: 
#      enabled: true
#开启hystrix功能    
feign:
  hystrix:
    enabled: true
#logging:
#  level:
#    org.exampledriven.eureka.customer.shared.CustomerServiceFeignClient: FULL
#lease 租期时间,不定时发送心跳包消息,如果长时间未能更新租期时间,服务就为抛弃该服务实例,注册中心会当成无用服务
# 1,Ribbon+RestTemplate的重试
#对于整合了Ribbon的RestTemplate,例如一个RestTemplate添加了@LoadBalanced 注解
#在此基础上,使用如下配置,即可实现重试:
#Feign的重试与Ribbon重试整合后, 只需使用Ribbon的重试配置即可,两种重试配置一样,所以下面配置至少要重试4次
ribbon:
 # 同一实例最大重试次数,不包括首次调用,配置后本实例至少会执行两次
  MaxAutoRetries: 1
 # 重试其他实例的最大重试次数,不包括首次所选的server,配置后本实例和其它服务注册实例至少会执行两次
  MaxAutoRetriesNextServer: 1
 # 是否所有操作都进行重试
  OkToRetryOnAllOperations: false

到这里ribbon配置基本完成,

依次启动springcloud-eureka-server 的服务注册,springcloud-euraka-client生产者 euraka-consumer-ribbonAndfeign-hystrix消费消息,在浏览器访问http://localhost:5080/hilmc,会显示如下, 因为我在生产端直接睡眠了9秒,所以触发熔断

再输入正常url   http://localhost:5080/helloCustomer 显示正常

在Feign上使用熔断器 下面是feign 包

Feign的起步依赖中已经引入了Hystrix的依赖,只需要在eureka-feign-client工程的配置文件中开启hystrix功能即可,前方配置文件已经给出

EurekaClientFeign 接口,

@Component
@FeignClient(value = "springcloud-euraka-client",fallback = HiHystrix.class)
public interface EurekaClientFeign {

    /**
     * 绑定 springcloud-euraka-client 服务的 /hi/ 接口
     *
     * @param name
     * @return
     */
    @GetMapping(value = "/hi")
    String sayHiFromClientEureka(@RequestParam(value = "name")String name);
    
    @GetMapping(value = "/hi2")
    String sayHiFromClientEureka2(@RequestParam(value = "name")String name);
    
}

HiHystrix 类必须实现EurekaClientFeign接口,才能触发熔断器的fallback

@Component
public class HiHystrix implements EurekaClientFeign {

    @Override
    public String sayHiFromClientEureka(String name) {
        return "hi,"+name+",sorry.error!";
    }

    @Override
    public String sayHiFromClientEureka2(String name) {
          return "hi2,"+name+",sorry.error! Hi2";
    }
}

CustomerFeignController 类

@RestController
public class CustomerFeignController {
    
    @Autowired
    EurekaClientFeign eurekaclient;
    //超时调用fallback 的方法,如果禁止重试,也会调用两次,如果配置文件有调用其它实例,至少会执行3次以上
    //所以在写接口的时候,需要对接口增加幂等性
    @GetMapping("/hi")
    public String sayHi(@RequestParam(defaultValue = "cralor",required = false)String name){
        return eurekaclient.sayHiFromClientEureka(name);
    }
    //正常执行调用
    @GetMapping("/hi2")
    public String sayHi2(@RequestParam(defaultValue = "lmc",required = false)String name){
        return eurekaclient.sayHiFromClientEureka2(name);
    }
    @Autowired
    private GreetingClient greetingClient;
    @RequestMapping("/get-greeting")
    public String greeting(Model model) {
       Map<String, String> parpm= new HashMap<String, String>();
          parpm.put("name", "lmc");
          parpm.put("test", "demo");
        String message =  greetingClient.greeting(parpm.toString()); //以字符串方式传参数
        return message+":传参数";
    }

浏览器访问输入http://localhost:5080//hi2 正常调用

浏览器访问输入http://localhost:5080//hi

这个方法在springcloud-euraka-client 项目里启动睡眠6秒超时

都 能触发熔断降级处理, 说明是可行,

还要注意的是 feign 重试机制,就是在超时或者异常的情况下,会不断去重试调用,feign默认重试次数是5次,100ms一次

这最前面的配置文件最后参数有说明 ,就是除了本身实例,本身服务,再去调用其它实例和服务,在集群的环境下配置这种策略最好,如果碰上数据量过大请求过去超时,但是生产者那方会如果没有限制超时就会继续执行,这个时间再去重试请求其实是非常危险的,一条数据会碰上执行两次以上,get的数据可能影响不大,如果是post写数据就要考虑接口的幂等性

使用Hystrix Dashboard监控熔断器的状态 这个跟eurak服务注册类型的步骤,有时间再弄会,集群监控所有熔断器的状态和运用情况等

githut项目地址 https://github.com/lmchuyang/huyang

如有理解 错的,希望大家能指正交流

参考资料 https://www.cnblogs.com/cralor/p/9230728.html 

参考资料 https://www.jb51.net/article/129336.htm

猜你喜欢

转载自blog.csdn.net/limingcai168/article/details/81206980