在微服务架构中,因为某个服务的不可用致使一系列的服务崩溃,被称之为雪崩效应。因此防护服务的雪崩效应是必不可少的,在Spring Cloud中防雪崩的利器就是Hystrix,Spring Cloud Hystri是基于Netflix Hystrix实现的。Hystrix的目标在于经过控制那些访问远程系统、服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。Hystrix 具有服务降级、服务容错、服务熔断、线程和信号隔离、请求缓存、请求合并以及服务监控等强大功能。java
Hystrix中的资源隔离:web
在Hystrix中, 主要经过线程池来实现资源隔离. 一般在使用的时候咱们会根据调用的远程服务划分出多个线程池. 例如调用产品服务的Command放入A线程池, 调用帐户服务的Command放入B线程池. 这样作的主要优势是运行环境被隔离开了. 这样就算调用服务的代码存在bug或者因为其余缘由致使本身所在线程池被耗尽时, 不会对系统的其余服务形成影响. 可是带来的代价就是维护多个线程池会对系统带来额外的性能开销. 若是是对性能有严格要求并且确信本身调用服务的客户端代码不会出问题的话, 可使用Hystrix的信号模式(Semaphores)来隔离资源.spring
关于服务降级:后端
本小节咱们来模拟一下触发服务降级的状况,首先在订单服务项目的pom.xml文件中,加入Spring Cloud Hystrix依赖。以下:缓存
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
添加好依赖后修改一下启动类的注解。修改后代码以下:bash
package org.zero.springcloud.order.server; import org.springframework.cloud.client.SpringCloudApplication; import org.springframework.cloud.openfeign.EnableFeignClients; @SpringCloudApplication @EnableFeignClients(basePackages = "org.zero.springcloud.product.client") public class OrderApplication { public static void main(String[] args) { SpringApplication.run(OrderApplication.class, args); } }
在controller包中,新建一个 HystrixController ,咱们在这个类里作实验。在这个类里,咱们调用了商品服务中的查询商品信息接口。为了模拟服务宕机触发降级,因此此时我已经把商品服务关闭了。具体代码以下:服务器
package org.zero.springcloud.order.server.controller; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import java.util.Collections; /** * @program: sell_order * @description: Hystrix Demo * @author: 01 * @create: 2018-08-28 20:10 **/ @Slf4j @RestController @RequestMapping("/hystrix/demo") public class HystrixController { /** * 经过@HystrixCommand注解指定可降级的服务,fallbackMethod参数指向的是回调函数,函数名称可自定义 * * @return String */ @HystrixCommand(fallbackMethod = "fallback") @GetMapping("/getProductInfoList") public String getProductInfoList() { RestTemplate restTemplate = new RestTemplate(); return restTemplate.postForObject("http://127.0.0.1:8519/buyer/product/listForOrder", Collections.singletonList("157875196366160022"), String.class); } /** * 触发降级后的回调函数 * * @return String */ public String fallback() { return "太拥挤了, 请稍后重试~"; } }
启动项目,访问结果以下:网络
从测试结果能够看到,因为商品服务关闭了,致使没法调用相应的接口。触发了服务降级后,调用了注解中指定的回调函数,并返回了相应的提示。架构
触发服务降级不必定是服务调用失败,由于服务降级的主要触发缘由是抛出了异常,因此只要这个方法中抛出了未被捕获的异常都会触发服务降级。以下示例:app
@HystrixCommand(fallbackMethod = "fallback") @GetMapping("/getProductInfoList") public String getProductInfoList() { throw new RuntimeException("发生了异常"); }
在某些状况下,咱们可能只须要定义一个默认的回调处理函数便可,那么咱们就可使用@DefaultProperties
注解来定义默认的回调函数,这样就不须要每一个 @HystrixCommand
注解都指定一个回调函数了。以下示例:
package org.zero.springcloud.order.server.controller; import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import java.util.Collections; /** * @program: sell_order * @description: Hystrix Demo * @author: 01 * @create: 2018-08-28 20:10 **/ @Slf4j @RestController @RequestMapping("/hystrix/demo") @DefaultProperties(defaultFallback = "defaultFallback") public class HystrixController { /** * 定义了@DefaultProperties后,只需经过@HystrixCommand注解指定可降级的服务便可 * * @return String */ @HystrixCommand @GetMapping("/getProductInfoList") public String getProductInfoList() { RestTemplate restTemplate = new RestTemplate(); return restTemplate.postForObject("http://127.0.0.1:8519/buyer/product/listForOrder", Collections.singletonList("157875196366160022"), String.class); } /** * 触发降级后的回调函数 * * @return String */ public String defaultFallback() { return "太拥挤了, 请稍后重试~"; } }
使用 @HystrixCommand
注解的接口是有一个默认超时时间的,当调用某个服务的耗时超过这个时间也会触发服务降级,默认的超时时间是1秒。咱们也能够去自定义这个超时时间,以下示例:
@HystrixCommand(commandProperties = { // 设置超时时间为3秒 @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000") }) @GetMapping("/getProductInfoList") public String getProductInfoList() { RestTemplate restTemplate = new RestTemplate(); return restTemplate.postForObject("http://127.0.0.1:8519/buyer/product/listForOrder", Collections.singletonList("157875196366160022"), String.class); }
断路器就像电路中的断路器同样,当短路发生时,它第一时刻熔断,切断了故障电路,保护其余用电单元。
在分布式架构中,断路器的做用相似,当某个服务单元发生了故障,经过断路器的故障监控,直接切断原来的主逻辑调用,强迫之后的多个服务调用再也不访问远程服务器,防止应用程序继续执行或等待超时。熔断器也能够监控服务单元的错误是否已经修正,若是已经修正,应用程序会再次尝试调用操做。
在微服务架构中,系统被拆分红了一个个小的服务单元,各自运行在本身的线程中,各单元之间经过注册与订阅的方式互相远程调用,此时若网络故障或是某一服务挂掉则会出现调用延迟,进一步致使调用方的对外服务也出现延迟,若是调用方的请求不断增长,服务单元线程资源没法释放,队列装满,最终致使故障的蔓延,故断路器就是解决这种问题的。
断路器模式:
当Hystrix Command请求后端服务失败数量超过必定比例(默认50%), 断路器会切换到开路状态(Open). 这时全部请求会直接失败而不会发送到后端服务. 断路器保持在开路状态一段时间后(默认5秒), 自动切换到半开路状态(HALF-OPEN). 这时会判断下一次请求的返回状况, 若是请求成功, 断路器切回闭路状态(CLOSED), 不然从新切换到开路状态(OPEN). 即有自我检测并恢复的能力.
代码示例:
@HystrixCommand(commandProperties = { @HystrixProperty(name = "circuitBreaker.enabled", value = "true"), // 开启熔断机制 @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"), // 设置当请求失败的数量达到10个后,打开断路器,默认值为20 @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"), // 设置打开断路器多久之后开始尝试恢复,默认为5s @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60"), // 设置出错百分比阈值,当达到此阈值后,打开断路器,默认50% }) @GetMapping("/getProductInfoList") public String getProductInfoList(@RequestParam("number") Integer number) { if (number % 2 == 0) { return "success"; } RestTemplate restTemplate = new RestTemplate(); return restTemplate.postForObject("http://127.0.0.1:8519/buyer/product/listForOrder", Collections.singletonList("157875196366160022"), String.class); }
在代码里写配置可能不太方便维护,咱们也能够在配置文件中使用配置项进行配置。例如超时时间配置以下:
hystrix: command: default: execution: isolation: thread: timeoutInMilliseconds: 3000
若指定配置某一个方法的超时时间,将default换成相应方法名便可。以下示例:
hystrix: command: getProductInfoList: execution: isolation: thread: timeoutInMilliseconds: 3000
断路器的的配置方式也是同样的,以下示例:
hystrix: command: default: execution: isolation: thread: timeoutInMilliseconds: 3000 circuitBreaker: enabled: true requestVolumeThreshold: 10 sleepWindowInMilliseconds: 10000 errorThresholdPercentage: 60
咱们在订单服务中,使用了feign组件去调用商品服务实现服务间的通讯。而feign内部已包含了hystrix,因此也能够实现服务降级。首先在订单服务项目的配置文件中,增长以下配置:
feign: hystrix: enabled: true # 开启hystrix
到商品服务项目的client模块中,新增一个 ProductClientFallback 类,并实现ProductClient接口。代码以下:
package org.zero.springcloud.product.client; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.zero.springcloud.product.common.DecreaseStockInput; import org.zero.springcloud.product.common.ProductInfoOutput; import java.util.List; /** * @program: sell_product * @description: 触发服务降级时会调用相应的方法 * @author: 01 * @create: 2018-08-29 21:39 **/ @Slf4j @Component public class ProductClientFallback implements ProductClient { @Override public List<ProductInfoOutput> productInfoList(List<String> productIdList) { log.info("productInfoList() 触发了服务降级"); return null; } @Override public void decreaseStock(List<DecreaseStockInput> cartDTOList) { log.info("decreaseStock() 触发了服务降级"); } }
而后在 ProductClient 接口的@FeignClient
注解里增长 fallback 属性,并指定以上编写的实现类。当某个接口触发降级时,就会调用实现类里的方法。代码以下:
package org.zero.springcloud.product.client; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.zero.springcloud.product.common.DecreaseStockInput; import org.zero.springcloud.product.common.ProductInfoOutput; import java.util.List; /** * @program: sell_order * @description: 配置须要调用的接口地址 * @author: 01 * @create: 2018-08-19 12:14 **/ @FeignClient(name = "PRODUCT", fallback = ProductClientFallback.class) public interface ProductClient { /** * 调用商品服务-按id查询商品列表 * 注意,接口地址须要填写完整 * * @param productIdList productIdList * @return List<ProductInfo> */ @PostMapping("/buyer/product/listForOrder") List<ProductInfoOutput> productInfoList(@RequestBody List<String> productIdList); /** * 调用商品服务-扣库存 * * @param cartDTOList cartDTOList */ @PostMapping("/buyer/product/decreaseStock") void decreaseStock(@RequestBody List<DecreaseStockInput> cartDTOList); }
编写完以上的代码后,不要忘了安装到maven本地仓库中,安装命令以下:
mvn clean -Dmaven.test.skip=true install
回到订单服务,在启动类上增长@ComponentScan
注解,扩大包扫描范围:
package org.zero.springcloud.order.server; import org.springframework.boot.SpringApplication; import org.springframework.cloud.client.SpringCloudApplication; import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.context.annotation.ComponentScan; @SpringCloudApplication @ComponentScan(basePackages = "org.zero.springcloud") @EnableFeignClients(basePackages = "org.zero.springcloud.product.client") public class OrderApplication { public static void main(String[] args) { SpringApplication.run(OrderApplication.class, args); } }
重启订单服务项目,访问建立订单接口,以下:
控制台输出以下:
注:此时我已关闭了商品服务,因此才会触发服务降级
若是是超时致使服务降级的话,能够在配置文件中配置feign的超时时间,以下:
feign: client: config: default: connectTimeout: 5000 readTimeout: 5000 loggerLevel: basic
hystrix-dashboard是一个可视化的熔断监视工具,咱们本小节来看看如何在项目中使用这个工具。咱们以订单服务项目为例,首先在pom.xml文件中,增长以下依赖:
<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>
在启动类中,增长@EnableHystrixDashboard
注解。代码以下:
package org.zero.springcloud.order.server; import org.springframework.boot.SpringApplication; import org.springframework.cloud.client.SpringCloudApplication; import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.context.annotation.ComponentScan; @EnableHystrixDashboard @SpringCloudApplication @ComponentScan(basePackages = "org.zero.springcloud") @EnableFeignClients(basePackages = "org.zero.springcloud.product.client") public class OrderApplication { public static void main(String[] args) { SpringApplication.run(OrderApplication.class, args); } }
在config包下新建一个 HystrixConfig 配置类,用于配置 HystrixMetricsStreamServlet 。代码以下:
package org.zero.springcloud.order.server.config; import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet; import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @program: sell_order * @description: 配置HystrixMetricsStreamServlet * @author: 01 * @create: 2018-08-29 22:22 **/ @Configuration public class HystrixConfig { @Bean public HystrixMetricsStreamServlet hystrixMetricsStreamServlet() { return new HystrixMetricsStreamServlet(); } @Bean public ServletRegistrationBean registration(HystrixMetricsStreamServlet servlet) { ServletRegistrationBean<HystrixMetricsStreamServlet> registrationBean = new ServletRegistrationBean<>(); registrationBean.setServlet(servlet); //是否启用该registrationBean registrationBean.setEnabled(true); registrationBean.addUrlMappings("/hystrix.stream"); return registrationBean; } }
完成以上代码的编写后,重启项目,访问http://localhost:9080/hystrix
,会进入到以下页面中:
经过Hystrix Dashboard主页面的文字介绍,咱们能够知道,Hystrix Dashboard共支持三种不一样的监控方式:
我这里使用的是单体应用的监控,点击Monitor Stream后,进入到以下页面,在此页面能够看到这个项目的请求信息:
咱们来对这些指标进行一个简单的说明: