springcloud-Hystrix断路器02(六)
服务降级
- 主要用到@HystrixCommand注解.
- 先从cloud-provider-hystrix-payment8001自身找问题
- 设置自身调用超时时间的峰值, 超过了需要有兜底的方法处理, 作服务降级fallback.
- 修改8001部分代码
- 主启动类激活: @EnableCircuitBreaker
@EnableCircuitBreaker @EnableEurekaClient @SpringBootApplication public class PaymentHystrixMain8001 { public static void main(String[] args) { SpringApplication.run(PaymentHystrixMain8001.class, args); } }
- service业务类
- 一旦调用服务方法失败并抛出了错误信息后, 会自动调用@HystrixCommand标注好的fallback方法
@Service public class PaymentServiceImpl implements PaymentService { @Override public String paymentInfo_OK(Integer id) { return "线程池: " + Thread.currentThread().getName() + " paymentInfo_OK, id: " + id; } //fallbackMethod属性中填写兜底方法 @HystrixCommand(fallbackMethod = "paymentInfo_TimeoutHandler", commandProperties = { //3秒内完成即为正常逻辑 @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000") }) @Override public String paymentInfo_Timeout(Integer id) { //这里测试, 超时会调用服务降级, 出错也会 int timeNumber = 1; int age = 10 / 0; try{ TimeUnit.SECONDS.sleep(timeNumber); } catch (InterruptedException e) { e.printStackTrace(); } return "线程池: " + Thread.currentThread().getName() + " paymentInfo_Timeout, id: " + id + ", 耗时: " + timeNumber; } public String paymentInfo_TimeoutHandler(Integer id) { return "线程池: " + Thread.currentThread().getName() + " 系统繁忙, 请稍后再试, id: " + id + "/(ㄒoㄒ)/~~"; } }
- 一旦调用服务方法失败并抛出了错误信息后, 会自动调用@HystrixCommand标注好的fallback方法
- 主启动类激活: @EnableCircuitBreaker
- 在消费者cloud-consumer-feign-hystrix-order80这里作服务降级.
- 其实一般服务降级都是在客户端做的, 这样可以更好的保护自己.
- yml中要开启Hystrix
feign: hystrix: enabled: true
- 主启动类: @EnableHystrix
@EnableHystrix @SpringBootApplication @EnableFeignClients public class OrderHystrixMain80 { public static void main(String[] args) { SpringApplication.run(OrderHystrixMain80.class,args); } }
- 业务类
- controller
@RestController @Slf4j 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; } @HystrixCommand(fallbackMethod = "paymentTimeoutFallbackMethod", commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500") }) @GetMapping("/consumer/payment/hystrix/timeout/{id}") 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秒后重试 /(ㄒoㄒ)/~~"; } }
- controller
- 解决膨胀与耦合
- 目前出现的问题
- 兜底的代码跟业务逻辑混到一起了, 代码耦合度低.
- 每个业务方法对应一个兜底的方法, 代码太膨胀
- 解决代码膨胀
- 用到注解@DefaultProperties
- 在具体的方法中, 如果只标有@HystrixCommand, 则使用defaultFallback
- 但若在@HystrixCommand中指明了fallbackMethod, 则使用自己指明的.
- 这样通用的和独享的各自分开, 避免了代码膨胀, 合理减少了代码量.
@RestController @Slf4j @DefaultProperties(defaultFallback = "payment_Global_FallbackMethod", commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500") }) 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; } @HystrixCommand @GetMapping("/consumer/payment/hystrix/timeout/{id}") public String paymentInfo_Timeout(@PathVariable("id") Integer id) { String result = paymentHystrixService.paymentInfo_Timeout(id); return result; } public String payment_Global_FallbackMethod() { //下面是全局fallback方法 return "Global异常处理信息,请稍后再试,(┬_┬)"; } }
- 解决代码耦合
- 只需要为Feign客户端定义的接口添加一个服务降级处理的实现类即可实现解耦合
- 本例在客户端80完成服务降级处理.
- 首先写一个类继承PaymentHystrixService, 在其中填写各个功能的降级处理方法.
@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 paymentInfo_Timeout"; } }
- 然后在PaymentHystrixService的@FeignClient的fallback属性中填上该实现类
@Component @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); }
- 这样就实现了解耦合,
- 注: 我们面对的异常通常有
- 运行时异常
- 超时
- 宕机
服务熔断
- 熔断机制概述
- 熔断机制是应对雪崩效应的一种微服务链路保护机制. 当扇出链路的某个微服务出错不可用或响应时间太长时, 会进行服务的降级, 进而熔断该节点微服务的调用, 快速返回错误的响应信息.
- 当检测到该节点微服务调用响应正常后, 会恢复调用链路.
- Hystrix实现熔断机制
- Hystrix会监控微服务间调用的状况, 当失败的调用到一定阙值, 缺省是5秒内20次调用失败, 就会启动熔断机制.
- 熔断机制的注解也是@HystrixCommand
- 实操
- 修改支付微服务: cloud-provider-hystrix-payment8001
- service方法
//服务熔断 //服务熔断 @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(); // UUID.randomUUID().toString() return Thread.currentThread().getName()+"\t"+"调用成功,流水号:"+serialNumber; } public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id){ return "id 不能负数,请稍候再试,(┬_┬)/~~ id: " +id; }
- 在其对应的controller方法中PaymentController添加
//---服务熔断 @GetMapping("/payment/circuit/{id}") public String paymentCircuitBreaker(@PathVariable("id") Integer id) { String result = paymentService.paymentCircuitBreaker(id); log.info("****result" + result); return result; }
- service方法
- 测试
- 正确的: http://localhost:8001/payment/circuit/11
- 错误的: http://localhost:8001/payment/circuit/-11
- 我们多次访问错误的, 之后访问正确的, 发现刚开始也不能显示正确页面, 再访问几次正确的后, 正确率高了, 才慢慢可以显示正确页面.
- 这就是 服务的熔断 -> 服务降级 -> 恢复调用链路
- 服务熔断总结
- 熔断类型
- 熔断打开: 请求不再进行调用当前服务, 内部设置时钟一般为MTTR(平均故障处理时间), 当打开时长达到所设时钟则进入熔断状态
- 熔断关闭
- 熔断半开: 部分请求根据规则调用当前服务, 如果请求成功且符合规则则认为当前服务恢复正常, 关闭熔断.
- 断路器在什么情况下开始起作用
- 快照时间窗口: 断路器确定是否打开需要统计一些请求和错误数据, 而统计的时间范围就是快照时间窗, 默认10秒
- 请求总数阈值: 在快照时间窗内,, 必须满足请求总数阈值才有资格熔断. 默认为20
- 意味着在10秒内, 如果该hystrix命令的调用次数不足20次, 即使所有的请求都超时或其他原因失败, 断路器也不会打开.
- 错误百分比阈值: 当请求总数在快照时间窗内超过了阈值, 比如发生30次调用, 15次失败, 也就是超过了50%的错误百分比, 在默认设定50%的情况下, 这时断路器就会打开了.
- 服务熔断步骤
- 当满足一定阀值的时候(默认10秒内超过20个请求次数)
- 当失败率达到一定的时候(默认10秒内超过50%请求失败)
- 到达以上阀值,断路器将会开启
- 当开启的时候,所有请求都不会进行转发
- 一段时间之后(默认是5秒), 这个时候断路器是半开状态, 会让其中一个请求进行转发. 如果成功, 断路器会关闭, 若失败, 继续开启. 重复4和5
- 在断路器打开之后
- 再有请求调用的时候, 将不会调用主逻辑, 而是直接调用降级fallback, 通过断路器, 实现了自动地发现错误并将降级逻辑切换为主逻辑, 减少响应延迟的效果.
- 原来的主逻辑如何恢复?
- 当断路器打开, 对主逻辑熔断后, hystrix会启动一个休眠时间窗, 在这个时间窗内, 降级逻辑临时成为主逻辑.
- 当休眠时间窗到期, 断路器将进入半开状态, 释放一次请求到原来的主逻辑上, 如果此次请求正常返回, 则断路器将闭合, 主逻辑恢复. 但如果该请求依然有问题, 断路器继续进入打开状态, 休眠时间窗口重新计时.
Hystrix工作流程
- 官方架构图
- 创建HyustrixCommand对象 - 用在依赖的服务返回单个操作结果的时候
- 命令执行, 其中HystrixCommand实现下面两种执行方式.
- execute(): 同步执行, 从依赖的服务返回一个单一的结果对象, 或是发生错误的时候抛出异常.
- queue(): 异步执行, 直接返回一个Future对象, 其中包含了服务执行结束时要返回的单一结果对象.
- 若当前命令的请求缓存功能是被启用的, 并且该命令缓存命中, 那么缓存结果会立即以Observable对象的形式返回.
- 检查断路器是否为打开状态, 如果断路器是打开的, 那么Hystrix不会执行, 而是转到fallback处理逻辑(第8步), 如果断路器是关闭的, 则继续执行命令.
- 线程池/请求队列/信号量是否占满, 如果命令依赖服务的专有线程池和请求队列, 或信号量(不用线程池的时候)已被占满, 那么Hystrix也不会继续执行, 而是转到fallback处理逻辑(第8步).
- Hystrix会根据我们编写的方法来决定采取什么样的方式去请求依赖服务,
- HystrixCommand.run(): 返回单一结果, 或抛出异常.
- Hystrix会将"成功", "失败", "拒绝", "超时"等信息报告给断路器, 而断路器会维护一组计数器来统计这些数据, 断路器会使用这些统计数据决定是否要打开断路器, 来对某个依赖服务的请求进行"熔断/短路".
- 当命令执行失败的时候, Hystrix会进入fallback尝试回退处理, 我们通常也称该操作为"服务降级", 而能够引起服务降级处理的情况有3种
- 当命令处于"熔断/短路"状态, 断路器是打开的时候.
- 当前命令的线程池/请求队列/信号量被占满的时候.
- HystrixCommand.run()抛出异常的时候.
- 当Hystrix命令执行成功后, 它会将处理结果直接返回或以Observable的形式返回.
- 注意: 如果我们没有为命令实现降级逻辑或在降级处理逻辑中抛出了异常, Hystrix依然会返回一个Observable对象, 但不会发射任何结果数据, 而是通过onError方法通知命令立即中断请求, 并通过onError()方法将引起命令失败的异常发送给调用者.
Hystrix图形化Dashboard
- 搭建仪表盘9001
- 新建cloud-consumer-hystrix-dashboard9001 Module
- pom文件
<dependencies> <!--新增hystrix dashboard--> <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
- 主启动类: 新注解@EnableHystrixDashboard
@EnableHystrixDashboard @SpringBootApplication public class HystrixDashboard9001 { public static void main(String[] args) { SpringApplication.run(HystrixDashboard9001.class, args); } }
- 注意: 所有Provider微服务提供类(8001/8002/...)都需要监控配置依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
- 启动该微服务, 并访问: http://localhost:9001/hystrix, 如果出现下图页面, 则表示环境搭建成功
- 图形监控演示
- 新版本的Hystrix需要在主启动类 PaymentHystrixMain8001 中指定监控路径
/** * 此配置是为了服务监控而配置, 与服务容错本身无关 * ServerletReegistrationBean是因为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; } 不指定会报错: Unable to connect to Command Metric Stream
- 监控测试
- 填写监控地址
- 图形说明
- 填写监控地址