SpringCloud中级(一) ——Hystrix豪猪哥断路器
目录
概述
分布式系统面临的问题:
功能:
- 服务降级
- 服务熔断
- 接近实时的监控
官网:
https://github.com/Netflix/Hystrix/wiki/How-To-Use
Hystrix官宣,停更进维。
github:https://github.com/Netflix/Hystrix
Hystrix重要概念
服务降级
服务器忙,请稍候再试,不让客户端等待并立刻返回一个友好提示,fallback
哪些情况会触发降级:
- 程序运行异常
- 超时
- 服务熔断触发服务降级
- 线程池/信号量打满也会导致服务降级
服务熔断
类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示。
就是保险丝,服务的降级->进而熔断->恢复调用链路。
服务限流
秒杀高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒钟N个,有序进行
Hystrix案例
构建项目
依赖:
<!--新增hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
配置文件:
server:
port: 8001
eureka:
client:
register-with-eureka: true #表识不向注册中心注册自己
fetch-registry: true #表示自己就是注册中心,职责是维护服务实例,并不需要去检索服务
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
spring:
application:
name: cloud-provider-hystrix-payment
启动类:
@SpringBootApplication
@EnableEurekaClient
public class PaymentHystrixMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.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 = 3;
try { TimeUnit.SECONDS.sleep(timeNumber); }catch (Exception 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;
}
}
测试:
http://localhost:8001/payment/hystrix/ok/31
//每次调用耗时3秒
http://localhost:8001/payment/hystrix/timeout/31
项目构建完毕。
高并发测试
开启Jmeter,来20000个并发压死8001,20000个请求都去访问paymentInfo_TimeOut服务。
再访问:
http://localhost:8001/payment/hystrix/ok/31
之前该接口是一访问就立即返回,现在是在转圈圈。
原因:tomcat的默认的工作线程数被打满了,没有多余的线程来分解压力和处理。
消费者加入
刚刚的压力测试,只是测了提供者,现在消费者也加入。
给提供者2W个并发,20000个请求都去访问paymentInfo_TimeOut服务。
消费端微服务再去访问正常的接口,要么转圈圈等待,要么消费端报超时错误
如何解决?
解决的要求:
- 超时导致服务器变慢(转圈)-》超时不再等待
- 出错(宕机或程序运行出错)-》出错要有兜底
解决: - 服务提供者超时了,服务消费者不能一直卡死等待,必须有服务降级
- 服务提供者down机了,服务消费者不能一直卡死等待,必须有服务降级
- 服务提供者正常,服务消费者自己出故障或有自我要求(自己的等待时间小于服务提供者),自己处理降级
服务降级
服务提供方服务降级
PaymentService
业务类修改:
//业务逻辑的执行时间超过指定的时间或者业务逻辑出现异常就会调用兜底方法
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = {
//3秒钟以内就是正常的业务逻辑,超过了3秒,就调用兜底方法
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000")
})
public String paymentInfo_TimeOut(Integer id){
//假设服务出现异常,那么就应该调用兜底的方法
//int timeNumber=1/0;
//假设业务逻辑的时间是5秒,超过指定的时间3秒,那么就应该调用兜底的方法,进行服务的降级
int timeNumber = 5;
try { TimeUnit.SECONDS.sleep(timeNumber); }catch (Exception e) {e.printStackTrace();}
return "线程池:"+Thread.currentThread().getName()+" paymentInfo_TimeOut,id: "+id+"\t"+"呜呜呜"+" 耗时(秒)"+timeNumber;
}
//兜底方法
public String paymentInfo_TimeOutHandler(Integer id){
return "线程池:"+Thread.currentThread().getName()+" 系统繁忙或出现异常, 请稍候再试 ,id: "+id+"\t"+"哭了哇呜";
}
启动类添加注解:@EnableCircuitBreaker
测试:启动Eureka,重启服务提供者。
访问:http://localhost:8001/payment/hystrix/timeout/31
出现:无论是测试超时的还是异常的都一样
服务消费方服务降级
先修改一下服务提供者的服务降级配置,假设5秒才会调用兜底方法,业务逻辑时间为3秒,所以服务提高者,是能够正常的调用方法提供服务的。
//业务逻辑的执行时间超过指定的时间或者业务逻辑出现异常就会调用兜底方法
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = {
//3秒钟以内就是正常的业务逻辑,超过了3秒,就调用兜底方法
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "5000")
})
public String paymentInfo_TimeOut(Integer id){
// int timeNumber=1/0;
int timeNumber = 3;
try { TimeUnit.SECONDS.sleep(timeNumber); }catch (Exception e) {e.printStackTrace();}
return "线程池:"+Thread.currentThread().getName()+" paymentInfo_TimeOut,id: "+id+"\t"+"呜呜呜"+" 耗时(秒)"+timeNumber;
}
服务消费者修改:
配置文件
feign:
hystrix:
enabled: true #如果处理自身的容错就开启。开启方式与生产端不一样。
启动类添加注解:@EnableHystrix
controller
:我们知道服务提供者执行业务逻辑本身就是要3秒,但是不同的消费者对超时的设置可能不一样,这里的消费者认为调用服务超过1.5秒,还没返回就调用兜底方法进行服务的降级
@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 i=10/0;
String result = paymentHystrixService.paymentInfo_TimeOut(id);
return result;
}
//兜底方法
public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id){
return "我是消费者80,对付支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,(┬_┬)";
}
测试:启动eureka,服务提供者,再启动当前消费者
http://localhost/consumer/payment/hystrix/timeout/31
问题
问题一:每个业务方法对应一个兜底的方法,代码膨胀。
解决:使用全局服务降级
1.加注解@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")
//全局的
2.加全局的兜底方法payment_Global_FallbackMethod
3.在需要服务降级的方法上加注解@HystrixCommand
,同时不指定使用哪个兜底方法,那么默认使用全局的兜底方法
@RestController
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod") //全局的
public class PaymentHystrixController {
@Autowired
private PaymentHystrixService paymentHystrixService;
@GetMapping("/consumer/payment/hystrix/ok/{id}")
String paymentInfo_OK(@PathVariable("id") Integer id){
String s = paymentHystrixService.paymentInfo_OK(id);
return s;
}
@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){
// int i=10/0;
String result = paymentHystrixService.paymentInfo_TimeOut(id);
return result;
}
//兜底方法
public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id){
return "我是消费者80,对付支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,(┬_┬)";
}
//下面是全局fallback方法
public String payment_Global_FallbackMethod(){
return "Global异常处理信息,请稍后再试,(┬_┬)";
}
}
问题二:和业务逻辑混一起,混乱,耦合度高,怎么解决?
使用通配服务降级
配置文件:
feign:
hystrix:
enabled: true #如果处理自身的容错就开启。开启方式与生产端不一样。
PaymentHystrixService 接口修改FeignClient注解:
@Component
//@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT")
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",fallback = PaymentFallbackService.class)
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);
}
添加PaymentFallbackService:
@Component
public class PaymentFallbackService implements PaymentHystrixService {
@Override
public String paymentInfo_OK(Integer id) {
return "-----PaymentFallbackService fall back-paymentInfo_OK , (┬_┬)";
}
@Override
public String paymentInfo_TimeOut(Integer id) {
return "-----PaymentFallbackService fall back-paymentInfo_TimeOut , (┬_┬)";
}
}
未来我们要面对的异常:
- 运行时异常
- 超时
- 宕机
测试:
启动Eureka,不启动服务提供者,模拟宕机,启动服务消费者。
访问:http://localhost/consumer/payment/hystrix/ok/31
注意:使用通配符降级,如果controller里某个特定的接口想使用特定的兜底方法,使用之前的全局服务降级或者一个接口指定一个特定的兜底方法就行,会覆盖掉通配符降级。也就是优先级:一个接口指定一个特定的兜底方法>全局服务降级>通配服务降级
服务熔断
断路器:类似家里的保险丝
熔断机制时应对雪崩效应的一种微服务链路保护机制。当链路的某个微服务出错不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点的微服务的带哦用,快速返回错误的响应信息。
当检测到该节点微服务调用响应正常后,恢复调用链路。
类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示。
就是保险丝,服务的降级->进而熔断->恢复调用链路。
实操
修改服务提供者:PaymentService
:
配置的熔断:表示服务降级使用兜底方法paymentCircuitBreaker_fallback;并且如果再10秒内,访问的10次请求失败率达到60%,将进行服务的熔断。过一段时间,如果访问的可以成功了,慢慢的恢复过来。
//服务熔断
@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 serialNumber = IdUtil.simpleUUID();
return Thread.currentThread().getName()+"\t"+"调用成功,流水号:"+serialNumber;
}
public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id){
return "id 不能负数,请稍候再试,(┬_┬)/~~ id: " +id;
}
controller:
//===服务熔断
@GetMapping("/payment/circuit/{id}")
public String paymentCircuitBreaker(@PathVariable("id") Integer id){
String result = paymentService.paymentCircuitBreaker(id);
log.info("*******result:"+result);
return result;
}
测试:
1.正确的访问接口:http://localhost:8001/payment/circuit/31
2.错误的访问接口:http://localhost:8001/payment/circuit/-1
id 不能负数,请稍候再试,(┬_┬)/~~ id: -1
3.快速的多次的错误的方式去访问:达到10秒错误率60%以上去触发它的熔断机制,看看什么效果
再正确的访问:发现已经不能正常的访问了
正确的访问,一会儿后:又能正常的访问!
熔断的工作流程
断路器开启或者关闭的条件:到达以下阀值,断路器将会开启
- 当满足一定阀值的时候(默认10秒内超过20个请求次数)
- 当失败率达到一定的时候(默认10秒内超过50%请求失败)
当开启的时候,所有请求都不会进行转发。一段时间之后(默认是5秒),这个时候断路器是半开状态,会让其中一个请求进行转发。如果成功,断路器会关闭,若失败,继续开启,再下一个5秒再试一下
断路器打开之后:
更多的配置
图形化监控
要监控的服务多了,看日志不太方便。
搭建图形化界面
新建子模块
依赖:
<!--新增hystrix dashboard-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
配置文件:
server:
port: 9001
启动类:@EnableHystrixDashboard
所有Provider微服务提供类(8001/8002/8003)都需要监控依赖配置:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
启动项目:访问http://localhost:9001/hystrix
本文地址:https://blog.csdn.net/weixin_42412601/article/details/107095652
上一篇: 考研难度最大的五大211学校:暨南大学上班,苏州大学第一
下一篇: 分布式事务之最终一致性