Hystrix是一个用于分布式系统的延迟和容错库,由Netflix开发并开源。它旨在提高应用程序的可靠性和弹性,并通过提供故障转移和回退机制来减少系统出现故障的影响。
Hystrix使用断路器模式来控制和停止远程服务的调用,以防止系统出现过度负载或其他错误。当远程服务的调用失败或超时时,Hystrix将采取适当的措施,例如使用回退方法返回预设值或提供备用服务。
Hystrix还提供了实时指标和监视功能,使开发人员能够监视应用程序的健康状况,并进行必要的调整和优化。
在微服务架构中,Hystrix可以在服务之间提供容错保护,以确保整个系统的可靠性和弹性。通过使用Hystrix,开发人员可以构建更具弹性和可靠性的分布式系统。
服务降级是一种应对系统高并发、故障等异常情况的策略。在系统资源紧张或者出现异常的情况下,为了保证核心功能的稳定性,可以暂时关闭一些非核心或者不重要的服务,降低服务质量,但保证核心服务的可用性。
比如,在大促销活动期间,电商平台的订单处理服务压力会比较大,为了保证订单核心服务的可用性,可以暂时关闭用户评价服务,避免评价服务带来的额外压力。
服务熔断是一种应对服务故障的策略。当服务出现故障或异常时,及时停止请求该服务,避免故障扩散到整个系统中,进而保证系统的可用性。
比如,当某个服务不可用时,可以在服务调用之前设置一个超时时间,如果服务没有及时响应或者返回错误,就及时停止请求该服务,并进行相应的降级处理。
服务限流是一种控制系统请求量的策略。在系统高并发的情况下,可以通过限制并发请求数、请求速率等方式,控制系统的请求量,避免系统过载。
比如,可以设置每秒钟只允许处理一定数量的请求,超出限制的请求就会被拒绝,从而保证系统的稳定性。
新建一个服务端user的module,参照user服务
新建lf-hystrix-user服务
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-hystrixartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
application.yml
server:
port: 9001
spring:
application:
name: lf-hystrix-user
main:
allow-bean-definition-overriding: true #当遇到同样名字的时候,是否允许覆盖注册
eureka:
instance:
# 配置eureka的状态显示
hostname: localhost
instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}
client:
#表示是否将自己注册进EurekaServer默认为true。
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
service-url:
#defaultZone: http://localhost:7001/eureka
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka # 集群版
启动类UserApplication
@SpringBootApplication
@EnableEurekaClient
public class UserApplication {
public static void main(String[] args) {
SpringApplication.run(UserApplication.class,args);
}
}
业务类UserController
一个正常返回的接口
一个延时返回的接口
@RestController
@RequestMapping("/user")
public class UserController {
/**
* 获取当前用户信息
*/
@GetMapping("/info/{username}")
public String info(@PathVariable("username") String username)
{
return username + " login success 端口:9001";
}
/**
* 获取当前用户信息timeout
*/
@GetMapping("/info/timeout/{username}")
public String infoTimeOut(@PathVariable("username") String username)
{
try {
Thread.sleep(5*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return username + " login success 超时端口:9001";
}
}
新建module lf-hystrix-auth
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-hystrixartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
application.yml
server:
port: 9002
spring:
application:
name: lf-hystrix-auth
eureka:
instance:
# 配置eureka的状态显示
hostname: localhost
instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}
client:
#表示是否将自己注册进EurekaServer默认为true。
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka # 集群版
启动类HystrixAuthApplication
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class HystrixAuthApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixAuthApplication.class,args);
}
}
业务类service
@FeignClient(value = "LF-HYSTRIX-USER")
public interface AuthService {
@GetMapping("/user/info/{username}")
String getUserInfo(@PathVariable("username") String username);
@GetMapping("/user/info/timeout/{username}")
String getUserInfoTimeOut(@PathVariable("username") String username);
}
业务类controller
@RestController
@RequestMapping("/auth")
public class AuthController {
@Autowired
AuthService authService;
@PostMapping("login")
public String login(@RequestBody String name)
{
return authService.getUserInfo(name);
}
@PostMapping("/login/timeout")
public String loginTimeout(@RequestBody String name)
{
return authService.getUserInfoTimeOut(name);
}
}
使用jMeter多线程高并发来直接请求服务端user的超时接口。
200个线程数,循环100次,1秒钟启动完毕
http请求配置
启动各个服务。
jMeter启动高并发的请求user服务的timeout接口。
同时,我们请求直接请求user服务的正常接口观察。
发现正常接口的调用也明显变慢。
这是由于tomcat的默认的工作线程数被打满了,没有多余的线程来分解压力和处理。
启动lf-hystrix-auth服务。
同样高并发的请求user服务的timeout接口。
这次观察使用auth服务调用普通接口的情况。
如图要么会等待很久,要么会超时报错
业务类UserController
@RestController
@RequestMapping("/user")
public class UserController {
/**
* 获取当前用户信息
*/
@GetMapping("/info/{username}")
public String info(@PathVariable("username") String username)
{
return username + " login success 端口:9001";
}
/**
* 获取当前用户信息timeout
*/
@GetMapping("/info/timeout/{username}")
@HystrixCommand(fallbackMethod = "userInfoTimeOutHandler", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000")
})
public String infoTimeOut(@PathVariable("username") String username)
{
try {
Thread.sleep(5*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return username + " login success 超时端口:9001";
}
/**
* 超时访问的降级方法
*
* @param username
* @return
*/
public String userInfoTimeOutHandler(String username) {
return "/(ㄒoㄒ)/调用用户信息接口超时或异常:\t" + "\t当前线程池名字" + Thread.currentThread().getName();
}
}
主要修改是,添加@HystrixCommand注解,定义降级的具体处理方法
启动类添加注解
@EnableCircuitBreaker
我们定义的是3s超时,而方法中延迟5秒,看结果
可以看到异常情况会被捕获到,走fallbackMethod的方法来处理。
启动类修改
添加注解@EnableHystrix
application.yml修改
主要是设计超时时间改为5s
server:
port: 9002
spring:
application:
name: lf-hystrix-auth
eureka:
instance:
# 配置eureka的状态显示
hostname: localhost
instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}
client:
#表示是否将自己注册进EurekaServer默认为true。
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka # 集群版
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 5000
feign:
client:
config:
## default 设置的全局超时时间,指定服务名称可以设置单个服务的超时时间
default:
connectTimeout: 5000
readTimeout: 5000
修改业务类AuthController
和服务端一样,设置一个fallbackMethod
@RestController
@RequestMapping("/auth")
public class AuthController {
@Autowired
AuthService authService;
@PostMapping("login")
public String login(@RequestBody String name)
{
return authService.getUserInfo(name);
}
@PostMapping("/login/timeout")
@HystrixCommand(fallbackMethod = "loginTimeOutHandler", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500")
})
public String loginTimeout(@RequestBody String name)
{
return authService.getUserInfoTimeOut(name);
}
/**
* 超时访问的降级方法
*
* @param username
* @return
*/
public String loginTimeOutHandler(String username) {
return "/(ㄒoㄒ)/调用登录接口超时或异常:\t" + "\t当前线程池名字" + Thread.currentThread().getName();
}
}
如图,走到了loginTimeOutHandler的方法,这是因为auth服务的超时时间是1.5s,而user服务端的接口有5s钟延迟。且服务端的超时时间是3s才触发,所以此处是auth的降级方法生效了。
我们把auth端的超时时间设为4s,再看看效果,就是服务端降级方法先生效了。
服务端user降级方法生效
通过上面的例子我们也可以发现,每个方法都加一个配置降级方法,代码膨胀。
我们可以用@DefaultProperties(defaultFallback = “”)对类中的方法统一配置全局异常处理,需要单独配置的才用上面的方法。
修改AuthController
@RestController
@RequestMapping("/auth")
@DefaultProperties(defaultFallback = "globalFallbackHandler")
public class AuthController {
@Autowired
AuthService authService;
@PostMapping("login")
public String login(@RequestBody String name)
{
return authService.getUserInfo(name);
}
@PostMapping("/login/timeout")
@HystrixCommand
public String loginTimeout(@RequestBody String name)
{
int a =1/0;
return authService.getUserInfoTimeOut(name);
}
/**
* 全局的降级方法
*/
public String globalFallbackHandler() {
return "/(ㄒoㄒ)/调用登录接口超时或异常:\t" + "\t当前线程池名字" + Thread.currentThread().getName();
}
/**
* 超时访问的降级方法
*
* @param username
* @return
*/
public String loginTimeOutHandler(String username) {
return "/(ㄒoㄒ)/全局异常降级处理:\t" + "\t当前线程池名字" + Thread.currentThread().getName();
}
}
如图,我们设置一个全局降级方法,然后写一个异常int a= 1/0测试效果。
Hystrix也可以对feign接口统一配置。例如服务端宕机了,可以统一处理进行服务降级。只需要为Feign客户端定义的接口添加一个服务降级处理的实现类即可实现解耦。
新增一个异常处理类AuthFallbakServiceImpl,实现feign注解的接口
AuthFallbakServiceImpl类
@Component
public class AuthFallbakServiceImpl implements AuthService {
@Override
public String getUserInfo(String username) {
return "====AuthService fall back getUserInfo,o(╥﹏╥)o====";
}
@Override
public String getUserInfoTimeOut(String username) {
return "====AuthService fall back getUserInfoTimeOut,o(╥﹏╥)o====";
}
}
同时修改AuthService @FeignClient注解的信息
@Component
@FeignClient(value = "LF-HYSTRIX-USER",fallback = AuthFallbakServiceImpl.class)
public interface AuthService {
@GetMapping("/user/info/{username}")
String getUserInfo(@PathVariable("username") String username);
@GetMapping("/user/info/timeout/{username}")
String getUserInfoTimeOut(@PathVariable("username") String username);
}
测试
启动项目
故意将服务端user关掉,模拟服务端user宕机,然后请求接口
我们在服务端user上测试
在controller中加入以下代码
// =====服务熔断=====
@HystrixCommand(fallbackMethod = "userCircuitBreaker_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"),// 失败率达到多少后跳闸
})
@GetMapping("/circuit/{id}")
public String paymentCircuitBreaker(@PathVariable("id") Integer id) {
if (id < 0) {
int a =1/0;
}
String serialNumber = UUID.randomUUID().toString();
return Thread.currentThread().getName() + "\t" + "调用成功,流水号: " + serialNumber;
}
public String userCircuitBreaker_fallback(@PathVariable("id") Integer id) {
return "id 不能负数,请稍后再试,/(ㄒoㄒ)/~~ id: " + id;
}
接下来,连续多次测试负数,使其走到降级方法中。然后测试正数,发现正数也开始报错了,等一段时间,正数才会恢复正常,这个等待时间就是时间窗口期
时间窗口期参数代表的是断路器开启后,在休眠窗口期间的时间,即在此期间,HystrixCommand将拒绝尝试执行该命令,而是直接返回一个fallback响应(如果定义了fallback逻辑)。
具体来说,当HystrixCommand执行请求时,如果超时、失败、线程池被占满等情况达到一定阈值时,HystrixCommand会切换至开启状态,此时断路器会拦截请求并返回fallback响应。同时,sleepWindowInMilliseconds参数将启动一个休眠窗口,以限制后续请求的执行,并且只有在休眠窗口期间结束后,才会尝试再次执行HystrixCommand。
请求总数阈值:指定在一段时间内,断路器需要接收的请求总数的最小值。如果请求总数低于此阈值,则断路器不会打开,即使在此期间出现了一些失败的请求。如果请求总数高于此阈值,则断路器可能会打开,阻止进一步的请求到达服务,以保护系统免受额外的负载和损害。
快照时间窗:指定断路器收集统计信息的时间段。在此时间段内,断路器会记录服务的响应时间、成功率和失败率等指标,并根据这些指标进行自适应调节。如果快照时间窗过短,则断路器可能无法准确反映系统的负载和异常情况;如果快照时间窗过长,则断路器可能会对系统的响应时间和失败率产生不必要的延迟和影响。
错误百分比阈值:指定断路器在快照时间窗内记录的失败请求占总请求数的百分比阈值。如果错误百分比超过此阈值,则断路器可能会打开,阻止进一步的请求到达服务,并返回预定义的错误响应(比如fallback),以保护系统免受额外的负载和损害。如果错误百分比低于此阈值,则断路器将保持关闭状态,允许请求正常到达服务。
新建module
cloud-consumer-hystrix-dashboard9999
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboardartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
application.yml
server:
port: 9999
启动类
HystrixDashboardMain9999
@SpringBootApplication
@EnableHystrixDashboard
public class HystrixDashboardMain9999 {
public static void main(String[] args) {
SpringApplication.run(HystrixDashboardMain9999.class, args);
}
}
@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;
}
访问http://localhost:9999/hystrix,并填写服务端地址http://localhost:9001/hystrix.stream
即可进入监控页面
请求服务端接口,就可以看到监控数据了。
监控图界面说明