一、概述
1、分布式系统面临的问题
复杂分布式体系机构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免地失败
1.1 服务雪崩
多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其他的微服务,这就是所谓的“扇出”如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”。
对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒钟内饱和,比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。
通常当你发现一个模块下某个实例失败后,这时候这个模块依然还会接收流量,然而这个有问题的模块还调用了其他模块,这样就会发生级联故障,或者叫雪崩。
2、Hystrix 是什么
- Hystrix 是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时,异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。
- 断路器本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的,可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩
- Hystrxi虽然已经停更,但是出道即巅峰,很多优秀的理念需要学习。
- 官方文档:https://github.com/Netflix/Hystrix
3、Hystrix能做什么
- 服务降级:比如当某个服务繁忙,不能让客户端的请求一直等待,应该立刻返回给客户端一个备选方案。
- 服务熔断:当某个服务出现问题,卡死了,不能让用户一直等待,需要关闭所有对此服务的访问然后调用服务降级。
- 服务限流:比如秒杀场景,不能访问用户瞬间都访问服务器,限制一次只可以有多少请求。
-
4、注解说明
@EnableHystrix 包含了 @EnableCircuitBreaker,所以后续使用 @EnableHystrix。二、Hystrix 重要概念
1、服务降级 (fallback)
服务器繁忙,请稍后再试,不让客户端等待并立刻返回一个好友提示。
哪些情况会触发降级: 程序运行异常
- 超时
- 服务熔断触发服务降级
-
2、服务熔断 (break)
类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示
服务熔断是达到某些条件后就断开服务【断路器】,然后调用服务降级。
- 当检测到该服务响应正常后,恢复调用链路。
3、服务限流 (flowlimit)
秒杀高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒钟N个,有序进行。三、微服务模块构建
1、 服务提供者模块搭建
- 创建模块 cloud-provider-hystrix-payment8001
写pom
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
写yaml ```yaml server: port: 8001
spring: application: name: cloud-provider-hystrix-payment
eureka: client: fetch-registry: true register-with-eureka: true service-url: defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
4. 主启动类
```java
@SpringBootApplication
@EnableEurekaClient
@EnableHystrix //开启
public class HystrixPaymentApplication8001 {
public static void main(String[] args) {
SpringApplication.run(HystrixPaymentApplication8001.class, args);
}
}
写业务类:service,节约时间不写接口
@Service public class PaymentService { //运行正常的方法 public String paymentInfo_OK(Integer id) { return "线程池:" + Thread.currentThread().getName() + " paymentInfo_OK,id:" + id + "\t" + "哈哈"; } //模拟复杂业务场景 public String paymentInfo_TimeOut(Integer id) { //睡眠模拟费时业务 int timeNumber = 5; try { TimeUnit.SECONDS.sleep(timeNumber); } catch (InterruptedException e) { e.printStackTrace(); } return "线程池:" + Thread.currentThread().getName() + " paymentInfo_TimeOut,id:" + id + "\t" + "!!!"; } }
controller
@RestController @Slf4j public class PaymentController { @Autowired private PaymentService paymentService; @Value("${server.port}") private String port; @GetMapping("/hystrix/ok/{id}") public String paymentInfo_OK(@PathVariable("id") Integer id) { String result = paymentService.paymentInfo_OK(id); log.info("****result:" + result); return result; } @GetMapping("/hystrix/timeout/{id}") public String paymentInfo_TimeOut(@PathVariable("id") Integer id) { String result = paymentService.paymentInfo_TimeOut(id); log.info("****result:" + result); return result; } }
- 测试:访问 /test/hystrix/timeout/1 时会等待5秒钟
2、 Jmester压力测试
使用2w个线程进超时方法,出现以下现象:
tomcat的默认工作现场被打满了,没有多余的先出来分解压力和处理,此时造成了拖慢了其他的用户线程。
3、 服务消费者模块搭建
- 创建模块 cloud-consumer-feign-hystrix-order80
- 写pom
```xml
org.springframework.cloud spring-cloud-starter-netflix-hystrix org.springframework.cloud spring-cloud-starter-netflix-eureka-client com.tao.springcloud cloud-api-commons ${project.version} org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-actuator org.springframework.boot spring-boot-starter-test test org.projectlombok lombok true
3. 写yaml
```yaml
server:
port: 80
spring:
application:
name: cloud-order-service
eureka:
client:
#表示是否将自己注册进eurekaserver
register-with-eureka: true
#是否从EurekaServer抓取主机
fetch-registry: true
service-url:
defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka
主启动类
@SpringBootApplication @EnableEurekaClient @EnableHystrix //开启 public class HystrixApplication80 { public static void main(String[] args) { SpringApplication.run(HystrixApplication80.class, args); } }
写业务类:使用Feign调用 ```java @Component @FeignClient(“CLOUD-PROVIDER-HYSTRIX-PAYMENT”) public interface PaymentRemoteHystrixService {
@RequestMapping(“/test/hystrix/ok/{id}”) public String testHystrixOk(@PathVariable(“id”) Integer id);
@RequestMapping(“/test/hystrix/timeout/{id}”) public String testHystrixTimeout(@PathVariable(“id”) Integer id) throws InterruptedException;
}
controller
```java
@RestController
public class OrderHystrixController {
@Autowired
private PaymentRemoteHystrixService paymentRemoteHystrixService;
@RequestMapping("/consumer/test/hystrix/ok/{id}")
public String testHystrixOk(@PathVariable("id") Integer id){
return paymentRemoteHystrixService.testHystrixOk(id);
}
@RequestMapping("/consumer/test/hystrix/timeout/{id}")
public String testHystrixTimeout(@PathVariable("id") Integer id) throws InterruptedException {
return paymentRemoteHystrixService.testHystrixTimeout(id);
}
}
4、 需要解决的问题
- provider服务 8001超时了,调用者80不能一直卡死等待,必须有服务降级
- provider服务 8001 宕机了,调用者80不能一直卡死等待,必须有服务降级
- provdier服务 8001 OK,调用者80自己出现了故障或有自我要求(自己的等待时间小于服务提供者)
四、服务降级
- 从整体网站请求负载考虑,当某个服务熔断或者关闭之后,服务将不再被调用,此时在客户端,我们可以准备一个返回策略 FallBackFactory ,返回一个默认的值(缺省值)。该方式会导致整体的服务下降,但是好歹能用,比直接挂掉强。
- 降级不一定代表服务不可用了,也有可能只是是暂时关闭服务,如最典型的就是双十一的天猫,这种情况下商品和订单还有支付这些服务的流量会特别大,于是就暂时把评论这种没那么重要的服务降级,因此把服务降级配置在客户端。
1、配置服务降级(客户端)
写yml
# 开启feign支持的hystrix feign: hystrix: enabled: true
配置服务降级处理 @HystrixCommand
@RestController @Slf4j public class OrderController { @Resource private PaymentHystrixService paymentHystrixService; @GetMapping("/hystrix/payment/ok/{id}") public String paymentInfo_OK(@PathVariable("id") Integer id) { String result = paymentHystrixService.paymentInfo_OK(id); log.info("***********" + result); return result; } @HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1500") }) @GetMapping("/hystrix/payment/timeout/{id}") public String paymentInfo_TimeOut(@PathVariable("id") Integer id) { String result = paymentHystrixService.paymentInfo_TimeOut(id); log.info("***********" + result); return result; } public String paymentInfo_TimeOutHandler(Integer id) { return "系统繁忙,请稍后再试,id:" + id + "\t" + "。。。"; } }
2、解决代码耦合度的问题
以上代码服务生产者和消费者都配置了Hystirx服务降级,代码耦合度过高,因此做以下变更:
修改order模块,这里开始,pay模块就不服务降级了,服务降级写在order模块即可。
2.1 配置全局的fallback方法
- @DefaultProperties:指定全局处理的fallback方法
@HystrixCommand:标记需要服务降级的方法
@RestController @Slf4j @DefaultProperties(defaultFallback = "paymentInfo_Global_FallbackMethod") //指定 public class OrderController { @Resource private PaymentHystrixService paymentHystrixService; @GetMapping("/hystrix/payment/ok/{id}") public String paymentInfo_OK(@PathVariable("id") Integer id) { String result = paymentHystrixService.paymentInfo_OK(id); log.info("***********" + result); return result; } //标记处理的方法 @HystrixCommand @GetMapping("/hystrix/payment/timeout/{id}") public String paymentInfo_TimeOut(@PathVariable("id") Integer id) { String result = paymentHystrixService.paymentInfo_TimeOut(id); log.info("***********" + result); return result; } public String paymentInfo_Global_FallbackMethod() { return "全局捕捉:系统繁忙,请稍后再试"; } }
2.2 开启feign方式接口的hystrix降级
创建 @FeignClient 接口的实现类
@Component public class PaymentFallbackService implements PaymentHystrixService { @Override public String paymentInfo_OK(@PathVariable("id") Integer id) { return "ok_系统繁忙,请稍后再试"; } @Override @HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler", commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000") }) public String paymentInfo_TimeOut(@PathVariable("id") Integer id) { return "timeou_系统繁忙,请稍后再试"; } public String paymentInfo_TimeOutHandler(Integer id) { return "线程池:" + Thread.currentThread().getName() + " 系统繁忙,请稍后再试,id:" + id + "\t" + "。。。"; } }
使用 @FeignClient 中的 fallback 声明实现类
@Component @FeignClient(name = "CLOUD-PAYMENT-HYSTRIX-SERVICE",fallback = PaymentFallbackService.class) public interface PaymentHystrixService { @GetMapping("/hystrix/ok/{id}") String paymentInfo_OK(@PathVariable("id") Integer id); @GetMapping("/hystrix/timeout/{id}") String paymentInfo_TimeOut(@PathVariable("id") Integer id); }
主启动类
@SpringBootApplication @EnableFeignClients @EnableHystrix public class HystrixOrderApplication80 { public static void main(String[] args) { SpringApplication.run(HystrixOrderApplication80.class, args); } }
五、服务熔断
1、熔断机制概述
- 熔断机制式应对雪崩效应的一种微服务链路保护机制。当扇出链路的某个微服务出错不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息。
- 当检测到该节点微服务调用响应正常后,自动恢复调用链路
- 在SpringCloud框架中,熔断机制通过Hystrix实现Hystrix会监控微服务间调用的状况。
- 如当失败的调用到一定阈值,缺省时5秒内20次调用失败,就会启动熔断机制,熔断机制的注解是
@HystrixCommand
。2、熔断的架构和整体流程
异常关闭,检测到正常后再恢复。
- 请求进来,首先查询缓存,如果缓存有,直接返回;如果缓存没有,则到2.
- 查看断路器是否开启,如果开启的,Hystrix直接将请求转发到降级返回,然后返回;如果断路器是关闭的,则到3。
- 判断线程池等资源是否已经满了,如果已经满了也会走降级方法,如果资源没有满,则到4。
- 判断我们使用的什么类型的Hystrix,决定调用构造方法还是run方法,然后处理请求
- 然后Hystrix将本次请求的结果信息汇报给断路器,断路器收到信息,判断是否符合开启或关闭断路器的条件。
如果本次请求处理失败,又会进入降级方法;如果处理成功,判断处理是否超时,如果超时了也进入降级方法。没有超时,则本次请求处理成功,将结果返回给controller。
3、配置服务熔断(服务端)
在 @HystrixCommand 启动断路器功能。
- 如果并发超过10个或者10个并发中失败了6个,就会开启断路器,熔断服务。
- 在时间窗口期10秒之内会尝试请求,如果请求成功就会关闭断路器,重新开启服务。
```java
//服务熔断
@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) {
} String serialNumber = IdUtil.simpleUUID();//hutool工具 return Thread.currentThread().getName() + “\t” + “调用成功,流水号:” + serialNumber; }throw new RuntimeException("****id 不能为负数");
public String paymentCircuitBreaker_fallback(Integer id) { return “id:” + id + “,不能为负数,请稍后再试,。。。”; }
2. 写controller,添加一个测试方法
```java
//服务熔断
@GetMapping("/circuit/{id}")
public String paymentCircuitBreaker(@PathVariable("id") Integer id){
String result = paymentService.paymentCircuitBreaker(id);
log.info("****result:"+result);
return result;
}
- 测试
首先先让服务熔断,在服务熔断状态下,服务不可用,无论怎么访问都会报错:
Hystrix在一定时间后,检测到服务恢复正常,恢复服务:
4、总结:熔断和降级的区别
- 降级不一定代表服务不可用了,也有可能只是是暂时关闭服务,如最典型的就是双十一的天猫,这种情况下商品和订单还有支付这些服务的流量会特别大,于是就暂时把评论这种没那么重要的服务降级,因此把服务降级配置在客户端。
- 熔断一定降级,代表服务不可用了,因此配置处理方法在服务端。
- 熔断会在检测到服务正常工作后,恢复服务可用状态,而降级不会自动恢复。
六、服务监控 Dashboard
HystrixDashboard是Hystrix提供的准实时调用监控,它记录了所有Hystrix发起的请求的执行信息,并以图形化的形式显示。
1、搭建HystrixDashboard
- 创建模块 cloud-consumer-hystrix-dashboard9001
写pom
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId> </dependency>
写yaml
server: port: 9001
主启动类
@SpringBootApplication @EnableHystrixDashboard //开启 public class HystrixDashboarMain9001 { public static void main(String[] args) { SpringApplication.run(HystrixDashboarMain9001.class, args); } }
2、配置被监控的服务
写pom
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
主启动类
@SpringBootApplication @EnableEurekaClient @EnableHystrix public class HystrixPaymentApplication8001 { public static void main(String[] args) { SpringApplication.run(HystrixPaymentApplication8001.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; } }
3、 测试
http://localhost:9001/hystrix 进入页面,然后输入 http://localhost:8001/hystrix.stream 进入监控页面
七、停更进维