什么是Hystrix
Hystrix是一个用于处理分布式系统的 延迟 和 容错 的开源库,在分布式系统里,许多依赖不可避免的会调用失败,
比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下, 不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性,稳定性。
雪崩效应
复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免地失败。
多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它的微服务,这就是所谓的 “扇出” 。
如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”.
例如曾经发生的一个案例,某个电子商务网站在某个星期五发生过载,过多的并法请求,
导致用户支付请求延迟很久没有响应,在等待很长时间后最终失败,支付失败又导致用户重新刷新页面再次尝试支付,
进一步增加了服务器的负载,最终整个系统崩溃了。
如何容错
想要避免雪崩效应,必须有强大的容错机制,该容错机制需要实现以下两点
- 为网络请求设置超时机制
即便是没有请求到数据 也强制结束 避免请求堆积
- 使用断路器模式
正常情况下断路器关闭可正常请求服务,当一定时间内请求达到了一定的阈(yu)值(请求失败率达到百分之50或者100次/分钟),断路器就会打开此时不会再去请求依赖服务。
断路器打开一段时间之后,会自动进入半开的状态。此时断路器允许一个请求请求服务实例,如果该服务可以调用成功,则关闭断路器,否则继续保持打开状态。
通用方式整合Hystrix(Ribbon)
<?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 https://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.0.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>SpringCloud-OrdersForGoods</groupId>
<artifactId>ordersforgoods</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>ordersforgoods</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!--hystrix 熔断器-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--ribbon-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<!-- 引入SpringCloud Eureka client的依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>2.0.3.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<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>
<!-- 约束整个项目的SpringCloud版本-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.SR2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
package springcloudordersforgoods;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
/**
* 声明当前客户端为可被发现
* 不仅可以注册到Eureka Server 也可以注册到其他注册中心
*/
@EnableDiscoveryClient
/**
* 开启熔断器
*/
@EnableHystrix
public class OrdersforgoodsApplication {
public static void main(String[] args) {
SpringApplication.run(OrdersforgoodsApplication.class, args);
}
/**
* @LoadBalanced 声明当前项目可以使用ribbon负载均衡
* @return
*/
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
server:
port: 8002
# 指定当前服务名称 这个名称会注册到注册中心
spring:
application:
name: ordersforgoods
# 指定 服务注册中心的地址
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8888/eureka,http://localhost:8889/eureka
package springcloudordersforgoods.Conterller;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
public class Conterllers {
@Value("${server.port}")//获取配置文件数据
private String post;
@Autowired
private RestTemplate restTemplate;
/**
* 当前方法设置断路器设置
* @HystrixCommand(fallbackMethod = "gettHystrixCommand")
* 在当前方法上开启断路器的设置
* fallbackMethod 回调的方法,如果方法调用失败则执行回调方法,代替当前方法
* @param id
* @return
*/
@HystrixCommand(fallbackMethod = "gettHystrixCommand")// 配置替代方案方法
@GetMapping("orderidpost")
public String getproductbyid(Integer id){
/**
* 调用商品服务一起返回
*/
/**
* get 开头都为get请求
*
* getForObject (请求地址,
* 响应参数得类型,
* 请求参数(可不写))
* 返回值为请求接口响应的数据
*
* getForEntity(请求地址,
* 响应参数得类型,
* 请求参数(可不写))
* 返回结果为响应体对象
* 响应体对象 不仅有相应数据 还有响应状态码
* 相对于getForObject 信息更全面
*/
// 服务调用
/**
* 通过RestTemplate 将服务调用 IP+端口号 是写死的
* 这样不能负载均衡
*
* 如 负载均衡不能写成 IP+端口号 需要写成被调用的服务名称
*/
String url = "http://SpringBoot-Cloud/getidpost?id="+id;
String forObject = restTemplate.getForObject(url, String.class);
return "查询成功: 商品信息为:"+forObject;
}
// 替代方案方法,如果调用服务失败, 断路器会执行替代方案方法
public String getHystrixCommand(){
return "服务器忙,请稍后重试.";
}
}
Feign的方式整合Hystrix
<?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 https://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.0.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>EurekaClient</groupId>
<artifactId>eurekaclient</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>eurekaclient</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- feign服务调用 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.0.3.RELEASE</version>
</dependency>
<!-- 引入SpringCloud Eureka client的依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>2.0.3.RELEASE</version>
</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>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<!-- client端,开启健康检查(需要spring-boot-starter-actuator依赖)-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
<!-- 约束整个项目的SpringCloud版本-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.SR2</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>
server:
port: 8001
# 指定当前服务名称 这个名称会注册到注册中心
spring:
application:
name: eureka-client
# 指定 服务注册中心的地址
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8888/eureka,http://localhost:8889/eureka
# Feign日志服务配置
logging:
level:
# 全局日志
# root: debug
# 配置服务调用类的所在的包名
eurekaclient.eurekaclient.Service: debug
eurekaclient.eurekaclient.Conterller: debug
# Feign是自带断路器的,不需要导入额外依赖,需要打开配置
feign:
hystrix:
enabled: true
package eurekaclient.eurekaclient.Conterller;
import eurekaclient.eurekaclient.Entity.Entity;
import eurekaclient.eurekaclient.Service.userfeign;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
@RestController
public class Clientterller {
//@Qualifier("SpringBoot-Cloud")
@Autowired
private userfeign userfeign; // 自动注入接口对象
@GetMapping("userConterller")
public String userConterller(Integer id){
/**
* 和使用其他接口一样, 使用Feign接口中的服务方法
*
* 服务调用细节,如 请求的发送 负载均衡 都被封装了
*/
return "用户服务调用成功: \t"+userfeign.getproductbyid(id);
}
@GetMapping("postuserConterller")
public String postuserConterller(Entity entity){
/**
* 和使用其他接口一样, 使用Feign接口中的服务方法
*
* 服务调用细节,如 请求的发送 负载均衡 都被封装了
*/
HashMap<Object, Object> objectObjectHashMap = new HashMap<>();
objectObjectHashMap.put("msg",entity);
return "用户服务调用成功: \t"+userfeign.postproductbyid(objectObjectHashMap);
}
}
package eurekaclient.eurekaclient.Service;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
/**
* FeignClient声明当前接口为Feign的客户端
* 当前接口就具备了发送HTTP请求的能力
*
* FeignClient(value = "SpringBoot-Cloud") vlaue = 为被调用服务的名字
*/
@FeignClient(value = "SpringBoot-Cloud", fallback = HystrixService.class)
/**
* 在调用时出错,就根据fallback的配置执行替代方案
*/
public interface userfeign {
/***
* 定义服务调用的方法
*
* 当前方法对应被调用的方法-->>需添加@GetMapping注解 参数为被调用方法的路径(全路径)
* 且方法的形参和返回值类型必须和被调用方法一致
*
* 注意! 因为请求方式为Rest风格 所以参数前必须加 @RequestParam注解
* @param id
* @return
*/
//@GetMapping("getidpost")
@GetMapping(path = "getidpost")
String getproductbyid(@RequestParam Integer id);
@PostMapping(path = "postgetidpost")
/**
* 如为Get请求则需要添加@RequestParam注解
* Post请求 添加 @RequestBody
*/
String postproductbyid(@RequestBody Map map);
}
package eurekaclient.eurekaclient.Service;
import org.springframework.stereotype.Service;
import java.util.Map;
/**
* 替代方案
*/
@Service
//这里需要实现服务调用接口,重写其方法
public class HystrixService implements userfeign {
@Override
public String getproductbyid(Integer id) {
return "服务调用失败,参数id为: "+id;
}
@Override
public String postproductbyid(Map map) {
return "服务调用失败,参数id为: "+map;
}
}