Hystrix微服务的容错处理、防雪崩效应、状态监控、隔离策略、回退日志

一、容错

1、雪崩效应

2、容错手段

1.为网络请求设置超时

2.使用断路器模式

二、Hystrix实现容错

1、整合Hystrix

2、HyStrix的状态监控与深入解析

3、隔离策略


一、容错

如果服务提供者相应非常缓慢,那么消费者对提供者的请求就会被强制等待,知道提供者相应超时。在高负载场景下,如果不作任何处理,此类问题可能会导致服务消费者的资源耗尽甚至整个系统崩溃。

1、雪崩效应

微服务架构的应用系统通常包含多个服务层,微服务之间通过网络进行通信,从而支撑起整个应用系统,因此,微服务之间难免存在依赖关系。我们常把“基础服务故障”导致“级联故障”的现象成为雪崩效应。雪崩效应描述的是提供者不可用导致消费者不可用,并将不可用逐步扩大的过程。

如下图所示:

A\B作为微服务消费者分别消费微服务C\D,微服务C\D同时作为微服务消费者消费微服务E。

当微服务E出现故障。

由于微服务E不可用,微服务C\D调用失败,请求堆积,占用系统资源,最后导致微服务C\D不可用。

由于微服务C\D不可用,微服务A\B调用失败,请求堆积,占用系统资源,最后导致微服务A\B也不可用。

最终的结果就是由于微服务E出现故障已至整个微服务集群瘫痪掉。

2、容错手段

1.为网络请求设置超时

必须为网络请求设置超时。正常情况下,一个远程调用一般在几十毫秒内就能得到响应了。如果依赖的服务不可用或者网络有问题,那么响应时间就会变得特别长。

通常情况下,一次远程调用对应着一个线程/进程。如果响应太慢,这个线程/进程就得不到释放。而线程/进程又对应着系统资源,如果得不到释放的线程/进程约积越多,资源就会逐渐被耗尽,最终导致服务的不可用。

2.使用断路器模式

如果对某个微服务的请求有大量超时(常常说明该微服务不可用),再去让新的请求访问该服务已经没有任何意义,只会无所谓消耗资源。例如,设置了超时时间为1秒,如果短时间内有大量的请求无法在1秒内得到响应,就没有必要再去请求依赖的服务了。

短路器可理解为对容易导致错误的操作的代理。这种代理能够统计一段时间内调用失败的次数,并决定是正常请求依赖的服务还是直接返回。

断路器可以实现快速失败,如果它在一段时间内检测到许多类似的错误(例如超时),就会在之后的一段时间内,强迫对该服务的调用快速失败,即不再请求所依赖的服务。这样,应用程序就无需再浪费cpu时间去等待长时间的超时。

短路器也可自动诊断是否已经恢复正常。如果发现依赖的服务已经恢复正常,那么就会恢复请求该服务。使用这种方式,就可以实现微服务的“自我修复”——当依赖的服务不正常打开断路器时快速失败,从而防止雪崩效应;当发现依赖的服务恢复正常时,又会恢复请求。

断路器状态转换逻辑如上图所示:

- 正常情况下,断路器关闭,可正常请求依赖的服务

- 当一段时间内,请求失败率达到一定阀值(例如错误率达到50%,或100次/分钟等),断路器就会打开。此时,不会再去请求依赖的服务。

- 断路器打开一段时间后,会自动进入“半开”状态。此时,断路器可允许一个请求访问依赖的服务。如果该请求能够调用成功,则关闭断路器;否则继续保持打开状态。

二、Hystrix实现容错

Hystrix是一个实现了超时机制和断路器模式的工具类库。是由Netfix开源的一个延迟和容错库,用于隔离访问远程系统、服务或者第三方库,防止级联失败,从而提升系统可用性与容错性。Hystrix主要通过以下几点实现延迟和容错。

  • 包裹请求:使用HystrixCommand(或者HystrixObservableCommand)包裹对依赖的调用逻辑,每个命令在独立线程中执行。这使用了设计模式中的“命令模式”。
  • 跳闸机制:当某服务的错误率超过一定阀值时,Hystrix可以自动或者手动跳闸,停止请求该服务一段时间。
  • 资源隔离:Hystrix为每个依赖都维护了一个小型的线程池(或者信号量)。如果该线程池已满,发往该依赖的请求就被立即拒绝,而不是排队等候,从而加速失败判断
  • 监控:Hystrix可以近乎实时地监控运行指标和配置的变化,例如成功、失败、超时、以及被拒绝的请求等。
  • 回退机制:当请求失败、超时、被拒绝,或当断路器打开时,执行回退逻辑。回退逻辑可由开发人员自行提供,例如返回一个缺省值。
  • 自我修复:断路器打开一段时间后,会自动进入“半开”状态。

1、整合Hystrix

1.复制项目eureka-client-consumer-ribbon为eureka-client-consumer-hystrix

2.添加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>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.springclouddemo</groupId>
    <artifactId>eureka-client-consumer-hystrix</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>eureka-client-consumer-hystrix</name>
    <description>整合Hystrix</description>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Greenwich.SR1</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <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-netflix-hystrix</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </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>
        </plugins>
    </build>

</project>

3.为main类添加@EnableHystrix启动熔断器

package com.springclouddemo.eurekaclientconsumerhystrix;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

/**
 * @author 何昌杰
 */
@SpringBootApplication
@EnableEurekaClient
@EnableHystrix
public class EurekaClientConsumerHystrixApplication {

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

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

4.修改application配置文件

spring.application.name=eureka-client-consumer-hystrix
server.port=7206
eureka.client.service-url.defaultZone=http://localhost:7000/eureka/
eureka.instance.prefer-ip-address=true

5.将DemoController.java修改如下

package com.springclouddemo.eurekaclientconsumerhystrix.controllers;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.ribbon.proxy.annotation.Hystrix;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

/**
 * @author 何昌杰
 */
@RestController
public class DemoController {
    private static final Logger log= LoggerFactory.getLogger(DemoController.class);
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private LoadBalancerClient loadBalancerClient;

    @GetMapping("/helloribbon")
    public void ribbonDemo(){
        ServiceInstance choose = this.loadBalancerClient.choose("eureka-client-provider");
        log.info(choose.getInstanceId()+","+choose.getHost()+":"+choose.getPort());
    }

    @GetMapping("/hello/{name}")
    @HystrixCommand(fallbackMethod = "demo2Fallback")
    public String demo1(@PathVariable("name") String name){
        return this.restTemplate.getForObject("http://localhost:7100/hello/"+name,String.class);
    }

    public String demo2Fallback(String name){
        return "模拟默认请求响应";
    }
}
  • 为demo1方法编写了一个回退方法demo2Fallback,该方法的方法声明与demo1一致,具有相同的请求参数与响应类型,不过该方法默认返回一个默认字符串。
  • @HystrixCommand注解的fallbackMethod属性指定了回退方法为demo2Fallback。
  • 当请求demo1方法对应接口失败、被拒绝、超时或者熔断时,都会进入回退方法。但是进入回退方法并不意味断路器以及被打开,确认断路器被打开需要通过状态监控实现

2、HyStrix的状态监控与深入解析

断路器的状态监控需要通过Actuator实现

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

断路器的状态会暴露在Actuator提供的/health端点中,这样就可以直观了解断路器的状态。

测试:

 这里我们发现虽然执行的回退方案返回了默认值,但是Hystrix的状态依然是UP,这是因为失败请求还未达到阈值(默认是5秒20次失败)

  1. 启动项目eureka-server、eureka-client-provider、eureka-client-consumer-hystrix
  2. 访问http://localhost:7206/hello/hcj,可以正常请求
  3. 访问http://localhost:7206/health,响应如下:
    {
        "status":"UP",
        "hystrix":{
            "status":"UP"
        }
        ...
    }
  4. 停止eureka-client-provider
  5. 访问http://localhost:7206/hello/hcj,响应如下:
    模拟默认请求响应
  6. 访问http://localhost:7206/health,响应如下:
    {
        "status":"UP",
        "hystrix":{
            "status":"UP"
        }
        ...
    }
  7. 连续快速访问http://localhost:7206/health,(5秒内请求大于20次)响应如下:
{
    "status":"UP",
    "hystrix":{
        "status":"CIRCUIT_OPEN",
        "openCircuitBreakers":[
            "DemoController::demo1"
        ]
    }
    ...
}

Hystrix的状态为CIRCUIT_OPEN说明断路器已经打开,不会去调用其他微服务了。

3、隔离策略

Hystrix的隔离策略有两种:线程隔离和信号量隔离。

  • THREAD(线程隔离):使用该方式,HystrixCommand将会在单独的线程执行,并发请求受线程池数据的限制
  • SEMAPHORE(信号量隔离):使用该方式,HystrixCommand将会在调用线程上执行,开销相对较小,并发请求收到信号量个数的限制

Hystrix中默认使用线程隔离(THREAD),因为这种方式有一个除网络超时以外的额外保护层。

一般来说,只有当调用负债非常高时(至少每秒数百次)才需要使用信号量隔离,因为这种场景使用线程隔离开销会比较高,信号量格式一般仅适用于非网络调用的隔离。

可以使用execution.isolation.strategy属性指定隔离策略。

    @GetMapping("/hello/{name}")
    @HystrixCommand(fallbackMethod = "demo2Fallback",
        commandProperties = {
            @HystrixProperty(name = "execution.isolation.strategy",value = "SEMAPHORE")
        }
    )
    public String demo1(@PathVariable("name") String name){
        return this.restTemplate.getForObject("http://localhost:7100/hello/"+name,String.class);
    }

更多关于Hystrix的内容各位可移步至此:Hystrix详解(如何保护、限流、熔断、隔离、降级、源码分析)

三、Feign整合Hystrix

1、为Feign添加回退

1.复制项目eureka-client-consumer-feign为eureka-client-consumer-feign-fallback

2、将ProviderFeign.java修改如下:

package com.springclouddemo.eurekaclientconsumerfeignfallback.feign;

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

/**
 * @author 何昌杰
 */
@Component
@FeignClient(name = "eureka-client-provider",fallback = ProviderFeignFallback.class)
public interface ProviderFeign {

    @GetMapping("/hello/{name}")
    String demo1(@PathVariable("name") String name);
}

class ProviderFeignFallback implements ProviderFeign{
    @Override
    public String demo1(String name) {
        return "默认返回值";
    }
}
  • @FeignClient的fallback属性指定该接口的回退类

2、通过Fallback Factory检查回退原因

通过上一小节我们了解到了如何在Feign中添加回退,但是当执行回退方法时,我们无从得知为何进行了回退。本小节为例讲述为Feign添加回退日志。

1.复制项目eureka-client-consumer-feign为eureka-client-consumer-feign-fallback-factory

2.将ProviderFeign.java修改如下

package com.springclouddemo.eurekaclientconsumerfeignfallbackfactory.feign;

import feign.hystrix.FallbackFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

/**
 * @author 何昌杰
 */
@Component
@FeignClient(name = "eureka-client-provider",fallbackFactory = FeignClientFallbackFactory.class)
public interface ProviderFeign {

    @GetMapping("/hello/{name}")
    String demo1(@PathVariable("name") String name);
}


@Component
class FeignClientFallbackFactory implements FallbackFactory<ProviderFeign>{

    private static final Logger log = LoggerFactory.getLogger(FeignClientFallbackFactory.class);

    @Override
    public ProviderFeign create(Throwable throwable) {
        return new ProviderFeign() {
            /**
             * 将日志置于fallback方法中防止引用启动时输出日志
             * @param name 请求参数
             * @return 响应
             */
            @Override
            public String demo1(String name) {
                log.info("fallback; the reason was:",throwable);
                return "默认返回值";
            }
        };
    }
}

3.编写application配置文件

spring.application.name=eureka-client-consumer-feign-fallback-factory
server.port=7208
eureka.client.service-url.defaultZone=http://localhost:7000/eureka/
eureka.instance.prefer-ip-address=true
feign.hystrix.enabled=true
  • 开启熔断器,必须将feign.hystrix.enabled设置为true(默认为false)

测试:

  1. 启动eureka-server、eureka-client-provider、eureka-client-consumer-feign-fallback-factory
  2. 访问http://localhost:8010/hello/hcj能正常响应
  3. 停止eureka-client-provider
  4. 再次访问http://localhost:8010/hello/hcj,响应如下:
    2019-07-09 12:21:03.991  INFO 9076 --- [ HystrixTimer-1] c.s.e.feign.FeignClientFallbackFactory   : fallback; the reason was:Load balance does not have available server  for client: eureka-client-provider

控制台输出如下:

2019-07-09 12:21:03.991  INFO 9076 --- [ HystrixTimer-1] c.s.e.feign.FeignClientFallbackFactory   : fallback; the reason was:Load balance does not have available server  for client: eureka-client-provider

 说明进入了回退方法。

  • fallbackFactory其实还有很多用途,可以根据不同的异常类型选择返回不同的日志:
@Component
class FeignClientFallbackFactory implements FallbackFactory<ProviderFeign>{

    private static final Logger log = LoggerFactory.getLogger(FeignClientFallbackFactory.class);

    @Override
    public ProviderFeign create(Throwable throwable) {
        return new ProviderFeign() {
            /**
             * 将日志置于fallback方法中防止引用启动时输出日志
             * @param name 请求参数
             * @return 响应
             */
            @Override
            public String demo1(String name) {
                if(throwable instanceof IllegalAccessException){
                    //..
                }else if(throwable instanceof ClassCastException){
                    //...
                }
                return "默认返回值";
            }
        };
    }
}

3、为Feign禁用Hystrix

使用Feign自定义配置可以方便的禁用Hystrix,禁用Hystrix可分为两种方式,一种禁用指定Feign接口,一种是全局禁用Feign

1.指定Feign禁用

创建配置类

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

为需要禁用Hystrix的FeignClient引用该配置

@Component
@FeignClient(name = "eureka-client-provider", configuration = FeignDisableHystrixConfiguration.class)
public interface ProviderFeign {
//...
}

2.全局禁用Hystrix

在application中添加配置项:

feign.hystrix.enabled=false

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值