一、Hystrix介绍
Hystrix 是Netfix的一个开源的延迟和容错库,用于隔离访问远程系统、服务或者第三方库,防止服务级联失败,从而提升系统的可用性与容错性。Hystrix主要通过以下几点实现延迟和容错。
- 包裹请求:使用@HystrixCommand注解将接口进行包装成一个对象,对第三方系统或者外部依赖进行提供。并且包装的对象将单独运行在一个线程中。此处采用命令模式(设计模式)。
- 跳闸机制:当某个服务的错误率超过一定阈值时,Hystrix可以自动或手动跳闸,停止请求改服务一段时间。
- 资源隔离:Hystrix为每个依赖(依赖即接口,微服务中接口在其上游接口下即为上游接口的依赖)都维护了一个小型的线程池(或者信号量)。如果改线程池已满,发往该依赖的请求会被立即拒绝,而不是排队等待,从而加速失败判定。
- 监控:Hystrix可以近乎实施的监控运行指标和配置的变化,例如成功、失败、超时、以及拒绝的请求信息。
- 回退机制:当请求失败、超时、被拒绝,或当断路器打开时,执行回退逻辑(fallback)。回退逻辑由开发人员定义,例如可以返回一个缺省的返回值。
- 自我修复:断路器打开一段时间后,会自动进入一个“半开”状态。
二、基于RestTemplate调用的消费者工程改造
1,pom引入hystrix的依赖 (新版本的Eureka也支持Hystrix,jar包中包含了Hystrix的模块依赖)
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
2,在启动类上添加@EnableCircuitBreaker激活Hystrix
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
public class RestOrderApplication {
3,配置熔断触发的降级逻辑
在Controller中添加接口降级逻辑,该方法需要与被保护降级的方法参数返回值一致。
/**
* 降级方法,需要和降级的方法入参和返回值一致
* @param id
* @return
*/
public CommonResult<Payment> getPaymentFallBack( Long id){
System.out.println("触发了降级逻辑");
return new CommonResult<>(400,"触发了FallBack降级逻辑。。。",null);
}
4,在需要保护的接口上配置@HystrixCommand注解
在需要保护降级的方法上使用@HystrixCommand注解,并指定其失败时的降级函数。
/**
* 使用注解配置熔断保护
* @param id
* @return
*/
@HystrixCommand(fallbackMethod = "getPaymentFallBack")
@GetMapping("/consumer/product/get/{id}")
public CommonResult<Payment> getPayment(@PathVariable("id") Long id){
CommonResult<Payment> commonResult = restTemplate.getForObject("http://cloud-payment-service/payment/get/"+id,CommonResult.class);
return commonResult;
}
------- 本项目完整结构代码如下(本项目为消费者工程,注册中心使用的Eureka):--------
1.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">
<parent>
<artifactId>SpringCloud</artifactId>
<groupId>com.xiaohui.springCloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>hystrix_order_service_rest</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>com.xiaohui.springCloud</groupId>
<artifactId>cloud-api-common</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Eureka 客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<!-- web依赖开始 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- web依赖结束 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
2,application.yml
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds 指定了触发降级时的超时时间
server:
port: 80 #服务端口
spring:
application:
name: cloud-order-rest-service #服务名称
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://eureka1.com:9000/eureka/
instance:
prefer-ip-address: true #使用ip进行注册
instance-id: ${spring.cloud.client.ip-address}:${server.port} #向注册中心注册服务ID
lease-renewal-interval-in-seconds: 5 #发送心跳间隔时间 秒
lease-expiration-duration-in-seconds: 10 # 服务续约时间 10秒内没有发送心跳(宕机)
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 1000
3,主启动类
package com.xiaohui.hystrix;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
public class RestOrderApplication {
public static void main(String[] args) {
SpringApplication.run(RestOrderApplication.class,args);
}
}
4,配置类
package com.xiaohui.hystrix.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/**
*
* 相当于 spring的 application.xml bean注解相当于 bean 标签
*/
@Configuration
public class ApplicationContextConfig {
@LoadBalanced
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
5,业务类(重点)
在Controller中
public CommonResult<Payment> getPaymentFallBack( Long id) 方法为 public CommonResult<Payment> getPayment(@PathVariable("id") Long id) 的降级方法。
public CommonResult<Payment> defaultFallBack() 方法为 该Controller的全局降级方法。
当方法指定了降级方法则会走方法指定的降级逻辑方法。没有指定的则走全局的降级方法。
package com.xiaohui.hystrix.controller;
import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.xiaohui.springcloud.entities.CommonResult;
import com.xiaohui.springcloud.entities.Payment;
import org.springframework.beans.factory.annotation.Autowired;
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;
@RestController
@DefaultProperties(defaultFallback = "defaultFallBack")
public class OrderController {
@Autowired
private RestTemplate restTemplate;
/**
* 使用注解配置熔断保护
* @param id
* @return
*/
@HystrixCommand(fallbackMethod = "getPaymentFallBack")
@GetMapping("/consumer/product/get/{id}")
public CommonResult<Payment> getPayment(@PathVariable("id") Long id){
CommonResult<Payment> commonResult = restTemplate.getForObject("http://cloud-payment-service/payment/get/"+id,CommonResult.class);
return commonResult;
}
@HystrixCommand
@GetMapping("/consumer/product/get2/{id}")
public CommonResult<Payment> getPayment2(@PathVariable("id") Long id){
CommonResult<Payment> commonResult = restTemplate.getForObject("http://cloud-payment-service/payment/get/"+id,CommonResult.class);
return commonResult;
}
/**
* 降级方法,需要和降级的方法入参和返回值一致
* @param id
* @return
*/
public CommonResult<Payment> getPaymentFallBack( Long id){
System.out.println("触发了降级逻辑");
return new CommonResult<>(400,"触发了FallBack降级逻辑。。。",null);
}
/**
* 统一降级方法不需要入参
* @return
*/
public CommonResult<Payment> defaultFallBack(){
System.out.println("触发了统一降级逻辑");
return new CommonResult<>(400,"触发了defaultFallBack降级逻辑。。。",null);
}
}
三、基于Feign调用的消费者工程改造
1,依赖:由于Feign依赖中包含了Hystrix,已经对Hystrix有了支持,所以不需要在新引入依赖。
2,在配置文件中开启Hystrix。
feign.hystrix.enabled=ture
3,添加feignclient接口实现类,实现降级方法
@Component
public class ProductFeignClientFallBack implements ProductFeignClient {
@Override
public CommonResult getPayment(Long id) {
System.out.println("Feign 降级处理逻辑..");
return new CommonResult(404,"Feign 降级处理逻辑..",null);
}
@Override
public CommonResult getPaymentTimeout() {
System.out.println("Feign timeOut降级处理逻辑...");
return new CommonResult(404,"Feign timeOut降级处理逻辑....",null);
}
}
4,Feignclient接口中使用@FeignClient注解添加指定 fallback实现类。其中name为feign的配置为调用接口服务名。
@FeignClient(name="CLOUD-PAYMENT-SERVICE" ,fallback = ProductFeignClientFallBack.class)
public interface ProductFeignClient {
@RequestMapping(value="/payment/get/{id}",method = RequestMethod.GET)
CommonResult getPayment(@PathVariable("id") Long id);
@GetMapping("/payment/get/timeout")
CommonResult getPaymentTimeout();
}
Feign的项目完整文件目录如下:
1,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">
<parent>
<artifactId>SpringCloud</artifactId>
<groupId>com.xiaohui.springCloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>openfeign_order_service</artifactId>
<dependencies>
<dependency>
<groupId>com.xiaohui.springCloud</groupId>
<artifactId>cloud-api-common</artifactId>
<version>${project.version}</version>
</dependency>
<!-- springCloud 整合的openFeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- Eureka 客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<!-- web依赖开始 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- web依赖结束 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- 健康检查监控 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</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-hystrix-dashboard</artifactId>
</dependency>
</dependencies>
</project>
2,application.yml
server:
port: 9001
spring:
application:
name: openfeign-order-service
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://eureka1.com:9000/eureka/
instance:
prefer-ip-address: true #使用ip进行注册
instance-id: ${spring.cloud.client.ip-address}:${server.port} #向注册中心注册服务ID
lease-renewal-interval-in-seconds: 5 #发送心跳间隔时间 秒
lease-expiration-duration-in-seconds: 10 # 服务续约时间 10秒内没有发送心跳(宕机)
#CLOUD-PAYMENT-SERVICE:
# ribbon:
# NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
#Feign 日志输出配置
feign:
client:
config:
CLOUD-PAYMENT-SERVICE:
loggerLevel: FULL
connectTimeout: 5000
readTimeout: 5000
#开启对hystrix 支持,默认false
hystrix:
enabled: true
logging:
level:
com.xiaohui.springcloud.service.ProductFeignClient: debug
3,启动类(启动类可以不加@EnableCircuitBreaker注解)
package com.xiaohui.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableFeignClients
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class,args);
}
}
4,业务类
Feign接口类
package com.xiaohui.springcloud.service;
import com.xiaohui.springcloud.entities.CommonResult;
import com.xiaohui.springcloud.service.impl.ProductFeignClientFallBack;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@FeignClient(name="CLOUD-PAYMENT-SERVICE" ,fallback = ProductFeignClientFallBack.class)
public interface ProductFeignClient {
@RequestMapping(value="/payment/get/{id}",method = RequestMethod.GET)
CommonResult getPayment(@PathVariable("id") Long id);
@GetMapping("/payment/get/timeout")
CommonResult getPaymentTimeout();
}
Feign 接口降级实现类
package com.xiaohui.springcloud.service.impl;
import com.xiaohui.springcloud.entities.CommonResult;
import com.xiaohui.springcloud.service.ProductFeignClient;
import org.springframework.stereotype.Component;
@Component
public class ProductFeignClientFallBack implements ProductFeignClient {
@Override
public CommonResult getPayment(Long id) {
System.out.println("Feign 降级处理逻辑..");
return new CommonResult(404,"Feign 降级处理逻辑..",null);
}
@Override
public CommonResult getPaymentTimeout() {
System.out.println("Feign timeOut降级处理逻辑...");
return new CommonResult(404,"Feign timeOut降级处理逻辑....",null);
}
}
Controller类
package com.xiaohui.springcloud.controller;
import com.xiaohui.springcloud.entities.CommonResult;
import com.xiaohui.springcloud.entities.Payment;
import com.xiaohui.springcloud.service.ProductFeignClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class OrderController {
@Autowired
private ProductFeignClient productFeignClient;
@GetMapping("/consumer/payment/get/{id}")
public CommonResult<Payment> getPayment(@PathVariable("id") Long id){
return productFeignClient.getPayment(id);
}
@GetMapping("/consumer/payment/get/timeout")
public CommonResult getPaymentTimeout(){
return productFeignClient.getPaymentTimeout();
}
}
只启动Eureka和消费者工程调用测试:根据现象可以看到没有对应的生产者方法时会执行实现的降级方法。