Hystrix基本使用
Hystrix基本使用
在讲Hystrix之前先了解一下服务雪崩:
多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其他的微服务,这就是所谓的"扇出"。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,导致服务雪崩
对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒钟内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联障碍。这些都表示需要对故障和延迟进行隔离和管理,以便依赖关系的失败,不能取消整个应用程序或系统。所以,通常当你发现一个模块下的某个实例失败后,这时候这个模块依然还会接收流量,然后这个有问题的模块还调用了其他的模块,这样就会发生级联故障,或者叫雪崩
Hystrix是干嘛的:
Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免会调用失败(由此可知服务降级一般使用在需要调用其他服务方法或被其他服务被调用的方法上,推荐在客户端实现服务降级,即在调用其他服务的方法上使用,当然客户端和服务端同时使用也可以
),比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性
"断路器"本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方法无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩
Hystrix的三个重要概念
- 服务降级
服务器繁忙,请稍后再试,不让客户端等待并立刻返回一个友好提示,fallback,以下情况会发生降级:
程序运行异常、超时、服务熔断触发降级、线程池/信号量打满也会导致服务降级
- 服务熔断
类比保险丝,达到最大服务访问后,直接拒绝访问,然后调用服务降级的方法返回友好提示,当服务调用响应正常后,恢复调用链路
- 服务限流
秒杀高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒钟N个,有序进行
Hystrix之服务端实现服务降级案例
所谓在服务端实现服务降级即在被其他服务调用的方法(普通方法)上添加服务降级功能
Hystrix所需依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
业务层方法添加@HystrixCommand注解实现服务降级
package com.yl.hystrix.payment.service;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
/**
* 支付
*
* @auther Y-wee
*/
@Service
public class PaymentService {
/**
* 模拟异常服务
* fallbackMethod:指定服务超时或异常的降级方法为paymentInfo_TimeOutHandler
* 注意服务降级方法参数要与调用降级的方法参数保持一致
* HystrixProperty:指定服务超过value(3s)调用降级方法
*
* @param id
* @return
*/
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",
commandProperties = {@HystrixProperty(
name = "execution.isolation.thread.timeoutInMilliseconds",
value = "3000")})
public String paymentInfo_TimeOut(Integer id) {
try {
TimeUnit.MILLISECONDS.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "线程池:" + Thread.currentThread().getName() + "paymentInfo_timeout,id:" + id;
}
/**
* 服务降级回调方法
*
* @param id
* @return
*/
public String paymentInfo_TimeOutHandler(Integer id) {
return "线程池:" + Thread.currentThread().getName() + "系统payment繁忙或者运行报错,请稍后再试,id:" + id;
}
}
主启动类添加@EnableCircuitBreaker注解
package com.yl.hystrix.payment;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public class HystrixPayment8001Application {
public static void main(String[] args) {
SpringApplication.run(HystrixPayment8001Application.class,args);
}
}
Hystrix之客户端实现服务降级案例
所谓客户端实现服务降级即在调用其他服务的方法(通过feign调用远程方法)上添加服务降级功能
由于OpenFeign依赖包含hystrix依赖,所以不用再导入hystrix依赖
controller接口方法添加@HystrixCommand注解实现服务降级
package com.yl.hystrix.openfeign.order.controller;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import com.yl.hystrix.openfeign.order.service.PaymentHystrixService;
import lombok.extern.slf4j.Slf4j;
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.RestController;
import javax.annotation.Resource;
/**
* 订单
*
* @auther Y-wee
*/
@RequestMapping("/order")
@RestController
@Slf4j
public class OrderHystrixController {
@Resource
private PaymentHystrixService paymentHystrixService;
/**
* 调用异常服务
*
* @param id id
* @return 调用结果
*/
@GetMapping("/payment/hystrix/timeout/{id}")
@HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod",
commandProperties = {@HystrixProperty(
name = "execution.isolation.thread.timeoutInMilliseconds",
value = "1500")})
public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
String result = paymentHystrixService.paymentInfo_TimeOut(id);
return result;
}
/**
* 服务降级方法
*
* @param id id
* @return 友好提示
*/
public String paymentTimeOutFallbackMethod(Integer id) {
return "我是消费者80,对方支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,o(╥﹏╥)o";
}
}
配置文件开启服务降级
feign:
hystrix:
enabled: true
注意:以上配置和下面的ribbon超时时间配置冲突,会导致ribbon超时时间配置不生效(可能是由于使用了服务降级,超时会调用服务降级方法就不需要配置超时时间了)
ribbon:
ReadTimeout: 7000
ConnectTimeout: 7000
主启动类添加@EnableHystrix注解
package com.yl.hystrix.openfeign.order;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@EnableHystrix
public class HystrixOpenFeignOrder80Application {
public static void main(String[] args) {
SpringApplication.run(HystrixOpenFeignOrder80Application.class,args);
}
}
Hystrix服务降级优化
以上服务降级是基于方法的,而在实际开发中如果每个方法都要配置一个服务降级方法显然是不切实际的
所以,我们需要配置一个全局服务降级方法(在案例二开启服务降级配置的基础上实现),实现步骤:
- controller接口添加@DefaultProperties注解指定默认服务降级方法
- 在需要默认服务降级的方法上添加@HystrixCommand注解
- 编写默认服务降级方法
package com.yl.hystrix.openfeign.order.controller;
import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import com.yl.hystrix.openfeign.order.service.PaymentHystrixService;
import lombok.extern.slf4j.Slf4j;
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.RestController;
import javax.annotation.Resource;
/**
* 订单
*
* @auther Y-wee
*/
@RequestMapping("/order")
@RestController
@Slf4j
// 指定默认服务降级方法
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")
public class OrderHystrixController {
@Resource
private PaymentHystrixService paymentHystrixService;
/**
* 调用异常服务
*
* @param id id
* @return 调用结果
*/
@GetMapping("/payment/hystrix/timeout/{id}")
@HystrixCommand
public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
String result = paymentHystrixService.paymentInfo_TimeOut(id);
return result;
}
/**
* 全局服务降级方法
*
* @return 友好提示
*/
public String payment_Global_FallbackMethod() {
return "Global异常处理信息,请稍后再试,/(ㄒoㄒ)/~~";
}
}
Hystrix通配服务降级
上面服务降级的方法跟服务方法写在一个类中,耦合度高,因此,我们可以写一个类专门实现服务降级
远程服务业务接口,添加@FeignClient注解指定要调用的远程服务名以及服务降级处理类
package com.yl.hystrix.openfeign.order.service;
import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
/**
* 支付业务feign接口
*
* @auther Y-wee
*/
@Component
@FeignClient(value = "PAYMENT", fallback = PaymentFallbackService.class)
public interface PaymentHystrixService {
/**
* 模拟正常服务
*
* @param id
* @return
*/
@GetMapping("/payment/hystrix/ok/{id}")
String paymentInfo_OK(@PathVariable("id") Integer id);
/**
* 模拟异常服务
*
* @param id
* @return
*/
@GetMapping("/payment/hystrix/timeout/{id}")
String paymentInfo_TimeOut(@PathVariable("id") Integer id);
}
编写服务降级处理类实现远程服务接口
package com.yl.hystrix.openfeign.order.service;
import org.springframework.stereotype.Component;
/**
* 支付业务层服务降级接口
*
* @auther Y-wee
*/
@Component
public class PaymentFallbackService implements PaymentHystrixService {
@Override
public String paymentInfo_OK(Integer id) {
return "PaymentFallbackService fall back-paymentInfo_OK ,o(╥﹏╥)o";
}
@Override
public String paymentInfo_TimeOut(Integer id) {
return "PaymentFallbackService fall back-paymentInfo_TimeOut ,o(╥﹏╥)o";
}
}
至此,通配服务降级配置完成,当服务异常时会调用对应服务降级方法返回友好提示
注意:
配置文件要开启服务降级,主启动类添加@EnableHystrix注解
如果同时配置了通配服务降级和全局默认服务降级,则通配服务降级优先级高于全局默认服务降级方法
Hystrix服务熔断
再次回忆一下服务熔断的相关理论:类比保险丝,达到最大服务访问后,直接拒绝访问,然后调用服务降级的方法返回友好提示,当服务调用响应正常后,恢复调用链路
实战
远程接口
package com.yl.hystrix.payment.controller;
import com.yl.hystrix.payment.service.PaymentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
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.RestController;
import javax.annotation.Resource;
/**
* 支付
*
* @auther Y-wee
*/
@RestController
@Slf4j
@RequestMapping("/payment")
public class PaymentController {
@Resource
private PaymentService paymentService;
/**
* 测试服务熔断
*
* @param id id
* @return 测试结果
*/
@GetMapping("/circuit/{id}")
public String paymentCircuitBreaker(@PathVariable("id") Integer id) {
String result = paymentService.paymentCircuitBreaker(id);
log.info("result:" + result);
return result;
}
}
远程服务
package com.yl.hystrix.payment.service;
import cn.hutool.core.util.IdUtil;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.PathVariable;
import java.util.concurrent.TimeUnit;
/**
* 支付
*
* @auther Y-wee
*/
@Service
public class PaymentService {
/**
* 通过id控制服务状态,id<0抛出异常反之正常
*
* @param id
* @return
*/
public String paymentCircuitBreaker(Integer id) {
if (id < 0) {
throw new RuntimeException("id不能是负数");
}
String serialNumber = IdUtil.simpleUUID();
return Thread.currentThread().getName() + "\t" + "调用成功,流水号:" + serialNumber;
}
}
客户端接口
客户端远程服务feign接口配置了通配服务降级
注意:客户端远程服务feign接口配置了通配服务降级后,服务熔断注解不能加在客户端远程服务feign接口方法上,会报错
package com.yl.hystrix.openfeign.order.controller;
import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import com.yl.hystrix.openfeign.order.service.PaymentHystrixService;
import lombok.extern.slf4j.Slf4j;
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.RestController;
import javax.annotation.Resource;
/**
* 订单
*
* @auther Y-wee
*/
@RequestMapping("/order")
@RestController
@Slf4j
public class OrderHystrixController {
@Resource
private PaymentHystrixService paymentHystrixService;
/**
* 测试服务熔断
*
* @param id id
* @return 测试结果
*/
@GetMapping("/payment/circuit/{id}")
@HystrixCommand(
// 在时间窗口期内达到指定请求次数失败率则开启断路器(在10s钟内10请求有6次失败则开启断路器)
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"),
})
public String paymentCircuitBreaker(@PathVariable("id") Integer id) {
return paymentHystrixService.paymentCircuitBreaker(id);
}
}
配置文件要开启服务降级,主启动类添加@EnableHystrix注解
测试结果:请求时设置id为负数多次调用远程服务失败后达到服务熔断条件(在时间窗口期内达到指定请求次数失败率则开启断路器),启用服务熔断,此时设置id为正数远程服务依旧不能正常访问,过一会服务才会恢复正常
Hystrix之Dashboard图形化监控
新建项目,导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
配置文件
server:
port: 9001
主启动类添加@EnableHystrixDashboard注解
package com.yl.dashboard;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
@SpringBootApplication
@EnableHystrixDashboard
public class HystrixDashboard9001Application {
public static void main(String[] args) {
SpringApplication.run(HystrixDashboard9001Application.class,args);
}
}
浏览器访问http://localhost:9001/hystrix即可进入Dashboard首页
实现服务监控
被监控服务一定要导入以下两个依赖
<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>
新版本springboot需要在被监控服务主启动类加入以下配置,熔断器注解@EnableCircuitBreaker(@EnableHystrix)也要加上
package com.yl.hystrix.payment;
import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public class HystrixPayment8001Application {
public static void main(String[] args) {
SpringApplication.run(HystrixPayment8001Application.class, args);
}
/**
* 此配置是为了服务监控而配置,与服务容错本身无关,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;
}
}
启动被监控服务并发送请求进行访问
注意:请求的方法必须开启服务降级或熔断才能被监控到
Dashboard首页输入被监控服务请求地址:http://localhost/hystrix.stream(注意服务端口号我的是80所以默认可以不加,如果是其他端口号则要加上)开启监控,然后就可以看到监控信息了