文章目录
1 Spring Cloud Hystrix
1.1 雪崩效应
1.1.1 概念
对于一个复杂的分布式系统,包含的应用可能多达数十个,这些应用有许多依赖项目,每个依赖项目在某个时刻不
可避免会失败导致故障,如果不对这些故障进行隔离,整个分布式系统都可能会崩溃。
在高流量情况下,一个后端的依赖延迟可能会导致所有服务的资源在数秒内变的饱和,这也就意味着,后续如果再
有请求将无法提供服务,应用会出现故障。比故障更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,从
而备份队列、线程和其他资源,从而导致整个系统出现更多级联故障。
1.1.2 解决方案
【1】降级
超时降级、资源不足时(线程或信号量)降级,降级后可以配合降级接口返回托底数据。实现一个 fallback 方法, 当
请求后端服务出现异常的时候, 接收 fallback 方法返回的值。
保证:服务出现问题整个项目还可以继续运行。
【2】熔断
当失败率(如因网络故障/超时造成的失败率高)达到阀值自动触发降级,熔断器触发的快速失败会进行快速恢复。
熔断就是具有特定条件的降级。
保证:服务出现问题整个项目还可以继续运行。
【3】缓存
在调用端使用 SpringCache 对服务端的返回结果进行缓存,出现大量请求服务端也不会出现高负载。
保证:减少对 Application Server 的调用。
【4】请求合并
将一定时间短内的同一目标请求进行合并,解决了 Application Server 的负载激增的问题。
保证:减少对 Application Server 的调用。
1.2 HyStrix
Hystrix 封装了每个依赖项,每个依赖项彼此隔离,当延迟发生时,它会被限制在资源中,并包含回退逻辑,该逻
辑决定在依赖发生任何类型故障时应作出何种响应。
Hystrix 被设计的目标是阻止级联故障,对通过第三方客户端访问的依赖项的延迟和故障进行保护和控制。Hystrix
实现这一目标的大致思路具体如下:
(1) 将外部依赖的访问请求封装在独立的线程中,进行资源隔离。
(2) 对于超出设定阈值的服务调用,直接进行超时,不允许其耗费过长时间阻塞线程。
(3) 每个依赖服务维护一个独立的线程池,一旦线程池满了,直接拒绝服务的调用。
(4) 统计依赖服务调用的成功次数、失败次数、拒绝次数、超时次数等结果。
(5) 在一段时间内,如果服务调用的异常次数超过一定阈值,就会触发熔断停止对特定服务的所有请求,在一定
时间内对服务调用直接降级,一段时间后再次进行自动尝试恢复。
(6) 如果某个服务出现调用失败、被拒绝、超时等异常情况,自动调用 fallback 降级机制。
(7) 实时监控指标和配置变化。
1.3 搭建 HyStrix
1.3.1 搭建注册中心 eureka-server
启动 eureka-server
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DMuUekub-1624770135380)(E:\Java\img\image-20210125201726668.png)]
1.3.2 搭建 hystrix-provider
1.3.2.1 添加依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
1.3.2.2 添加配置文件
server:
port: 8080
eureka:
instance:
hostname: localhost
client:
service-url:
defaultZone: http://localhost:7000/eureka/
spring:
application:
name: hystrix-provider
1.3.2.3 新建控制器
package com.cl.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Author chenlan
* @Description TODO
* @Date 2021/1/25 19:56
* @Version 1.0
*/
@RestController
public class HystrixController {
@GetMapping("/hi")
public String sayHi(){
return "Hello,Java";
}
}
1.3.2.4 启动 hystrix-provider
1.3.3 搭建 eureka-hystrix-client
1.3.3.1 添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</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-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
1.3.3.2 添加注解
package com.cl;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
/**
* @author chenlan
*
* hystrix 消费者客户端
* @EnableHystrix 启动 hystrix 熔断功能
*/
@SpringBootApplication
@EnableEurekaClient
@EnableHystrix
public class EurekaHystrixClientApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaHystrixClientApplication.class, args);
}
}
1.3.3.3 添加配置文件
spring:
application:
name: eureka-hystrix-clinet
server:
port: 80
eureka:
client:
service-url:
defaultZone: http://localhost:7000/eureka/
instance:
hostname: localhost
1.3.3.4 配置负载均衡
package com.cl.config;
import org.springframework.boot.web.client.RestTemplateBuilder;
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;
/**
* @Author chenlan
* @Description TODO
* @Date 2021/1/25 20:31
* @Version 1.0
*/
@Configuration
public class HystrixConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate(RestTemplateBuilder builder){
return builder.build();
}
}
1.3.3.5 新建业务层以及控制层
package com.cl.service;
/**
* @Author chenlan
* @Description TODO
* @Date 2021/1/25 20:34
* @Version 1.0
*/
public interface ClientService {
/**
* 调用远程服务接口方法
*
* @return
*/
String sayHi();
}
package com.cl.service.impl;
import com.cl.service.ClientService;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
/**
* @Author chenlan
* @Description TODO
* @Date 2021/1/25 20:35
* @Version 1.0
*/
@Service
public class ClientServiceImpl implements ClientService {
@Autowired
private RestTemplate restTemplate;
/**
* @HystrixCommand 启动熔断器功能
* fallbackMethod 定义处理回退方法
*
* @return
*/
@HystrixCommand(fallbackMethod = "sayHiError")
@Override
public String sayHi() {
return restTemplate.getForObject("http://hystrix-provider/hi", String.class);
}
/**
* 处理回退方法
*
* @return
*/
public String sayHiError(){
return "bad request";
}
}
package com.cl.controller;
import com.cl.service.ClientService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Author chenlan
* @Description TODO
* @Date 2021/1/25 20:38
* @Version 1.0
*/
@RestController
public class ClientController {
@Autowired
private ClientService clientService;
@GetMapping("/hi")
public String sayHi(){
return clientService.sayHi();
}
}
1.3.3.6 测试结果
启动 eureka-provider
关闭 eureka-provider
服务提供者不可用的情况下,服务消费者发送请求失败,此时会开启熔断器。熔断器打开后,请求会直接执fallbackMethod逻辑,通过快速失败,做到及时处理请求,避免线程被阻塞
1.4 熔断
当一定时间内,异常请求比例(请求超时、网络故障、服务异常等)达到阀值时,启动熔断器,熔断器一旦启动,
则会停止调用具体服务逻辑,通过 fallback 快速返回托底数据,保证服务链的完整。
熔断有自动恢复机制说,如:当熔断器启动后,每隔5秒,尝试将新的请求发送给Application Service,如果服务
可正常执行并返回结果,则关闭熔断器,服务恢复。如果仍旧调用失败,则继续返回托底数据,熔断器持续开启状
态。
降级是出错了返回托底数据,而熔断是出错后如果开启了熔断将会一定时间不在访问 application server
1.4.1 配置熔断参数
package com.cl.service.impl;
import com.cl.service.ClientService;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
/**
* @Author chenlan
* @Description TODO
* @Date 2021/1/25 20:35
* @Version 1.0
*/
@Service
public class ClientServiceImpl implements ClientService {
@Autowired
private RestTemplate restTemplate;
/**
* @HystrixCommand 启动熔断器功能
* fallbackMethod 定义处理回退方法
*
* @return
*/
@HystrixCommand(fallbackMethod = "sayHiError",commandProperties = {
// 请求数量达到 3 个
@HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD,value = "3"),
// 判断时间 每 10s 为一个单位
@HystrixProperty(name = HystrixPropertiesManager.EXECUTION_ISOLATION_THREAD_TIMEOUT_IN_MILLISECONDS,value = "10000"),
// 失败率达到 50%
@HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE,value = "50"),
// 开启熔断后,30s 不再请求远程服务
@HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_SLEEP_WINDOW_IN_MILLISECONDS,value = "30000")
})
@Override
public String sayHi() {
return restTemplate.getForObject("http://hystrix-provider/hi", String.class);
}
/**
* 处理回退方法
*
* @return
*/
public String sayHiError(){
return "bad request";
}
}
1.5 请求缓存
Hystrix 为了降低访问服务的频率,支持将一个请求与返回结果做缓存处理。如果再次请求的 URL 没有变化,那么
Hystrix 不会请求服务,而是直接从缓存中将结果返回。这样可以大大降低访问服务的压力
使用 SpringCache + redis 进行请求缓存
1.5.1 添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
1.5.2 添加配置文件
spring:
redis:
host: 192.168.72.12
1.5.3 开启 SpringCache
// 在启动类添加注解
@EnableCaching
1.5.4 添加缓存
在业务实现类上添加并配置Cacheanle注解
/**
* @return
* @HystrixCommand 启动熔断器功能
* fallbackMethod 定义处理回退方法
*/
@HystrixCommand(fallbackMethod = "sayHiError", commandProperties = {
// 请求数量达到 3 个
@HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD, value = "3"),
// 判断时间 每 10s 为一个单位
@HystrixProperty(name = HystrixPropertiesManager.EXECUTION_ISOLATION_THREAD_TIMEOUT_IN_MILLISECONDS, value = "10000"),
// 失败率达到 50%
@HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE, value = "50"),
// 开启熔断后,30s 不再请求远程服务
@HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_SLEEP_WINDOW_IN_MILLISECONDS, value = "30000")
})
@Cacheable(cacheNames = "com.cl", key = "'sayHi'")
@Override
public String sayHi() {
return restTemplate.getForObject("http://hystrix-provider/hi", String.class);
}
1.5.5 查看缓存数据
1.6 请求合并
1.6.1 分析
优点:
在微服务架构中,我们将一个项目拆分成很多个独立的项目,这些独立的项目通过远程调用来互相配合工作,但
是,在高并发情况下,通信次数的增加会导致总的通信时间增加,同时,线程池的资源也是有限的,高并发环境
导致有大量的线程处于等待状态,进而导致响应延迟,为了解决这些问题,我们需要使用请求合并,将同一时间范
围内的同一请求合并为一条请求,降低 Application Server 的负载。
缺点:
设置请求合并之后,本来一个请求可能 5ms 就搞定了,但是现在必须再等 10ms 看看还有没有其他的请求一起
的,这样一个请求的耗时就从 5ms 增加到 15ms 了,不过,如果我们要发起的命令本身就是一个高延迟的命令,
那么这个时候就可以使用请求合并了,因为这个时候时间窗的时间消耗就显得微不足道了,另外高并发也是请求合
并的一个非常重要的场景。
1.6.2 代码实现
1.6.2.1 配置 Hystrix-provider
ProvderController
@PostMapping("/list")
public List<String> showList(@RequestBody List<String> names) {
ArrayList<String> list = new ArrayList<>();
for (String name : names) {
list.add("姓名" + name);
}
System.out.println("list = " + list);
return list;
}
1.6.2.2 配置 clinet 端
ClientService
/**
* 调用远程服务接口方法
*
* @param name
* @return
*/
Future<String> showList(String name);
ClientServiceImpl
/**
* @HystrixCollapser 使用该注解开启请求合并 返回值类型必须为 Future 使用异步方法进行请求合并
* batchMethod 值为请求合并后的方法
* scope 值为请求方式
* HystrixPropertiesManager.TIMER_DELAY_IN_MILLISECONDS 设置请求时间段
* HystrixPropertiesManager.MAX_REQUESTS_IN_BATCH 设置最大请求数
*
* 在请求时间段内,有最大的请求数的请求进行合并
*
* @param name
* @return
*/
@HystrixCollapser(batchMethod = "myBatchMethod",
scope = com.netflix.hystrix.HystrixCollapser.Scope.GLOBAL,
collapserProperties = {
@HystrixProperty(name = HystrixPropertiesManager.TIMER_DELAY_IN_MILLISECONDS, value = "10"),
@HystrixProperty(name = HystrixPropertiesManager.MAX_REQUESTS_IN_BATCH, value = "200")
})
@Override
public Future<String> showList(String name) {
// @HystrixCollapser 标记的方法 方法体返回 null 即可
return null;
}
/**
* @HystrixCommand 请求合并执行的方法必须有此注解
*
* @param nameList
* @return
*/
@HystrixCommand
public List<String> myBatchMethod(List<String> nameList) {
return restTemplate.postForObject("http://hystrix-provider/list", nameList, List.class);
}
ClientController
@PostMapping("/list")
public String showName() {
try {
return clientService.showList(UUID.randomUUID().toString().substring(0, 5)).get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
return null;
}
}
1.7 Hystrix Dashboard
在微服务架构中,为了保证服务的可用性,防止因为某个服务出现故障导致线程阻塞,出现了 Hystrix 熔断器。因
此熔断器成为了一个反映程序健康性的重要指标,Hystrix Dashboard是监控 Hystrix 熔断器健康状况的一个重要
组件,它提供了数据监控和友好的图形化展示界面
1.7.1 添加依赖
<!--hystrix 监控图形化依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<!--监控信息图形化依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
1.7.2 添加注解
package com.cl;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
/**
* @author chenlan
*
* hystrix 消费者客户端
* @SpringBootApplication springboot 入口启动类
* @EnableEurekaClient 启动 eureka client
* @EnableHystrix 启动 hystrix 熔断功能
* @EnableCaching 开启 spring cache 注解支持
* @EnableHystrixDashboard 启动 hystrix dashboard
*
*/
@SpringBootApplication
@EnableEurekaClient
@EnableHystrix
@EnableCaching
@EnableHystrixDashboard
public class EurekaHystrixClientApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaHystrixClientApplication.class, args);
}
}
1.7.3 添加配置文件
management:
endpoints:
web:
exposure:
include: hystrix.stream
1.8 Feign 中使用 HyStrix 熔断
1.8.1 添加依赖
Feign中包含 Hystrix 中部分功能。所以不需要单独导入 Hystrix 的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
1.8.2 添加配置文件
Feign 自带熔断功能,默认情况下,熔断功能是关闭的,需要在配置中开启熔断功能
server:
port: 80
eureka:
client:
service-url:
defaultZone: http://localhost:7000/eureka/
spring:
application:
name: eureka-feign-client
# feign 开启 hystrix
feign:
hystrix:
enabled: true
1.8.3 添加注解
package com.cl;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* @author chenlan
*
* @EnableEurekaClient 开启 eureka 客户端配置
* @EnableFeignClients 开启 feign 配置
*
*/
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class EurekaFeignClientApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaFeignClientApplication.class, args);
}
}
1.8.4 修改实现类
package com.cl.service;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
/**
* @Author chenlan
* @Description TODO
* @Date 2021/1/22 14:24
* @Version 1.0
*/
@FeignClient(value = "eureka-provider",fallback = ClientServiceImpl.class)
public interface ClientService {
/**
* 调用 注册中心 服务测试方法
*
* @return
*/
@GetMapping(value = "/port")
String sayHello();
/**
* 调用 注册中心 服务并传递参数测试方法
*
* @param username
* @param password
* @return
*/
@GetMapping("/login")
String login(@RequestParam String username, @RequestParam String password);
}
@Component
class ClientServiceImpl implements ClientService{
@Override
public String sayHello() {
return "bad request";
}
@Override
public String login(String username, String password) {
return "bad request";
}
}
@FeignClient 在注解中添加fallback属性配置,指定 ClientServiceImpl 为失败逻辑处理类
失败逻辑处理类LocalItemServiceImpl要实现被@FeignClient修饰的LocalItemService接口并使用@Component 注入到Spring 容器中