Spring-Cloud 学习笔记-(5)熔断器Hystrix
[TOC]java
一、前言
-
上个章节咱们作了什么?git
上个章节咱们使用了
Ribbon
实现了服务之间调用的负载均衡,具体能够分为三个步骤github- 引ribbon依赖
- 在启动类中的RestTemplate 加注解@LoadBalanced
- 把serviceId直接写在RestTemplate 请求的url中调用
而且咱们针对ribbon底层实现原理,走了一遍源码。spring
-
这个章节咱们会作什么?api
熔断器Hystrix服务器
二、Hystrix介绍
2.一、简介
Hystrix,英文翻译是豪猪,是一种保护机制,Netflix公司的一款组件。app
主页:https://github.com/Netflix/Hystrix/ 负载均衡
Hystix是Netflix开源的一个延迟和容错库,用于隔离访问远程服务、第三方库,防止出现级联失败。 ide
2.二、雪崩问题
上一章咱们服务的调用方(order-service)调用了服务的提供方(user-serivce)查询用户的方法,咱们能够称order-service依赖于user-service,一旦咱们user-service不可用,也会致使了order-service也不可用,相似这种级联的失败,咱们能够称做雪崩。微服务
2.2.一、雪崩效应产生缘由
- **服务的级联失败:**就是刚刚说的A服务依赖B服务,B服务失败了,却是A服务也挂了,若是还有服务依赖A服务,这样它也会挂了,就这样一直延伸下去致使整个项目的不可用。
- **服务链接数被耗尽:**失败的服务占用了链接数,却是正常的服务依旧访问不了。
描述的详细一点能够这么理解(图片来自于:https://github.com/Netflix/Hystrix/wiki)
2.2.二、Hystrix如何解决雪崩问题
-
服务的熔断和降级:
熔断:当用户的请求调用一个服务,这个服务挂了,阻塞了,咱们设置一个超时时常,若是超过这个时间,咱们会快速的返回一个失败的友好提示给客户端。
降级:之前访问一个功能,咱们能够提供全部的服务,可是如今咱们有个地方有点问题,咱们只能给你提供核心服务,不重要的暂时就访问不了了。
-
线程的隔离:
好比咱们Tomcat线程有500个,一个用户的请求来了调用5个服务,咱们分一个线程给他,让这个线程去调用服务,调用成功返回结果,也就是说之前全部的服务均可以用这500个线程,这样500个线程用完了,这个项目就挂了,如今的作法是什么呢,咱们有针对性的给这些服务分配线程,好比一个服务分配100个线程,这样就算有一个服务挂了, 就算服务I不可用,那只会阻塞这个100个线程,其他的400个线程仍是正常,依旧能够调用其余正常的服务,咱们把这种把不一样的服务请求,用不一样的线程池去隔离,就算你资源耗尽,仅仅会消耗当前线程池的链接数叫作线程的隔离。
官网对线程的隔离图解
当服务繁忙时,若是服务出现异常,不是粗暴的直接报错,而是返回一个友好的提示,虽然拒绝了用户的访问,可是会返回一个结果。
这就比如去买鱼,日常超市买鱼会额外赠送杀鱼的服务。等到逢年过节,超时繁忙时,可能就不提供杀鱼服务了,这就是服务的降级。
系统特别繁忙时,一些次要服务暂时中断,优先保证主要服务的畅通,一切资源优先让给主要服务来使用,在双11、618时,京东天猫都会采用这样的策略。
三、服务的降级和线程隔离
3.一、代码:
在服务的调用方(order-service)
3.1.一、引依赖:
<!-- hystrix --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
3.1.二、加注解
@SpringBootApplication @EnableCircuitBreaker//开启服务的熔断 public class OrderApplication { public static void main(String[] args) { SpringApplication.run(OrderApplication.class); } /** * 把RestTemplate注入到Spring容器中 */ @Bean @LoadBalanced //让RestTemplate内置一个负载均衡器 public RestTemplate restTemplate(){ return new RestTemplate(); } }
其实咱们做为Eureka的服务端须要加注解@EnableEurekaServer
,一样咱们做为eureka的客户端也须要加一个注解
@EnableDiscoveryClient
,只是咱们Eureka比较智能,若是你有spring-cloud-starter-netflix-eureka-client
这个依赖,eureka就会默认你是一个eureka客户端,因此@EnableDiscoveryClient
能够不用加。因此一个正常的springcloud微服务,基本上都会有三个注解,@SpringBootApplication
、@EnableDiscoveryClient
、@EnableCircuitBreaker
,因此springcloud很人性化的把这三个注解合成一个注解SpringCloudApplication
,因此你们嫌麻烦能够直接加一个SpringCloudApplication
注解就能够了。
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootApplication//启动类 @EnableDiscoveryClient//eureka客户端 @EnableCircuitBreaker//熔断 public @interface SpringCloudApplication { }
3.1.二、修改代码:
上面说过,若是服务失败,咱们快速返回一个失败信息,因此如今咱们要作的是写一个快速失败的处理。
在OrderController中
//OrderController类 @RequestMapping("{user_id}") //开启服务的线程合理和降级处理,并指定失败后调用的方法↓ @HystrixCommand(fallbackMethod = "findUserByIdFallbace") public BaseData findUserById(@PathVariable("user_id")int id){ Order order = orderService.findById(id); return new BaseData(order); } /** findUserById失败后调用的方法 * 方法参数和返回值要和上面的彻底同样 */ public BaseData findUserByIdFallbace(int id){ return new BaseData("服务器拥挤,请稍后再试!",null); }
3.1.四、模拟服务调用异常
UserServiceImpl中
//UserServiceImpl类 /** * 根据id查询用户基本信息 * @param id 用户id * @return 用户对象 */ @Override public User findById(int id) { //模拟服务器延迟 try { //休眠两秒 TimeUnit.SECONDS.sleep(2L); } catch (InterruptedException e) { e.printStackTrace(); } User user = userMap.get(id); user.setPort(port); return user; }
3.1.五、测试
启动服务,访问 http://localhost:8781/api/v1/order/2
3.二、升级版
刚刚咱们是针对某一个类写了一个降级方法,可是若是Controller中有不少方法咱们就要写不少的降级方法。因此咱们能够针对一个类全部方法降级
3.一、代码
3.1.一、注解修改
@RestController @RequestMapping("api/v1/order") @DefaultProperties(defaultFallback = "defaultFallback") //为整个类开启服务的熔断 public class OrderController { //.... @RequestMapping("{user_id}") //开启服务的线程合理和降级处理 @HystrixCommand public BaseData findUserById(@PathVariable("user_id")int id){ Order order = orderService.findById(id); return new BaseData(order); }
3.1.二、降级方法修改:
/** * 方法参数为空 */ public BaseData defaultFallback(){ return new BaseData("服务器拥挤,请稍后再试!",null); }
3.1.三、测试
3.三、配置修改
3.3.一、单一方法Hystrix配置
打开控制台(F12),这里咱们看的出来,虽然user-service咱们设置了睡眠时间是2秒,可是每次一秒就返回结果了,说明Hytrix默认的超时时长是1秒,可是因为业务逻辑须要,好比发送邮件、银行转帐、等咱们超时时长均可以稍微设置长一点,一些简单查询能够超时时长能够设置稍微短一点。
3.3.1.一、注解修改
//OrderController类 @RequestMapping("{user_id}") /** *commandProperties,中咱们能够配置一些属性,能够配置多个。 *可是这些配置的nama 和value 是什么呢...咱们看源码,这些配置都在HystrixCommandProperties类中 */ @HystrixCommand(commandProperties = { @HystrixProperty(name = "",value = "") }) public BaseData findUserById(@PathVariable("user_id")int id){ Order order = orderService.findById(id); return new BaseData(order); }
//HystrixCommandProperties类 //执行 超时时长 单位毫秒 咱们ctrl+f 搜索一下default_executionTimeoutInMilliseconds,看看这个配置的key是什么... private static final Integer default_executionTimeoutInMilliseconds = 1000;
//找到配置 key:execution.isolation.thread.timeoutInMilliseconds this.executionTimeoutInMilliseconds = getProperty(propertyPrefix, key, "execution.isolation.thread.timeoutInMilliseconds", builder.getExecutionIsolationThreadTimeoutInMilliseconds(), default_executionTimeoutInMilliseconds);
因此咱们要修改某一个方法熔断配置,能够在@HystrixProperty中配置对于的name和value,好比咱们配置超时时长为3秒,咱们就能够
//OrderController类 @RequestMapping("{user_id}") //开启服务的线程合理和降级处理,并指定失败后调用的方法 @HystrixCommand(commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000") }) public BaseData findUserById(@PathVariable("user_id")int id){ Order order = orderService.findById(id); return new BaseData(order); }
3.3.1.二、测试
返回成功
3.3.二、全局配置
修改配置文件application.yml文件
#hystrix超时时长配置 hystrix: command: default: execution.isolation.thread.timeoutInMilliseconds: 3000
咱们删除以前的方法上的超时配置,重启一下order-service测试一下
依旧没有问题。
四、服务的熔断
4.一、熔断的原理
-
一个请求过来,若是是关闭状态,那请求继续。
-
若是请求失败超过必定的
阈值
(默认最近的20次请求有50%以上的请求失败)则熔断器打开。 -
后续请求发现熔断器是开启状态,将直接返回错误信息,不会等待。
-
从熔断器打开时候开始计时,熔断器会通过一个
休眠时间窗
(默认5秒),超过5秒后熔断器会进入半开状态。 -
半开状态的熔断器会放必定量的请求经过进行尝试,若是依旧超时,熔断器继续进入关闭状态,而后在经历休眠,如此反复,直到半开状态的熔断器放过去的请求成功了,熔断器会继续进入关闭状态。
这些默认值也是在
//HystrixCommandProperties类 //打开熔断器的最小请求次数 private static final Integer default_circuitBreakerRequestVolumeThreshold = 20; //休眠时间窗 private static final Integer default_circuitBreakerSleepWindowInMilliseconds = 5000; //设置打开熔断并启动回退逻辑的错误比率 private static final Integer default_circuitBreakerErrorThresholdPercentage = 50;
4.二、测试
修改代码便于测试
- 把user-service中的睡眠时间删除
- 修改OrderController代码
//OrderController类 @RequestMapping("{user_id}") //开启服务的线程合理和降级处理 @HystrixCommand( commandProperties = { @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"), @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "5000"), @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60") } ) public BaseData findUserById(@PathVariable("user_id")int id){ //手动控制请求成功失败 技术 if(id%2==0){ throw new RuntimeException(""); } Order order = orderService.findById(id); return new BaseData(order); }
-
测试
访问:http://localhost:8781/api/v1/order/1 成功
访问:http://localhost:8781/api/v1/order/2 失败
若是咱们一直访问http://localhost:8781/api/v1/order/2 一直失败
根据上面咱们的配置在最近的10次请求中,若是失败超过60%,这个时候熔断器就会开启,就算咱们访问http://localhost:8781/api/v1/order/1 成功的请求也会当即返回失败信息,这个时候会经历休眠时间窗5秒,超过5秒熔断器进入半开状态,咱们访问http://localhost:8781/api/v1/order/1,成功。