文章目录:
1.分布式系统面临的问题:
在微服务架构里,一个系统会有很多的服务。如下图:
上图请求需要调用 A,H,I, P 四个服务,如果一切顺利没有什么问题,倘若其中某一个服务,机房着火,不幸挂掉了会怎么样?是不是每次调用这个服务的时候,都会卡住几秒钟,然后抛出—个超时异常。如果这是在高并发的场景下,大量请求涌过来,导致请求都卡在这里,没有其他线程去处理正常的服务,就会导致的恐怖的服务雪崩。
1.1:服务雪崩:
多个微服务之前调用的时候,假设服务A调用服务B和服务C,服务B和服务C又调用其他的微服务,这就是所谓的扇出,如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,这就是所谓的服务雪崩。这个情况我们如何处理呢?下面我们掌声有请我们的一号男嘉宾–Hystrix 豪猪先生!
2. Hystrix是什么?
Hystrix 是一个处理分布式系统的延迟和容错的开源库,在分布式系统里,很多依赖会不可避免的调用失败,比如超时,异常等,Hystrix 能够保证在一个依赖出现问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。
“断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似于家里的保险丝),向调用方返回一个符合预期的,可处理备选响应(FallBack),而不是长时间等待或抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会长时间不必要的占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。
3.Hystrix能干什么?
- 服务熔断
- 服务降级
- 接近实时的监控
- 限流、隔离等
一号男嘉宾真是多才多艺,一表人才呀,活生生的高富帅呀,这不赶紧爆灯?
对不起,Hystrix 官宣停更!
臧克家:“有的人活着,它已经死了”
Eureka,Hystrix :“滚…”
贴心不,感动不,我就知道你们看不懂英文,我也看不懂!它停更以后,推荐使用Resilience4j,但是国内还是推荐使用Sentinel,后续会更新,大家多多关注。
4.Hystrix核心概念
4.1服务降级:
“服务器忙,请稍后再试!”,不让客户端等待并立刻返回一个友好提示(fallback)
哪些情况会发出降级?
- 程序运行异常
- 超时
- 服务熔断触发服务降级
- 线程池 / 信号量打满也会导致服务降级
4.2服务熔断:
类似家里的保险丝,达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示。
4.3服务限流:
秒杀高并发等操作时,严禁一窝蜂的过来拥挤,大家排队,一秒N个,有序进行。
5.Hystrix实现服务降级
5.1 服务端实现服务降级
①:在父工程下,新建Module : cloud-provider-hystrix-payment8001
②:引入相关依赖:
<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>
<!--引入自己定义的api通用包,可以使用Payment支付Entity-->
<dependency>
<groupId>com.mk</groupId>
<artifactId>cloud-api-common</artifactId>
<version>${project.version}</version>
</dependency>
<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>
③: application.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
④:Service层
@Service
public class PaymentService {
public String paymentInfo_ok(Integer id){
return "线程池:"+Thread.currentThread().getName()+"paymentInfo_ok,id:"+id+"\t"+"访问顺利,O(∩_∩)O哈哈~";
}
public String paymentInfo_timeOut(Integer id){
try { // 超时异常
TimeUnit.MILLISECONDS.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "线程池:"+Thread.currentThread().getName()+"paymentInfo_timeOut,id:"+id+"\t"+"访问耗时3秒";
}
}
⑤:控制层:
@RestController
public class PaymentController {
@Autowired
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);
return result;
}
@GetMapping("/payment/hystrix/timeout/{id}")
public String paymentInfo_timeOut(@PathVariable("id") Integer id)
{
String result = paymentService.paymentInfo_timeOut(id);
return result;
}
}
⑥:主启动类
@SpringBootApplication
@EnableEurekaClient
public class PaymentHystrixMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.class, args);
}
}
启动cloud-eureka-server7001,cloud-provider-hystrix-payment8001访问测试:
可以发现ok的方法访问的话是立刻加载完成,超时那个方法要等待3秒钟,现在貌似没什么大问题?!
Jemete压测:
两万的并发量再次查看结果,发现ok那个方法,也被拖垮了,开始了爱的魔力转圈圈…好家伙,终于找到理由换电脑了
自己玩都那么费劲了,如果80再调用这个服务,嘶~
如何解决尼?为了大家能直观的看到,上面写了那么多,如感觉耽误你时间了,请见谅!
豪猪哥,醒一醒,醒醒,该你表演了!
对8001做如下修改:
启动类加@EnableCircuitBreaker
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker //开启hystrix服务熔断降级
public class PaymentHystrixMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.class, args);
}
}
PaymentService 修改后如下:
@Service
public class PaymentService {
public String paymentInfo_ok(Integer id){
return "线程池:"+Thread.currentThread().getName()+"paymentInfo_ok,id:"+id+"\t"+"访问顺利,O(∩_∩)O哈哈~";
}
@HystrixCommand(fallbackMethod = "paymentInfo_timeOutHandler",commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "2000")
})
public String paymentInfo_timeOut(Integer id){
try { // 超时异常
TimeUnit.MILLISECONDS.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "线程池:"+Thread.currentThread().getName()+"paymentInfo_timeOut,id:"+id+"\t"+"访问耗时3秒";
}
//出现异常,超时 则进入这个方法
public String paymentInfo_timeOutHandler(Integer id){
return "线程池:"+Thread.currentThread().getName()+"id:"+id+"\t"+"当前服务器繁忙,请稍后再试!o(╥﹏╥)o";
}
}
@HystrixCommand(fallbackMethod = “paymentInfo_timeOutHandler”,commandProperties = {
@HystrixProperty(name = “execution.isolation.thread.timeoutInMilliseconds”,value = “2000”)
})
设置自身调用超时时间的峰值,峰值内可以正常运行,超过了或有异常就进入fallbackMethod ,做服务降级
启动测试:
峰值两秒,程序延迟3秒,测试结果如下
5.2 消费端实现服务降级
①:在父工程下,新建Module : cloud-consumer-feign-hystrix-order80
②:引入相关依赖:
<dependencies>
<!--hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</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.mk</groupId>
<artifactId>cloud-api-common</artifactId>
<version>${project.version}</version>
</dependency>
<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>
③:application.yml 配置文件
server:
port: 80
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
④: 主启动类
@SpringBootApplication
@EnableFeignClients //开启feign进行远程调用
@EnableHystrix //开启hystrix服务熔断和降级
public class OrderHystrixMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderHystrixMain80.class, args);
}
}
⑤:Service
@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT")
public interface PaymentHystrixService {
@GetMapping("/payment/hystrix/ok/{id}")
String paymentInfo_ok(@PathVariable("id") Integer id);
@GetMapping("/payment/hystrix/timeout/{id}")
String paymentInfo_timeOut(@PathVariable("id") Integer id);
}
⑥:控制器
@RestController
public class OrderHystrixController {
@Autowired
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")
})
public String paymentInfo_timeOut(@PathVariable("id") Integer id) {
//int age = 10 / 0;
String result = paymentHystrixService.paymentInfo_timeOut(id);
return result;
}
public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id) {
return "我是消费者80,对方支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,o(╥﹏╥)o";
}
}
启动测试:
80等待峰值为1.5秒,程序要运行3秒,结果如下
5.3 全局服务降级
通过前面的示例可以发现,每一个业务方法,都要对应一个处理方法,这样的代码显得很臃肿,是不是可以设置全局的处理方法,特殊的再自己定制自己的特殊化处理比较好。
修改PaymentService
@Service
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")
public class PaymentService {
public String paymentInfo_ok(Integer id){
return "线程池:"+Thread.currentThread().getName()+"paymentInfo_ok,id:"+id+"\t"+"访问顺利,O(∩_∩)O哈哈~";
}
/*@HystrixCommand(fallbackMethod = "paymentInfo_timeOutHandler",commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "2000")
})*/
@HystrixCommand
public String paymentInfo_timeOut(Integer id){
/*try { // 超时异常
TimeUnit.MILLISECONDS.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
int i =10/0;
return "线程池:"+Thread.currentThread().getName()+"paymentInfo_timeOut,id:"+id+"\t"+"访问耗时3秒";
}
//出现异常,超时 则进入这个方法
public String paymentInfo_timeOutHandler(Integer id){
return "线程池:"+Thread.currentThread().getName()+"id:"+id+"\t"+"当前服务器繁忙,请稍后再试!o(╥﹏╥)o";
}
public String payment_Global_FallbackMethod() {
return "全局设置,服务器繁忙";
}
}
启动测试如下:
现在还有个问题就是服务降级代码和业务逻辑代码写在一起,看着是不是很难受,接下来实现解耦
为PaymentHystrixService增加实现类:
@Component
public class PaymentFallbackService implements PaymentHystrixService {
@Override
public String paymentInfo_ok(Integer id) {
return "服务器出现异常,paymentInfo_ok请稍后再试";
}
@Override
public String paymentInfo_timeOut(Integer id) {
return "服务器出现异常,paymentInfo_timeOut请稍后再试";
}
}
yml增加配置
server:
port: 80
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
feign:
hystrix:
enabled: true
PaymentHystrixService修改如下
启动测试:
6.Hystrix实现服务熔断
6.1:熔断机制概述
熔断机制是应对雪崩效应的一种微服务链路保护机制。 当扇出链路的某个微服务不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回”错误”的响应信息。当检测到该节点微服务响应正常后恢复调用链路,在SpringCloud框架机制通过Hystrix实现,Hystrix会监控微服务见调用的状况,当失败的调用到一个阈值,缺省是5秒内20次调用失败就会启动熔断机制,熔断机制的注解是@HystrixCommand
6.2:熔断测试案例
修改cloud-provider-hystrix-payment8001
①: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") Long id){
if(id < 0){
throw new RuntimeException("************id,不能为负数");
}
String uuid = IdUtil.simpleUUID();
return Thread.currentThread().getName()+"\t" + "调用成功,流水号===" + uuid;
}
public String paymentCircuitBreaker_fallback(@PathVariable("id") Long id){
return "id不能为负数,请稍后重试!o(╥﹏╥)o id:"+id;
}
②:控制器新增
@GetMapping("/payment/circuit/{id}")
public String payemntCircuitBreaker(@PathVariable("id") Long id){
String result = paymentService.paymentCircuitBreaker(id);
return result;
}
启动测试:
进而实现:服务的降级-》熔断-》恢复调用链路
断路器的三个重要参数:
1.circuitBreaker.sleepWindowInMilliseconds 时间快照窗: 断路器确定是否打开需要统计一些请求和错误数据,而统计的时间范围就是快照时间窗,默认为最近的10秒
2.circuitBreaker.requestVolumeThreshold 请求总阀值: 在快照时间窗内,必须满足请求总阀值数才有资格熔断。默认为20,也就是说在10秒内,如果该Hystrix 命令的调用不足20次,即使请求都超时或其他原因失败,断路器都不会打开。
3.circuitBreaker.errorThresholdPercentage 错误百分比阀值: 当请求总数在快照时间窗内超过了阀值,比如发生了30次调用,这30次调用有15次超时异常,也就是超过了50%的错误百分比,在默认设定50%阀值的情况下,断路器会打开。
Hystrix 整个工作流程图如下:
7.hystix图形化监控
7.1 概述
Hystix提供了准实时的调用监控,Hystix会持续的记录所有通过Hystix发起的请求的执行信息,并以统计报表和图形的形式展示给用户,包括每秒执行多少请求,多少成功失败等。
7.2 案例
①在父工程下,新建Module : cloud-consumer-hystrix-dashboard9001
②:引入相关依赖:
<dependencies>
<!--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-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>
③:application.yml
server:
port: 9001
④:主启动类
@SpringBootApplication
@EnableHystrixDashboard
public class HystrixDashboardMain9001 {
public static void main(String[] args) {
SpringApplication.run(HystrixDashboardMain9001.class,args);
}
}
启动访问:http://localhost:9001/hystrix
用到了上面的8001作为测试,完活
好家伙,上来就出错!
修改8001的主启动类
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker //开启hystrix服务熔断降级
public class PaymentHystrixMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.class, args);
}
/**
* 此配置是为了服务监控而配置,与服务容错本身无关
*/
@Bean
public ServletRegistrationBean getServlet() {
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/actuator/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
}
再次请求访问
写作不易,加个关注呗
求关注、求点赞!