SpringCloud之Hystrix(断路器)

说明:关于SpringCloud系列的文章中的代码都在码云上面
地址:https://gitee.com/zh_0209_java/springcloud-alibaba.git

分布式系统面临的问题

复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免的失败。
在这里插入图片描述

Hystrix的作用

Hystrix是一个用于分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时,异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性

“断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的,可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间,不必要的占用,从而避免故障在分布式系统中的蔓延,乃至雪崩。

Hystrix的作用就是:服务降级,服务熔断,接近实时的监控等等

那些情况会触发服务降级?
  • 程序运行异常
  • 超时
  • 服务熔断触发服务降级
  • 线程池/信号量打满也会导致服务降级

服务降级:当某个服务不能用时,可以向调用方返回一个符合预期的,可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常

服务熔断:类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法。

服务限流:秒杀高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒钟N个,有序进行

测试Hystrix

创建新的服务cloud-provider-hystrix-payment8001
  1. 修改pom
<dependencies>
		<!--hystrix 依赖-->
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
		</dependency>

		<!--		核心依赖-->
		<dependency>
			<groupId>com.zh.springcloud</groupId>
			<artifactId>core</artifactId>
			<version>1.0-SNAPSHOT</version>
		</dependency>

		<!--引入eureka Client依赖-->
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
		</dependency>

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

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>
  1. 添加配置文件
server:
  port: 8001

spring:
  application:
    name: cloud-payment-hystrix-service
  datasource:
    url: jdbc:mysql://localhost:3306/zh_springcloud?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowMutiQueries=true
    #root
    username: root
    password: 123456
    driver-class-name: com.mysql.jdbc.Driver

#mybatis plus 设置
mybatis-plus:
  mapper-locations: classpath*:mapper/*Mapper.xml
  global-config:
    # 关闭MP3.0自带的banner
    banner: false
    db-config:
      #主键类型  0:"数据库ID自增",1:"该类型为未设置主键类型", 2:"用户输入ID",3:"全局唯一ID (数字类型唯一ID)", 4:"全局唯一ID UUID",5:"字符串全局唯一ID (idWorker 的字符串表示)";
      id-type: 3
      # 默认数据库表下划线命名
      table-underline: true
      # 逻辑已删除值(默认为 1)
      logic-delete-value: 1
      # 逻辑未删除值(默认为 0)
      logic-not-delete-value: 0
  configuration:
    # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    # 返回类型为Map,显示null对应的字段
    call-setters-on-nulls: true

# ============ eureka client ===========
eureka:
  # 设置控制台显示的服务路径
  instance:
    instance-id: payment8001
    prefer-ip-address: true # 访问地址可以显示ip
    # Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
    lease-renewal-interval-in-seconds: 1
    # Eureka 服务端在收到最后一次心跳后等待时间上线,单位为秒(默认是90秒),超时将剔除服务
    lease-expiration-duration-in-seconds: 2
  client:
    # 表示是否将自己注册进eurekaServer,默认为true
    register-with-eureka: true
    # 是否从EurekaServer抓取已有的注册信息,默认为true.单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
    fetchRegistry: true
    service-url:
      # 本机入住eurekaServer 地址
      defaultZone: http://localhost:7001/eureka  # 单机版
#      defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka # 集群版
  1. 修改启动类
package com.zh.springcloud;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

/**
 * @Description:
 * @ClassName PaymentApplication
 * @date: 2021.06.08 14:35
 * @Author: zhanghang
 */
@SpringBootApplication
@EnableEurekaClient
@MapperScan("com.zh.springcloud.mapper")
public class PaymentHystrixApplication8001 {
	public static void main(String[] args) {
		SpringApplication.run(PaymentHystrixApplication8001.class,args);
	}
}
  1. 编写业务类
public interface PaymentService extends IService<Payment> {


	/**
	 * description: 可以正常访问ok
	 * date: 2021年-06月-22日 11:56
	 * author: zhanghang
	 * 
	 * @param id
	 * @return java.lang.String
	 */ 
	String paymentInfo_ok(Integer id);

	/**
	 * description: 模拟出错
	 * date: 2021年-06月-22日 11:56
	 * author: zhanghang
	 *
	 * @param id
	 * @return java.lang.String
	 */
	String paymentInfo_timeout(Integer id);
}


@Service
public class PaymentServiceImpl extends ServiceImpl<PaymentMapper, Payment> implements PaymentService {


	@Override
	public String paymentInfo_ok(Integer id) {
		return "线程:"+Thread.currentThread().getName()+" paymentInfo_ok,id:"+id+" \t "+"O(∩_∩)O";
	}

	@Override
	public String paymentInfo_timeout(Integer id) {
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		return "线程:"+Thread.currentThread().getName()+" paymentInfo_timeout,id:"+id+" \t "+"O(∩_∩)O";
	}
}


@RestController
@Slf4j
public class PaymentController {

	@Autowired
	private PaymentService paymentService;

	@Value("${server.port}")
	private String serverPort;

	@GetMapping("/payment/hystrix/ok/{id}")
	public String paymentinfo_ok(@PathVariable("id") Integer id){
		String result = paymentService.paymentInfo_ok(id);
		log.info("====result ->"+result);
		return result;
	}

	@GetMapping("/payment/hystrix/timeout/{id}")
	public String paymentinfo_timeout(@PathVariable("id") Integer id){
		String result = paymentService.paymentInfo_timeout(id);
		log.info("====result ->"+result);
		return result;
	}

}
  1. 启动服务
    先启动eureka,在启动服务提供者,
    可能出现的问题:因为我在父项目中集成了swagger2.9.2版本,而springfox-swagger2中依赖的guava是20.0版本,但是spring-cloud-starter-netflix-hystrix中依赖的guava为15.0版本,所有启动会tomcat会包jar包冲突的问题,解决办法就是在父项目的dependencyManagement定义中定义guava的版本即可
<!--因为springfox-swagger2是2.9.2版本,引入的guava是20.0版本,
				而hystrix中的hystrix-javanica中引入的guava版本为15.0,所有会引起jar包冲突,所有在父项目中声明好版本的使用 -->
			<dependency>
				<groupId>com.google.guava</groupId>
				<artifactId>guava</artifactId>
				<version>20.0</version>
			</dependency>

当我们使用JMeter 对 timeout接口进行2W请求压测时,在访问ok接口,发现ok接口耗时明显增大。也就是超时接口会拉跨别的接口

创建访问hystrix8001的客户端 cloud-consumer-feign-hystrix-order80 服务

  1. 修改pom
<dependencies>
		<!--open feign -->
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-openfeign</artifactId>
		</dependency>

		<dependency>
			<groupId>com.zh.springcloud</groupId>
			<artifactId>core</artifactId>
			<version>1.0-SNAPSHOT</version>
		</dependency>

		<!--引入eureka Client依赖-->
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
		</dependency>

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

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>
  1. 修改配置文件
server:
  port: 80

spring:
  application:
    name: cloud-order-service
  datasource:
    url: jdbc:mysql://localhost:3306/zh_springcloud?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowMutiQueries=true
    #root
    username: root
    password: 123456
    driver-class-name: com.mysql.jdbc.Driver

  #mybatis plus 设置
mybatis-plus:
  mapper-locations: classpath*:mapper/*Mapper.xml
  global-config:
    # 关闭MP3.0自带的banner
    banner: false
    db-config:
      #主键类型  0:"数据库ID自增",1:"该类型为未设置主键类型", 2:"用户输入ID",3:"全局唯一ID (数字类型唯一ID)", 4:"全局唯一ID UUID",5:"字符串全局唯一ID (idWorker 的字符串表示)";
      id-type: 3
      # 默认数据库表下划线命名
      table-underline: true
      # 逻辑已删除值(默认为 1)
      logic-delete-value: 1
      # 逻辑未删除值(默认为 0)
      logic-not-delete-value: 0
  configuration:
    # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    # 返回类型为Map,显示null对应的字段
    call-setters-on-nulls: true

# ============ eureka client ===========
eureka:
  client:
    # 表示是否将自己注册进eurekaServer,默认为true
    register-with-eureka: true
    # 是否从EurekaServer抓取已有的注册信息,默认为true.单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
    fetchRegistry: true
    service-url:
      # 本机入住eurekaServer 地址
      defaultZone: http://localhost:7001/eureka # 单机版
#      defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka # 集群版

logging:
  level:
    com.zh.springcloud: debug
  1. 新增启动类
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class OrderFeignApplication {

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

}
  1. 新增feign 接口
@Component
// 想要调用提供方的接口,必须加@FeignClient注解,value的值就是提供方服务的服务名称
@FeignClient(value = "cloud-payment-hystrix-service")
public interface PaymentService {

	@GetMapping("/payment/hystrix/ok/{id}")
	public String paymentinfo_ok(@PathVariable("id") Integer id);

	@GetMapping("/payment/hystrix/timeout/{id}")
	public String paymentinfo_timeout(@PathVariable("id") Integer id);
}
  1. 新增controller
@RestController
@Slf4j
public class OrderFeignController {

	// 将feign接口注入进来
	@Autowired
	private PaymentService paymentService;

	@GetMapping("/consumer/payment/hystrix/ok/{id}")
	public Result<?> paymentinfo_ok(@PathVariable Integer id){
		Result<Object> result = new Result<>();
		// 可以直接使用
		String str = paymentService.paymentinfo_ok(id);
		result.setResult(str);
		return result;
	}

	@GetMapping("/consumer/payment/hystrix/timeout/{id}")
	public Result<?> paymentinfo_timeout(@PathVariable Integer id){
		Result<Object> result = new Result<>();
		// 可以直接使用
		String str = paymentService.paymentinfo_timeout(id);
		result.setResult(str);
		return result;
	}
}
  1. 启动80服务进行测试,
    故障现象和导致原因:8001同一层次的其他接口服务被困死,因为tomcat线程池里面的工作线程已经被挤占完毕,80此时调用8001,客户端访问响应缓慢。

解决方案

  1. 超时导致服务器变慢(转圈圈 )-- 超时不在等待
  2. 出错(宕机或程序运行出错)-- 出错要有兜底方案

解决:对方服务(8001)超时或者宕机了,调用者(80)不能一直卡死等待,必须有服务降级

服务降级

先从8001服务提供者找问题:设置自身调用超时时间的峰值,峰值内可以正常运行,超过了需要有兜底的方法处理,作为服务降级的fallback

@HystrixCommand 注解使用在资源接口上,

解释:一旦调用服务方法失败并抛出了错误信息后会自动调用@HystrixCommand 标注好的fallbackMethod 调用类中的指定方法

服务提供者服务降级使用方法:

  1. 在启动类开启hystrix @EnableCircuitBreaker
@SpringBootApplication
@EnableEurekaClient 
@EnableCircuitBreaker // 开启hystrix
@MapperScan("com.zh.springcloud.mapper")
public class PaymentHystrixApplication8001 {
	public static void main(String[] args) {
		SpringApplication.run(PaymentHystrixApplication8001.class,args);
	}
}
  1. 在controller 方法上添加@HystrixCommand注解
@GetMapping("/payment/hystrix/timeout/{id}")
	@HystrixCommand(fallbackMethod = "paymentinfo_timeoutHandler",commandProperties =
		// 设置超时条件
		@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000")
	)
	public String paymentinfo_timeout(@PathVariable("id") Integer id){
		String result = paymentService.paymentInfo_timeout(id);
		log.info("====result ->"+result);
		return result;
	}

	// 当paymentinfo_timeout 超时时调用这个方法
	private String paymentinfo_timeoutHandler(@PathVariable("id") Integer id){

		return "线程:"+Thread.currentThread().getName()+" paymentInfo_timeout,id:"+id+" \t "+"o(╥﹏╥)o";
	}
  1. 设置paymentInfo_timeout方法睡眠5秒,启动8001访问测试发现调用paymentinfo_timeoutHandler方法
    在这里插入图片描述

经测试得出结论:无论是计算异常还是超时异常,只要paymentinfo_timeout方法不可用,都会做服务降级。兜底的方案都是paymentinfo_timeoutHandler方法

服务调用者客户端服务降级
  1. 服务调用者客户端服务降级
<!--hystrix 依赖-->
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
		</dependency>
  1. 在配置文件中添加配置
# 开启feign对hystrix的支持
feign:
  hystrix:
    enabled: true
  1. 在启动类上添加注解@EnableHystrix
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
@EnableHystrix
public class OrderFeignApplication {

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

}
  1. 在业务类上写调用者超时规则以及兜底方法
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
	@HystrixCommand(fallbackMethod = "paymentinfo_timeoutFallbackMethod", commandProperties =
		@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500")
	)
	public Result<?> paymentinfo_timeout(@PathVariable Integer id){
		Result<Object> result = new Result<>();
		// 可以直接使用
//		int a = 10 / 0;
		String str = paymentService.paymentinfo_timeout(id);
		result.setResult(str);
		return result;
	}

	public Result<?> paymentinfo_timeoutFallbackMethod(@PathVariable("id") Integer id){
		Result<Object> result = new Result<>();
		String str ="我是消费者80,对方服务繁忙,请稍后再试!"+id;
		result.setResult(str);
		return result;
	}
  1. 启动测试,发现当调用者超时1.5秒时就会调用paymentinfo_timeoutFallbackMethod方法
服务熔断

简介:类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示。

熔断机制概述

熔断机制是应对雪崩效应的一种微服务链路保护机制。当扇出链路的某个微服务出错不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息

当检查到该节点微服务调用响应正常后,回复调用链路。

在Spring Cloud 框架里,熔断机制通过Hystrix 实现。Hystrix 会监控微服务间调用的状况。

当失败的调用到一定阈值时,缺省是5秒内50次调用失败,就会启动熔断机制。熔断机制的注解是@HystrixCommand

熔断状态
  • 熔断打开
    请求不在进行调用当前服务,内部设置时钟一般为MTTR(平均故障处理时间),当打开时长到达所设时钟则进入半熔断状态
  • 熔断关闭
    熔断关闭不会对服务进行熔断
  • 熔断半开
    部分请求根据规则调用当前服务,如果请求成功且符合规则则认为当前服务恢复正常,关闭熔断
测试服务熔断

修改cloud-provider-hystrix-payment8001项目

  1. 新增接口
@GetMapping("/payment/hystrix/circuitBreaker/{id}")
	@HystrixCommand(fallbackMethod = "circuitBreaker_fallback", commandProperties = {
			@HystrixProperty(name = "circuitBreaker.enabled", value = "true"), // 是否开启断路器
			@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"), // 请求次数
			@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"), // 时间窗口期
			@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60") // 失败率达到多少后跳闸,这里是60%
	})
	public String circuitBreaker(@PathVariable("id") Integer id){
		if (id  < 0 ){
			throw new RuntimeException("不能小于零");
		}
		String str = IdUtil.simpleUUID();
		return Thread.currentThread().getName() + " \t "+ "调用成功,流水号:"+str;
	}

	public String circuitBreaker_fallback(@PathVariable("id") Integer id){
		return "id 不能为负数,请稍后再试,id:"+id;
	}

启动服务访问接口进行测试,多次传入负值,导致服务熔断,在传入正整数,会发现前几次依然是走fallback方法,慢慢的会恢复正常访问

涉及到断路器的三个重要参数: 快照时间窗口,请求总数阈值,错误百分比阈值

  • 快照时间窗口:断路器确定是否打开需要统计一些请求和错误数据,而统计的时间范围就是快照时间窗口,默认为最近的10秒。
  • 请求总数阈值:在快照时间窗内,必须满足请求总数鯯才有资格熔断,默认为20,意味着在10秒内,如果该hystrix命令的调用次数不足20次,即使所有的请求都超时或其他原因失败,断路器都不会打开。
  • 错误百分比阈值:当请求总数在快照时间窗内超过了阈值,比如发生了30次调用,有15次发生了超时异常,也就是超过了50%的错误百分比,在默认设定50%阈值情况下,这时候就会将断路器打开。

当断路器关闭状态时,一段时间之后(默认是5秒),这时候断路器是半开状态,会让其中一个请求进行转发。如果成功,断路器会关闭,若失败,继续开启。
断路器打开之后再有请求调用的时候,将不会调用主逻辑,而是直接调用降级fallback.通过断路器,实现了自动发现的错误并将降级逻辑切换为主逻辑,减少响应延迟的效果。

服务监控HystrixDashboard
简介

除了隔离依赖服务的调用以外,Hystrix 还提供了 准实时的调用监控(Hystrix Dashboard),Hystrix会持续的记录所有通过Hystrix 发起的请求的执行信息,并以统计报表和图形的形式展示给用户,包括每秒执行多少请求多少成功,多少失败等,Netflix通过Hystrix-metrics-event-stream 项目实现了对以上指标的监控,Spring Cloud 也提供了Hystrix Dashboard 的整合,对监控内容转化成可视化界面

新建Hystrix 监控项目 cloud-consumer-hystrix-dashboard9001
  1. 修改pom文件
<dependencies>
		<!--hystrix 依赖-->
		<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-hystrix-dashboard</artifactId>
		</dependency>

		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<scope>runtime</scope>
			<optional>true</optional>
		</dependency>

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

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>
  1. 修改配置文件
server:
  port: 9001
  1. 启动类添加注解@EnableHystrixDashboard
@SpringBootApplication
@EnableHystrixDashboard // 开启Hystrix可视化页面
public class HystrixDashboard9001Application {
	public static void main(String[] args) {
		SpringApplication.run(HystrixDashboard9001Application.class,args);
	}
}
  1. 启动测试
    在这里插入图片描述
修改被监控项目cloud-provider-hystrix-payment8001
  1. 修改pom文件
<!--被监控项目必须添加此依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
  1. 在主启动类里添加Bean
/**
	 * 此配置是为了服务监控而配置,与服务容错本身无关,springcloud升级后的坑
	 * ServletRegistrationBean因为SpringBoot的默认路径不是“、hystrix.stream”,
	 * 只有在自己的项目里配置上下文servlet就可以了
	 */
	@Bean
	public ServletRegistrationBean getServlet(){
		HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
		ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
		registrationBean.setLoadOnStartup(1);
		registrationBean.addUrlMappings("/hystrix.stream");
		registrationBean.setName("HystrixMetricsStreamServlet");
		return registrationBean;
		}
  1. 启动测试
    在这里插入图片描述
    在这里插入图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值