学习笔记
作用:断路器,保护系统,控制故障范围
简介:Hystrix是一个延迟和容错库,旨在隔离对远程系统,服务和第三方库的访问点,停止级联故障,并在复杂的分布式系统中实现弹性,在这些系统中,故障是不可避免的。为了保证其高可用,单个服务通常会集群部署。由于网络原因或者自身的原因,服务并不能保证100%可用,如果单个服务出现问题,调用这个服务就会出现线程阻塞,此时若有大量的请求涌入,Servlet容器的线程资源会被消耗完毕,导致服务瘫痪。服务与服务之间的依赖性,故障会传播,会对整个微服务系统造成灾难性的严重后果,这就是服务故障的“雪崩”效应。
Hystrix特性
服务熔断: 目的是为了保护服务,在高并发的情况下,如果请求达到了一定的极限(可以自己设置阈值),如果流量超过了自己设置的阈值,自动开启保护功能,使用服务降级方式返回一个友好的提示。
服务降级:Fallback相当于是降级操作。在高并发的情况下、防止用户一直等待,使用服务降级方式(调用fallBack本地方法直接返回一个友好提示给客户端,告知后面的请求服务不可用了),目的是为了更好的用户体验。
依赖隔离(采用舱壁模式,Docker就是舱壁模式的一种):在Hystrix中, 主要通过线程池来实现资源隔离。通常在使用的时候会根据调用的远程服务划分出多个线程池。
请求缓存:请求缓存是在同一请求多次访问中保证只调用一次这个服务提供者的接口,在这同一次请求第一次的结果会被缓存,把第一次请求缓存的结果返回给后面同样的请求。
请求合并:把重复的请求批量的用一个HystrixCommand命令去执行,以减少通信消耗和线程数的占用,默认合并10ms内的请求。优点:减少了通信开销。缺点:请求延迟增加。
Hystrix流程结构解析
流程说明
1:每次调用创建一个新的HystrixCommand,把依赖调用封装在run()方法中.
2:执行execute()/queue做同步或异步调用.
3:判断熔断器(circuit-breaker)是否打开,如果打开跳到步骤8,进行降级策略,如果关闭进入步骤.
4:判断线程池/队列/信号量是否跑满,如果跑满进入降级步骤8,否则继续后续步骤.
5:调用HystrixCommand的run方法.运行依赖逻辑
5a:依赖逻辑调用超时,进入步骤8.
6:判断逻辑是否调用成功
6a:返回成功调用结果
6b:调用出错,进入步骤8.
7:计算熔断器状态,所有的运行状态(成功, 失败, 拒绝,超时)上报给熔断器,用于统计从而判断熔断器状态.
8:getFallback()降级逻辑.
以下四种情况将触发getFallback调用:
(1):run()方法抛出非HystrixBadRequestException异常。
(2):run()方法调用超时
(3):熔断器开启拦截调用
(4):线程池/队列/信号量是否跑满
8a:没有实现getFallback的Command将直接抛出异常
8b:fallback降级逻辑调用成功直接返回
8c:降级逻辑调用失败抛出异常
9:返回执行成功结果
如何使用
服务降级和服务熔断操作
Ribbon消费者
1,引入依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
2,打开开关(贴 @EnableHystrix 注解)
@SpringBootApplication @EnableEurekaClient @EnableFeignClients @EnableHystrix public class AppOrder { public static void main(String[] args) { SpringApplication.run(AppOrder.class, args); } @Bean //把RestTemplate注册到SpringBoot容器中,如果使用rest方式以别名方式进行调用,依赖ribbon负载均衡器@LoadBalanced @LoadBalanced //@LoadBalanced就能让这个RestTemplate在请求时拥有客户端负载均衡的能力 RestTemplate restTemplate() { return new RestTemplate(); } }
3,添加服务器降级处理方法
@RestController public class OrderController { //RestTemplate是由SpringBoot Web组件提供,默认整合ribbon负载均衡器 //rest方式底层是采用httpclient技术 @Autowired private RestTemplate restTemplate; //订单服务调用会员服务 @RequestMapping("/getOrder") @HystrixCommand(fallbackMethod = "defaultFallback") public String getOrder() { String url = "http://app-hclz-member/getMember"; String result = restTemplate.getForObject(url, String.class); System.out.println("订单服务调用会员服务result:"+result); return result; } // 添加服务器降级处理方法 public String defaultFallback() { return "error order fallback"; } }
4,测试,将服务提供者关掉
过一会,再将服务提供者启动,可以观察到,服务消费者自动恢复。
在Feign中使用
@EnableFeignClients中已经默认打开了断路器功能,所以这里的启动类上不需要再加@EnableHystrix注解
只需要在@FeignClient中为fallback参数指定fallback方法
@FeignClient(value = "app-hclz-member",fallback = MyFallBack.class) public interface MemberFeign { @GetMapping("/getMember") public String getMember(); }
因为@FeignClient注解的是接口,所以我们必须创建一个替代的类
@Component public class MyFallBack implements MemberFeign { public String getMember() { return "feign中断路器开启 error getMember"; } }
最后一步在配置文件application.yml中开启断路器
#开启断路器 feign: hystrix: enabled: true
可以看到和Ribbon一样的效果
附:Hystrix 说明
什么情况下会触发 fallback
方法
名字 | 描述 | 触发fallback |
---|---|---|
EMIT | 值传递 | NO |
SUCCESS | 执行完成,没有错误 | NO |
FAILURE | 执行抛出异常 | YES |
TIMEOUT | 执行开始,但没有在允许的时间内完成 | YES |
BAD_REQUEST | 执行抛出HystrixBadRequestException | NO |
SHORT_CIRCUITED | 断路器打开,不尝试执行 | YES |
THREAD_POOL_REJECTED | 线程池拒绝,不尝试执行 | YES |
SEMAPHORE_REJECTED | 信号量拒绝,不尝试执行 | YES |
fallback
方法在什么情况下会抛出异常
名字 | 描述 | 抛异常 |
---|---|---|
FALLBACK_EMIT | Fallback值传递 | NO |
FALLBACK_SUCCESS | Fallback执行完成,没有错误 | NO |
FALLBACK_FAILURE | Fallback执行抛出出错 | YES |
FALLBACK_REJECTED | Fallback信号量拒绝,不尝试执行 | YES |
FALLBACK_MISSING | 没有Fallback实例 | YES |
使用熔断器仪表盘监控
在 Ribbon 和 Feign 项目增加 Hystrix 仪表盘功能,两个项目的改造方式相同
1.在pom.xml添加依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId> </dependency>
2.在Application中添加@EnableHystrixDashboard注解
3.创建hystrix.stream的Servlet配置,Spring Boot 2.x 版本开启 Hystrix Dashboard 与 Spring Boot 1.x 的方式略有不同,需要增加一个 HystrixMetricsStreamServlet
的配置,代码如下:
@Configuration public class HystrixDashboardConfiguration { @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; } }
浏览器端访问 http://localhost:8764/hystrix 界面如下:
Hystrix Dashboard 界面监控参数
依赖隔离操作
添加OrderCommand,UserCommand
public class OrderCommand extends HystrixCommand<String> { private String value; public OrderCommand(String value) { super(Setter.withGroupKey( //服务分组 HystrixCommandGroupKey.Factory.asKey("OrderGroup")) //线程分组 .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("OrderPool")) //线程池配置 .andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter() .withCoreSize(10) .withKeepAliveTimeMinutes(5) .withMaxQueueSize(10) .withQueueSizeRejectionThreshold(10000)) .andCommandPropertiesDefaults( HystrixCommandProperties.Setter() .withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD))); this.value = value; } @Override protected String run() throws Exception { String threadName = Thread.currentThread().getName(); return threadName + " ---> " + value; } }
测试
@Controller public class OrderController { // 测试依赖隔离 public String testPool() throws ExecutionException, InterruptedException { UserCommand userCommand = new UserCommand("绿巨人"); OrderCommand orderCommand1 = new OrderCommand("钢铁侠"); OrderCommand orderCommand2 = new OrderCommand("蜘蛛侠"); // 同步调用 String val1 = userCommand.execute(); String val2 = orderCommand1.execute(); String val3 = orderCommand2.execute(); return "val1=" + val1 + " val2=" + val2 + " val3=" + val3; } @GetMapping("/pool") @ResponseBody public String pool() throws ExecutionException, InterruptedException { return testPool(); } }
同步调用
请求缓存操作
继承类的方式
public class CacheCommand extends HystrixCommand<String> { private Integer cacheKey; private RestTemplate restTemplate; private Integer uid; public CacheCommand(Integer cacheKey, RestTemplate restTemplate, Integer uid) { super(Setter.withGroupKey( //服务分组 HystrixCommandGroupKey.Factory.asKey("cache-group")) .andCommandKey(HystrixCommandKey.Factory.asKey("cache-test"))); this.cacheKey = cacheKey; this.restTemplate = restTemplate; this.uid = uid; } @Override protected String run() throws Exception { System.out.println("----------------没有缓存查询数据"); String url = "http://app-hclz-member/getUser/{id}"; String info = restTemplate.getForObject(url, String.class, uid); return info; } @Override protected String getCacheKey() { return String.valueOf(cacheKey); } //清空request缓存 public void clearRequestCache() { HystrixRequestCache.getInstance( HystrixCommandKey.Factory.asKey("cache-test"), HystrixConcurrencyStrategyDefault.getInstance()).clear(String.valueOf(cacheKey)); } }
@Controller public class UserController { @Autowired private RestTemplate restTemplate; public String getUser(Integer id) { //cacheKey相同缓存数据 Integer cacheKey = 1; CacheCommand cacheCommand = new CacheCommand(cacheKey, restTemplate, id); String val = cacheCommand.execute(); //清空request缓存 cacheCommand.clearRequestCache(); return val; } @GetMapping("/cache") @ResponseBody public String cache() { HystrixRequestContext context = HystrixRequestContext.initializeContext(); //调用用户,查询用户信息 String result1 = getUser(1); String result2 = getUser(2); context.close(); return "result1--->"+result1+" result2--->"+result2; } }
贴注解的方式
@Controller public class UserController { @Autowired private RestTemplate restTemplate;//贴注解的方式 @CacheResult //该注解用来标记请求命令返回的结果应该被缓存,必须与@HystrixCommand注解结合使用 @HystrixCommand(commandKey = "cache-user") public String getUser2(Integer id, @CacheKey Long cacheKey) { String url = "http://app-hclz-member/getUser/{id}"; String info = restTemplate.getForObject(url, String.class, id); return info; } @CacheRemove(commandKey = "cache-user")//该注解使请求命令的缓存失效,失效的缓存根据定义的Key决定 @HystrixCommand public void clearRequestCache(@CacheKey Long cacheKey) { } @GetMapping("/cache2") @ResponseBody public String cache2() { HystrixRequestContext context = HystrixRequestContext.initializeContext(); Long cacheKey = 1L; //调用用户,查询用户信息 String result1 = getUser2(1,cacheKey); String result2 = getUser2(2,cacheKey); context.close(); return "result1--->"+result1+" result2--->"+result2; } }
请求合并操作
@RestController public class UserController { @Autowired private RestTemplate restTemplate; @HystrixCollapser(batchMethod = "findAll",collapserProperties = { @HystrixProperty(name = "timerDelayInMillisseconds",value = "500") }) public Future<String> findOne(Integer id) { System.out.println("被合并的请求"); return null; } @HystrixCommand public List<String> findAll(List<Integer> ids) { System.out.println("合并的请求"); String url = "http://app-hclz-member/getUserAll?ids= {1}"; return restTemplate.getForObject(url, List.class, StringUtils.join(ids,",")); } @RequestMapping("/findTest") public String findTest() throws InterruptedException, ExecutionException { HystrixRequestContext context = HystrixRequestContext.initializeContext(); Future<String> f1 = findOne(1); Future<String> f2 = findOne(2); Future<String> f3 = findOne(3); String result = f1.get()+","+f2.get()+","+f3.get(); context.close(); return result; } }