超详细讲解Hystrix的正确打开方式以及熔断和降级测试(带源码分析)

1 缘起

历旧项目,发现SpringBoot的版本为2.2.8.RELEASE,应该可以集成Hystrix,
实现熔断和降级测试,于是尝试在项目中集成,
可是查了相关资源,发现,很多资源都不完整,无法一步到位帮助开发者实现集成和测试,
所以,本文从实践到理论讲解Hystrix,帮助读者成功集成Hystrix并了解运作原理。
但是,没有讲为什么要使用Hystrix,这个读者可以自己总结。
第一,先从代码实践开始讲起,开发者阅读这一篇文章即可成功集成Hystrix到自己的项目中,
并给出测试样例和日志,有助于理解和验证结果。
第二,通过源码讲解Hystrix如何开启、读取属性,执行回调方法,并给出了Hystrix熔断和降级的流程示意图。
虽然,Hystrix已经进入维护阶段,但是,这不是开发者放弃学习的理由,
对于某些通用的功能和需求而言,只是使用不同的实现方式,不妨碍我们学习,
比如,新版的SpringBoot2.4.5及以后的版本,放弃Hystrix,使用Resllience4j,其实,也是熔断、降级和限流等功能,
Resilience4j的集成讲解参见:Chapter24:高可用组件resilience4j实现熔断、降级、限流
Resilience4j原理参见:详解SpringBoot服务限流原理之resilience4j

2 实践

该部分直接讲解如何集成Hystrix实现熔断和降级,
从架构的角度,讲解如何通过构建微服务,集成Hystrix,(完整的服务需要测试者自行搭建,这里讲解架构和具体的实验结果)
完成熔断和降级的测试和验证工作,
本文使用的微服务架构非常简单,如下图所示,
注册中心:Eureka;
被调用方:User服务;
调用方:Data服务(集成OpenFeign,调用User服务)。
在这里插入图片描述

版本:
SpringBoot 2.2.8.RELEASE
Eureka 2.2.3.RELEASE
Hystrix 2.2.8.RELEASE
OpenFeign 2.2.6.RELEASE

2.1 依赖

服务需要的依赖如下,核心有三个:Eureka、OpenFeign和Hystrix。
在Data服务(调用方)中集成Eureka、OpenFeign和Hystrix;
User服务中集成Eureka即可。

<!-- 服务注册和发现、客户端负载均衡、熔断 -->
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    <version>2.2.3.RELEASE</version>
</dependency>
<!-- 服务间调用依赖Feign -->
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-openfeign</artifactId>
	<version>2.2.6.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-hystrix -->
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
	<version>2.2.8.RELEASE</version>
</dependency>

2.2 User服务

被Data服务调用的服务。
无特殊,正常开发接口(这里就不给样例了,可以任意发挥),

  • 配置应用名称application.name=microservice-user;
  • 添加Eureka注册中心;

2.2.1 配置

配置样例如下,集成Eureka,将User服务注册到Eureka。

spring:
  profiles:
    active: dev
  application:
    name: microservice-user
    
eureka:
  client: 
    fetch-registry: true
    register-with-eureka: true
    service-url: 
      defaultZone: http://localhost:8001/eureka/eureka # 与Eureka server(注册中心)交互地址,查询Eureka服务的地址

2.2.2 Controller

User服务对外提供的接口如下,这给了一个分页查询样例,
这是之前的旧项目,有数据库配置,直接就拿来用了,
如果开发者也想要测试Hystrix的熔断和降级,可以直接写一个简单的返回接口,
自定义响应延迟等。

package com.company.microserviceuser.controller;

import com.company.microserviceuser.exception.MyException;
import org.springframework.web.bind.annotation.*;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import com.github.pagehelper.PageInfo;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;

import com.company.microserviceuser.dto.*;
import com.company.microserviceuser.enums.MyCodeEnums;
import com.company.microserviceuser.service.*;
import com.company.microserviceuser.vo.common.*;
import com.company.microserviceuser.vo.*;
import com.company.microserviceuser.dos.*;
import com.company.microserviceuser.utils.*;

import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.List;

import static com.company.microserviceuser.constant.StringConstant.DATE_Y_M_D_H_M_S;
import com.company.microserviceuser.constant.*;
/**
 * UserController API.
 * @author xindaqi 
 * @since 2020-10-26
 */
@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
@RequestMapping("/api/v1/user")
@Api(tags = "人员信息")
public class UserController {

    private static Logger logger = LoggerFactory.getLogger(UserController.class);

    @Autowired 
    private TimeProcessUtil timeProcessUtil;

    @Autowired
    private IUserService userService;

    @Autowired
    private BCryptPasswordEncoder passwordEncoderCrypt;


    @PostMapping(value = "/query/page")
    @ApiOperation(value = "分页查询用户", notes = "分页查询用户v0.0", httpMethod = "POST")
    @ApiImplicitParam(name = "params", value = "分页查询用户注册信息", dataType = "QueryUserByPageInputDTO", paramType = "body")
    public ResponseVO<List<UserDetailsVO>> queryUserByPage(@RequestBody QueryUserByPageInputDTO params) {
        try{
            UserDO userDo = new UserDO();
            Integer pageNum = params.getPageNum();
            Integer pageSize = params.getPageSize();
            BeanUtils.copyProperties(params, userDo);
            PageInfo<UserDetailsVO> userInformation = userService.queryUserByPage(userDo, pageNum, pageSize);
            List<UserDetailsVO> userList = userInformation.getList();
            Long total = userInformation.getTotal();
            logger.info("成功--分页查询用户");
            return new ResponseVO<List<UserDetailsVO>>().ok(userList, total);
        }catch (Exception e) {
            logger.error("失败--列表分页查询用户:", e);
            return new ResponseVO<List<UserDetailsVO>>().fail();
        }
    }
}

2.3 Data服务

  • 使用OpenFeign调用User服务,需要在Data服务中集成OpenFeign;
  • 熔断User服务需要集成Hystrix;
  • 没有在启动类中使用启动Hystrix注解,如 @EnableHystrix、@EnableCircuitBreaker

2.3.1 配置

Data服务添加:
直接添加如下配置,

  • 注册到Eureka;
  • 为Feign开启Hystrix;
  • 同时设置Hystrix熔断配置;
  • ribbon配置连接和响应时间上限。
eureka:
  client: 
    fetch-registry: true
    register-with-eureka: true
    service-url: 
      defaultZone: http://localhost:8001/eureka/eureka # 与Eureka server(注册中心)交互地址,查询Eureka服务的地址

hystrix:
  command:
    default:
      circuitBreaker:
        errorThresholdPercentage: 50 # 触发熔断的错误比例
        sleepWindowMilliseconds: 100000 # 熔断后的休眠时间,单位:毫秒,即熔断开启到正常访问服务的时间间隔
        requestVolumeThreshold: 2 # 触发熔断的最小请求次数
      execution:
        timeout:
          enabled: true
        isolation:
          strategy: THREAD # 隔离策略:线程隔离
          thread:
            timeoutInMilliseconds: 10000 # 线程超时时间:5秒后,调用Fallback
            
ribbon:
  ConnectTimeout: 2000 # 服务连接超时时间,单位:毫秒
  ReadTimeout: 3000 # 获取响应超时时间,单位:毫秒,不可大于Hystrix超时时间
  MaxAutoRetries: 0 # 最大自动重试次数
  MaxAutoRetriesNextServer: 0 # 向集群其他服务最大重试次数
  
feign:
  hystrix:
    enabled: true #为Feign开启Hystrix

2.3.2 添加OpenFeign调用

Data服务使用OpenFeign调用User服务,
因此只需在Data服务中配置User服务的名称和URI即可完成调用,
为了使OpenFeign具有回调的实际内容(方法),需要在@FeignClient中配置fallback属性,
配置代码如下。
因为向注册中心Eureka注册,IP和PORT已在Eureka中互通,
Eureka相关参见:注册中心Eureka生命周期是怎样的

package com.company.microservicedata.feign;

import com.company.microservicedata.feign.impl.UserModuleFeignImpl;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestBody;

import java.util.List;

import com.company.microservicedata.dto.*;
import com.company.microservicedata.vo.*;
import com.company.microservicedata.vo.common.*;

/**
 * UserModule远程调用.
 *
 * @author xindaqi
 * @since 2022-06-23 15:58
 */
@FeignClient(value="microservice-user", fallback = UserModuleFeignImpl.class)
public interface IUserModuleFeign {

    /**
     * 调用User模块的测试接口,验证Feign
     * 
     * @param params 用户分页查询参数
     * @return 用户详情-分页展示
     */
    @RequestMapping("/api/v1/user/query/page")
    ResponseVO<List<UserDetailsVO>> queryUserByPage(@RequestBody QueryUserByPageInputDTO params);
    
}

2.3.3 Hystrix熔断回调实现

上面配置了User服务的调用接口,
如果使用Hystrix进行回调,需要一比一实现上面的接口方法,
保证,各自方法有正确的响应类型映射,
样例如下:

package com.company.microservicedata.feign.impl;

import com.company.microservicedata.dto.QueryUserByPageInputDTO;
import com.company.microservicedata.feign.IUserModuleFeign;
import com.company.microservicedata.vo.UserDetailsVO;
import com.company.microservicedata.vo.common.ResponseVO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

/**
 * UserModule熔断回调实现.
 *
 * @author xindaqi
 * @since 2022-06-23 15:58
 */
@Component
public class UserModuleFeignImpl implements IUserModuleFeign {

    private static final Logger logger = LoggerFactory.getLogger(UserModuleFeignImpl.class);
    
    @Override
    public ResponseVO<List<UserDetailsVO>> queryUserByPage(QueryUserByPageInputDTO params) {
        logger.info(">>>>>>>>>Hystrix feign fallback!");
        return new ResponseVO<List<UserDetailsVO>>().ok(new ArrayList<>(), 0L);
    }
}

2.3.4 Controller

通过Hystrix实现回调有两种方式,
一种:方法方式,直接实现OpenFeign的接口。
二种:注解方式,通过@HystrixCommand配置回调方法。

  • 实现OpenFeign接口方式回调熔断方法
package com.company.microservicedata.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.beans.factory.annotation.Autowired;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import com.alibaba.fastjson.JSON;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.company.microservicedata.service.*;
import com.company.microservicedata.feign.*;
import com.company.microservicedata.dto.*;
import com.company.microservicedata.vo.*;
import com.company.microservicedata.vo.common.*;
import com.company.microservicedata.enums.*;
import com.company.microservicedata.util.ExcelProcessUtil;
import com.company.microservicedata.constant.StringConstant;


import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.company.microservicedata.util.TimeProcessUtil;

/**
 * Feign call test.
 * @author xindaqi
 * @since 2020-12-02
 */
@CrossOrigin(origins="*", maxAge=3600)
@RestController
@RequestMapping("/v1/data")
@Api(tags = "User Feign")
public class UserInformationController {

    private static Logger logger = LoggerFactory.getLogger(UserInformationController.class);

    @Autowired
    private IUserModuleFeign userModuleFeign;

    @Autowired
    private IUserInformationService userInformationService;

    @Autowired
    private ExcelProcessUtil excelProcessUtil;
    
    @Autowired
    private TimeProcessUtil timeProcessUtil;

    @RequestMapping(value="/user/page", method=RequestMethod.POST)
    @ApiOperation("分页查询用户信息")
    public ResponseVO<List<UserDetailsVO>> queryUserByPage(@RequestBody QueryUserByPageInputDTO params) {
        return userModuleFeign.queryUserByPage(params);
    }
}
  • 使用注解方式回调熔断方法
package com.company.microservicedata.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.beans.factory.annotation.Autowired;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import com.alibaba.fastjson.JSON;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.company.microservicedata.service.*;
import com.company.microservicedata.feign.*;
import com.company.microservicedata.dto.*;
import com.company.microservicedata.vo.*;
import com.company.microservicedata.vo.common.*;
import com.company.microservicedata.enums.*;
import com.company.microservicedata.util.ExcelProcessUtil;
import com.company.microservicedata.constant.StringConstant;


import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.company.microservicedata.util.TimeProcessUtil;

/**
 * Feign call test.
 * @author xindaqi
 * @since 2020-12-02
 */
@CrossOrigin(origins="*", maxAge=3600)
@RestController
@RequestMapping("/v1/data")
@Api(tags = "User Feign")
public class UserInformationController {

    private static Logger logger = LoggerFactory.getLogger(UserInformationController.class);

    @Autowired
    private IUserModuleFeign userModuleFeign;

    @Autowired
    private IUserInformationService userInformationService;

    @Autowired
    private ExcelProcessUtil excelProcessUtil;
    
    @Autowired
    private TimeProcessUtil timeProcessUtil;

    @RequestMapping(value="/user/page", method=RequestMethod.POST)
    @ApiOperation("分页查询用户信息")
    @HystrixCommand(fallbackMethod = "com.company.microservicedata.feign.impl.UserModuleFeignImpl.queryUserByPage")
    public ResponseVO<List<UserDetailsVO>> queryUserByPage(@RequestBody QueryUserByPageInputDTO params) {
        return userModuleFeign.queryUserByPage(params);
    }

    public ResponseVO<List<UserDetailsVO>> queryUserByPageFallback(QueryUserByPageInputDTO params) {
        logger.info(">>>>>>>>>Hystrix feign fallback!");
        return new ResponseVO<List<UserDetailsVO>>().ok(new ArrayList<>(), 0L);
    }
}

3 测试结果

3.1 User服务直接故障(DOWN)

这种情况,Data服务通过OpenFeign调用User服务,
由于为OpenFeign配置了Hystrix,此时,
Data服务会直接调用回调方法,返回默认数据。
当请求次数和失败率达到Hystrix配置后,Hystrix触发熔断,
在熔断等待期间,即使User服务重新可用,也不会将请求发送到User服务,
而是直接调用自身的回调方法,保证及时响应。
熔断及回调的时序如下图所示。
在这里插入图片描述

  • 回调响应
    Data调用结果如下图所示,User服务未启动,直接使用回调结果。
    在这里插入图片描述

  • 日志信息
    Data服务运行,User服务未启动,此时,日志结果如下图所示,
    由日志可知,进入回调方法,设定的信息:>>>>>>>>>Hystrix feign fallback!
    调用回调的线程为:hystrix-microservice-user-3
    由此可以推断,Hystrix是通过线程隔离的方式进行熔断。
    在这里插入图片描述

  • 熔断时间窗
    在熔断时间窗内,即使User服务已经可用(启动),
    Data服务仍不会将请求打到User服务,而是直接使用回调方法,
    当过了熔断窗口,才会重新向User请求。
    在熔断期内启动User服务,此时并没有请求进入,User日志信息如下图所示。
    在这里插入图片描述

过了熔断期,User服务恢复正常,此时请求进入User服务,Data服务重新请求User服务,
两个服务的日志如下图所示,均有日志输出,并有traceId和spanId。
在这里插入图片描述

3.2 User延迟过高(高于Ribbon的最大响应时间)

Data服务未在设定的时间内获取User响应,
此时将User服务手动增加响应时延,如强制增加6秒的时延,
超过Ribbon的服务响应时间,如上面的配置:3秒,
此时,Data请求User服务,未在规定的时间内获得响应,直接调用回调方法,
但是,Data仍会向User发送请求,只是不等待其响应返回。
User接口的时延样例如下,仅增加了一行线程延时:Thread.sleep(6000)。

package com.company.microserviceuser.controller;

import com.company.microserviceuser.exception.MyException;
import org.springframework.web.bind.annotation.*;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import com.github.pagehelper.PageInfo;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;

import com.company.microserviceuser.dto.*;
import com.company.microserviceuser.enums.MyCodeEnums;
import com.company.microserviceuser.service.*;
import com.company.microserviceuser.vo.common.*;
import com.company.microserviceuser.vo.*;
import com.company.microserviceuser.dos.*;
import com.company.microserviceuser.utils.*;

import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.List;

import static com.company.microserviceuser.constant.StringConstant.DATE_Y_M_D_H_M_S;
import com.company.microserviceuser.constant.*;
/**
 * UserController API.
 * @author xindaqi 
 * @since 2020-10-26
 */
@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
@RequestMapping("/api/v1/user")
@Api(tags = "人员信息")
public class UserController {

    private static Logger logger = LoggerFactory.getLogger(UserController.class);

    @Autowired 
    private TimeProcessUtil timeProcessUtil;

    @Autowired
    private IUserService userService;

    @Autowired
    private BCryptPasswordEncoder passwordEncoderCrypt;


    @PostMapping(value = "/query/page")
    @ApiOperation(value = "分页查询用户", notes = "分页查询用户v0.0", httpMethod = "POST")
    @ApiImplicitParam(name = "params", value = "分页查询用户注册信息", dataType = "QueryUserByPageInputDTO", paramType = "body")
    public ResponseVO<List<UserDetailsVO>> queryUserByPage(@RequestBody QueryUserByPageInputDTO params) {
        try{
        	// 强制延时6秒,超过Ribbon的响应时间
        	Thread.sleep(6000);
            UserDO userDo = new UserDO();
            Integer pageNum = params.getPageNum();
            Integer pageSize = params.getPageSize();
            BeanUtils.copyProperties(params, userDo);
            PageInfo<UserDetailsVO> userInformation = userService.queryUserByPage(userDo, pageNum, pageSize);
            List<UserDetailsVO> userList = userInformation.getList();
            Long total = userInformation.getTotal();
            logger.info("成功--分页查询用户");
            return new ResponseVO<List<UserDetailsVO>>().ok(userList, total);
        }catch (Exception e) {
            logger.error("失败--列表分页查询用户:", e);
            return new ResponseVO<List<UserDetailsVO>>().fail();
        }
    }
}
  • 响应结果
    在这里插入图片描述
    User服务日志如下图所示,由此可知,Data的请求仍会向User发起。
    在这里插入图片描述
    Data服务,配置的Ribbon响应时间为3秒,
    使用OpenFeign请求的服务,响应时间超过3秒,则不等待响应,
    直接使用回调方法的结果,保证Data服务不会阻塞,避免级联灾难,
    此时Data的响应日志如下图所示。
    在这里插入图片描述

4 源码分析

4.1 OpenFeign启用Hystrix

如何为OpenFeign开启Hystrix,我开始也是苦恼,
开始在网上查到的很多不是通过配置文件开启Hystrix,
然后又再次搜索,终于查到使用配置文件为OpenFeign开启Hystrix,
可是,问题又来了,这个配置是如何生效的?谁读取的?
这不是通过@ConfigurationProperties通过前缀读取的,
而是@ConditionalOnProperty生效的,
源码如下图所示。

位置:org.springframework.cloud.openfeign.FeignClientsConfiguration.HystrixFeignConfiguration#feignHystrixBuilder

在这里插入图片描述

4.2 Hystrix配置

同样,我们配置了Hystrix属性之后,
这些属性是如何被读取到的?
当然,也不是常用的@ConfigurationProperties,
而是在程序中读取,
以hystrix为前缀,源码如下图所示。
位置:com.netflix.hystrix.HystrixCommandProperties

在这里插入图片描述

启动SpringBoot时会实例化HystrixPropertiesStrategyDefault,
继承Hystrix属性策略,获取填充的Hystrix属性,
源码如下图所示。
位置:com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategyDefault

在这里插入图片描述

接下来OpenFeign会配置相关的Hystrix属性,包括groupKey和commandKey,
为后面执行回调函数做准备,
源码如下图所示,
位置:feign.hystrix.SetterFactory

在这里插入图片描述

使用OpenFeign请求User服务时,
会配置相关属性,
Hystrix配置并不是在启动SprjngBoot时生效的,
而是在请求时生效,即通过OpenFeign请求服务时才会检查参数并载入参数。
上面步骤已经填充了Hystrix属性,
下面开始应用,第一步初始化构造器:HystrixCommand,
源码如下图所示:
位置:com.netflix.hystrix.HystrixCommand#HystrixCommand(com.netflix.hystrix.HystrixCommand.Setter)
在这里插入图片描述
通过继承HystrixCommandProperties填充属性,
源码如下图所示,因为在配置文件自定义了Hystrix属性,
所以会使用自定义的builder。
位置:com.netflix.hystrix.strategy.properties.HystrixPropertiesCommandDefault
在这里插入图片描述

如上所述,自定义的Hystrix属性,会进入自定义builder,
构造器:HystrixCommandProperties源码如下图所示,
位置:com.netflix.hystrix.HystrixCommandProperties#HystrixCommandProperties(com.netflix.hystrix.HystrixCommandKey, com.netflix.hystrix.HystrixCommandProperties.Setter)
在这里插入图片描述

这里讲一下如何读取配置文件值,源码如下图所示,
通过数据绑定后的前缀获取数据。
位置:com.netflix.hystrix.HystrixCommandProperties#getProperty(java.lang.String, com.netflix.hystrix.HystrixCommandKey, java.lang.String, java.lang.Boolean, java.lang.Boolean)
在这里插入图片描述

4.3 Ribbion

执行Hystrix相关的命令时,会进入Ribbon的切面,
首先是Ribbon的负载均衡,
位置:org.springframework.cloud.openfeign.ribbon.FeignLoadBalancer

其次是填充Ribbon属性:
位置:org.springframework.cloud.netflix.ribbon.RibbonProperties

Ribbon默认属性:
位置:com.netflix.client.config.DefaultClientConfigImpl

  • 如何读取ribbon的配置?
    通过Environment读取yml配置,源码如下图所示,
    位置:org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration#getProperty

在这里插入图片描述

使用Environment的ribbion属性值配置Ribbon,源码如下图所示,
位置:org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration#ribbonClientConfig
这些属性的初始化是在使用OpenFeign调用时才进行的,
SpringBoot启动时不会初始化Ribbon的相关配置(怎么知道的?断点调试,在【ribbonClientConfig内打断点】,以Debug方式启动SpringBoot,发现没有进入ribbonClientConfig断点,于是,请求,发现,进入断点),
由源码可知,如果没有自定义Ribbon参数,则会使用默认的Ribbon配置。
在这里插入图片描述

Ribbon配置的键名,源码如下图所示,
由源码可知,Ribbon绑定的属性名是大驼峰格式,
所以配置时需要按照这个约定,
位置:com.netflix.client.config.CommonClientConfigKey
在这里插入图片描述

4.4 执行回调

当使用OpenFeigin调用服务,触发熔断后,会使用Hystrix线程池调用fallback方法,
由上面的测试日志可知,调用fallback的线程:hystrix-microuser-n,默认n由10个,即1-10,可看源码找到Hystrix的ThreadPool。
回调有两种调用方式:同步回调和异步回调。

4.4.1 同步回调

同步回调使用execute,源码如下图所示,
这是HystrixCommand继承com.netflix.hystrix.HystrixExecutable而实现的方法,
由源码可知,HystrixCommand中调用了queue.get(),这其实是异步回调中的方法,
只是在同步回调中使用Future.get,所以是同步的,因为需要等待响应结果,
为什么是同步的,参考:Java基础系列:多线程的同步和异步
位置:com.netflix.hystrix.HystrixCommand#execute

在这里插入图片描述

4.4.2 异步回调

异步回调的源码如下图所示,
由源码可知,使用了Future作为返回类型,
由此可知,该方法使用了JUC并发编程,但是,获取结果的方式决定了同步和异步,
为什么是异步的,参考:Java基础系列:多线程的同步和异步
位置:com.netflix.hystrix.HystrixCommand#queue
在这里插入图片描述

4.5 熔断和降级流程

通过上面的分析,
总结一下Hystrix的熔断和降级的流程示意图,如下图所示。
在这里插入图片描述

5 小结

核心:
(1)Hystrix熔断和降级流程:自动装配相关Bean-》数据绑定-》OpenFeign请求服务-》HystrixCommanProperties填充数据-》HystrixCommand初始化-》HystrixExecutable.execute执行回调方法;
(2)Hystrix是通过线程隔离的方式进行熔断,执行回调由同步执行和异步执行两种方式;
(3)在熔断等待期间,即使被服务重新可用,也不会将请求发送到启动的服务,而是直接调用自身的回调方法,保证及时响应;
(4)被调用的服务过程:在线但超过Ribbon的响应时间,调用方即使使用了回调方法,但是请求仍然会到服务,只是不等待结果;
(5)为OpenFeign开启Hystrix:feign.hystrix.enabled=true;
(6)Hystrix结合Ribbon使用。

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天然玩家

坚持才能做到极致

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值