SpringCloud~~~【Hystrix】
Hystrix是一种用于处理分布式系统的延迟和容错的开源库,在分布式系统中,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能保证在一个依赖出现问题的情况下,不会导致整体服务的失败,避免级联故障,以提供分布式系统的弹性。
断路器本身是一种开关装置,当某个服务发生故障之后,通过断路器的故障监控,向调用方返回一个符合预期的、可处理的备选响应(Fallback),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要的占用,从而避免故障在分布式系统中蔓延,乃至雪崩。
网址:https://github.com/Netflix/Hystrix/wiki/How-To-Use
重要概念
服务降级
哪些情况会触发服务降级:
- 程序运行异常
- 超时
- 服务熔断触发服务降级
- 线程池/信号量打满也会导致服务降级
服务熔断
类比保险丝达到最大服务访问后,直接拒绝访问,然后调用服务降级的方法并返回友好提示
服务限流
秒杀高并发等操作,排队限流,有序进行。
案例
搭建
- pom
<dependencies>
<!--hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</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>
<dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</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>
- yml
server:
port: 8001
spring:
application:
name: cloud-provider-hystrix-payment
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
#defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
defaultZone: http://eureka7001.com:7001/eureka
- 主启动类
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
public class PaymentHystrixMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.class,args);
}
}
- 业务类
service:
@Service
public class PaymentService {
/**
* 正常访问ok的方法
* @param id
* @return
*/
public String paymentInfo_ok(Integer id){
return "线程池:" + Thread.currentThread().getName()+" paymentInfo_ok,id: "+id+"\t"+"~~~";
}
/**
* 模拟超时
* @param id
* @return
*/
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "5000")
})
public String paymentInfo_TimeOut(Integer id){
//int age = 10/0;
int timeNumber = 3;
try { TimeUnit.SECONDS.sleep(timeNumber); } catch (InterruptedException e) { e.printStackTrace(); }
return "线程池:" + Thread.currentThread().getName()+" paymentInfo_TimeOut,id: "+id+"\t"+"耗时:" +timeNumber;
}
}
controller:
@RestController
@Slf4j
public class PaymentController {
@Resource
private PaymentService paymentService;
@Value("${server.port}")
private String serverPort;
@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfo_ok(@PathVariable("id") Integer id){
String result = paymentService.paymentInfo_ok(id);
log.info("******result:" + result);
return result;
}
@GetMapping("/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
String result = paymentService.paymentInfo_TimeOut(id);
log.info("******result:" + result);
return result;
}
}
高并发测试
JMeter压测配置:
此时发现timeout和ok请求路径都开始出现等待。
消费端服务dajian
- pom
<dependencies>
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<dependency>
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</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>
<!--一般基础通用配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</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>
- yml
server:
port: 80
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
feign:
hystrix:
enabled: true
- 启动类
@SpringBootApplication
@EnableFeignClients
@EnableHystrix
public class OrderHystrixMain80
{
public static void main(String[] args)
{
SpringApplication.run(OrderHystrixMain80.class,args);
}
}
- 业务类
service:
@Component
public class PaymentFallbackService implements PaymentHystrixService {
@Override
public String paymentInfo_ok(Integer id) {
return "----PaymentFallbackService----fall back";
}
@Override
public String paymentInfo_TimeOut(Integer id) {
return "----PaymentFallbackService----fall back";
}
}
Feign接口:
@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",fallback = PaymentFallbackService.class)
public interface PaymentHystrixService {
@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfo_ok(@PathVariable("id") Integer id);
@GetMapping("/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id);
}
在正常的项目中可以把Feign接口调用单独抽取
controller:
@RestController
@Slf4j
//全局配置异常或者超时兜底方法,实现服务降级,避免代码膨胀,使用统一方法,局部也可以自定义,就近原则
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")
public class OrderHystrixController {
@Resource
private PaymentHystrixService paymentHystrixService;
@GetMapping("/consumer/payment/hystrix/ok/{id}")
public String paymentInfo_ok(@PathVariable("id") Integer id){
String result = paymentHystrixService.paymentInfo_ok(id);
return result;
}
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
// @HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod",commandProperties = {
// @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1500")
// })
@HystrixCommand
public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
String result = paymentHystrixService.paymentInfo_TimeOut(id);
return result;
}
public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id){
return "我是消费者80,对方支付系统繁忙请10秒钟后再试,或者自己运行出错请检查自己。。。。";
}
public String payment_Global_FallbackMethod(){
return "Global异常处理信息,请稍后再试";
}
}
降级配置
@HystrixCommand注解
80服务上述已经配置,启动类添加@EnableHystrix注解。
8001服务:一旦调用服务方法失败并抛出错误信息后,会自动调用@@HystrixCommand注解中的fallbackMethod调用类中的指定方法paymentInfo_TimeOutHandler
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "5000")
})
public String paymentInfo_TimeOut(Integer id){
//int age = 10/0;
int timeNumber = 3;
try { TimeUnit.SECONDS.sleep(timeNumber); } catch (InterruptedException e) { e.printStackTrace(); }
return "线程池:" + Thread.currentThread().getName()+" paymentInfo_TimeOut,id: "+id+"\t"+"耗时:"+timeNumber;
}
/**
* 超时兜底,服务降级
* @param id
* @return
*/
public String paymentInfo_TimeOutHandler(Integer id){
return "线程池:" + Thread.currentThread().getName()+" 8001系统繁忙或者运行报错,请稍后再试,id: "+id+"\t"+"失败";
}
主启动类激活:
@EnableCircuitBreaker
解决代码膨胀问题
@DefaultProperties(defaultFallback="")
除了个别重要核心业务有专属的fallback,其他普通的可以使用上述注解统一跳转到统一处理结果页面。减少代码量。
//全局配置异常或者超时兜底方法,实现服务降级,避免代码膨胀,使用统一方法,局部也可以自定义,就近原则
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")
public String payment_Global_FallbackMethod(){
return "Global异常处理信息,请稍后再试";
}
若@HystrixCommand中没有指定fallback则使用全局的。
实现fallback与业务类解耦
在80端新建一个PaymentFallbackService类实现Feign接口类,对feign接口里面的方法进行异常处理。
PaymentFallbackService:
@Component
public class PaymentFallbackService implements PaymentHystrixService {
@Override
public String paymentInfo_ok(Integer id) {
return "----PaymentFallbackService----fall back";
}
@Override
public String paymentInfo_TimeOut(Integer id) {
return "----PaymentFallbackService----fall back";
}
}
注意:PaymentHystrixService中fallback的配置如下
为了明白,截个图:
@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",fallback = PaymentFallbackService.class)
public interface PaymentHystrixService {
@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfo_ok(@PathVariable("id") Integer id);
@GetMapping("/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id);
}
逻辑有点乱,需要总结~~~~
服务熔断理论
熔断机制是应对雪崩效应的一种微服务链路保护机制。删除链路的某个微服务出错不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息。当检测到该节点微服务调用响应正常后,恢复链路调用。
使用案例
8001服务:
PaymentService:
//================服务熔断===============================
@HystrixCommand(fallbackMethod = "paymentCircuitBreaker_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")//失败率达到多少后跳闸,百分比
})
public String paymentCircuitBreaker(@PathVariable("id") Integer id){
if (id < 0){
throw new RuntimeException("--------id 不能为负数");
}
String seriaNumber = IdUtil.simpleUUID();
return Thread.currentThread().getName() + "\t" + "调用成功,流水号:" + seriaNumber;
}
public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id){
return "id不能为负数,请稍后再试。id:" + id;
}
PaymentController:
//======服务熔断=====
@GetMapping("/payment/circuit/{id}")
public String paymentCircuitBreaker(@PathVariable("id")Integer id){
String result = paymentService.paymentCircuitBreaker(id);
log.info("result: " + result);
return result;
}
测试:
当测试id为负数出现次数过多,服务器发生熔断,此时请求id为正数的地址:
换成正数之后慢慢的恢复到half-open再到正常状态。
熔断的类型
- 熔断打开:请求不再进行调用当前服务,内部设置时钟一般为MTTR(平均故障处理时间),当打开时长达到所设时钟则进入半熔断状态。
- 熔断关闭:熔断关闭不会对服务进行熔断。
- 熔断半开:部分请求根据规则调用当前服务,如果请求成功且符合规则则认为当前服务恢复正常,关闭熔断。
Hystrix仪表盘图形化界面
搭建服务
- pom
<dependencies>
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</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>
- yml
server:
port: 9001
- 主启动类
@SpringBootApplication
@EnableHystrixDashboard
public class HystrixDashBoardMain9001 {
public static void main(String[] args) {
SpringApplication.run(HystrixDashBoardMain9001.class,args);
}
}
启动后访问http://127.0.0.1:9001/hystrix
8001服务主启动类配置:
/****
* 此处配置是为了服务监控而配置,与服务容错本身无关
* ServletRegistrationBean因为springboot的默认路径不是"/hystrix.stream"
* 只要在自己的项目配置上下文的servlet即可
* @return
*/
@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页面填入8001服务地址,开启监控。