客户管理系统开发定制超详细讲解Hystrix的正确打开方式以及熔断和降级测试(带源码分析)

1 缘起

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

2 实践

该部分直接讲解如何集成Hystrix实现熔断和降级,
从架构的角度,讲解如何通过构建微服务,集成Hystrix,(完整的服务需要测试者自行搭建,这里讲解架构和具体的实验结果)
完成熔断和降级的测试和验证工作,
本文使用的微服务架构非常简单,如下图所示,
注册中心:;
被调用方:User服务;
调用方:Data服务(集成,调用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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

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服务的地址
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

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();        }    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72

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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

2.3.2 添加OpenFeign调用

Data服务使用OpenFeign调用User服务,
因此只需在Data服务中配置User服务的名称和URI即可完成调用,
为了使OpenFeign具有回调的实际内容(方法),需要在@FeignClient中配置fallback属性,
配置代码如下。
因为向注册中心Eureka注册,IP和PORT已在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);    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

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 */@Componentpublic 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);    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

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);    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 使用注解方式回调熔断方法
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);    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71

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();        }    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 响应结果

    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,所以是同步的,因为需要等待响应结果,
为什么是同步的,参考:
位置:com.netflix.hystrix.HystrixCommand#execute

4.4.2 异步回调

异步回调的源码如下图所示,
由源码可知,使用了Future作为返回类型,
由此可知,该方法使用了JUC并发编程,但是,获取结果的方式决定了同步和异步,
为什么是异步的,参考:
位置: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使用。

网站建设定制开发 软件系统开发定制 定制软件开发 软件开发定制 定制app开发 app开发定制 app开发定制公司 电商商城定制开发 定制小程序开发 定制开发小程序 客户管理系统开发定制 定制网站 定制开发 crm开发定制 开发公司 小程序开发定制 定制软件 收款定制开发 企业网站定制开发 定制化开发 android系统定制开发 定制小程序开发费用 定制设计 专注app软件定制开发 软件开发定制定制 知名网站建设定制 软件定制开发供应商 应用系统定制开发 软件系统定制开发 企业管理系统定制开发 系统定制开发