springcloud记录篇4-断路器Circuit Breaker

一。断路器介绍

  分布式系统中 服务和服务之间调用必然存在多层,调用过程必然涉及到网络 cpu 内存的占用 假设订阅者调用发布者 发布者服务出现宕机等 订阅者如果

不能及时发现监控到错误 所有的请求依然会被请求到失败的发布者服务,失败的请求可能需要连接网络 开启线程 失败重试等 可能导致订阅者服务越来越多请

申请这些资源 而导致订阅者宕机 此时如果有其他的服务也要调用订阅者的服务 就会发生连锁的效应(雪崩效应) 

  

 如上图所示

   A调用B B调用C  如果C发生异常 B依然调用 会出现雪崩效应导致B和A都异常 如果B调用C过程中发现达到多少次一直无法连接 就应该

开启一个断路器 开发者给断路器指定一个回调 在C未恢复健康状况之前一直返回回调 不需要再次连接C  

断路器的状态

》》完全打开

  一定时间内 达到多少次C无法调用 并且多次检测C没有恢复迹象  断路器完全打开

》》半开

  一定时间内 达到多少次C无法调用 短时间内 C有恢复迹象  断路器会将部分请求发送给C  部分请求通过断路器返回回调 当部分请求完成能正常调用

 可能会关闭断路器 

》》关闭  

  如果C服务一直处于正常状态 B都可以正常调用 断路器处于关闭状态


二。ribbon使用断路器

  测试场景 沿用 纪录篇3代码  http://blog.csdn.net/liaomin416100569/article/details/78164024

[html]  view plain  copy
  1. eurekaserver    本机ip为 192.168.88.20  主机名 mymaster 端口 8761  这里为了简单就不开启eurekaserver的从服务eurekabackup    
  2. 服务提供和服务消费配置      
  3.   eurekapub  本机ip  192.168.88.20 端口8086 应用名称 idserver  为了简单不开启多个发布方 只开启一个  
  4.   eurekaconsumer 本机ip  192.168.88.20 端口8888    
Netflix提供了一个名字为 Hystrix的库 用于实现断路器模式 (https://github.com/Netflix/Hystrix)springcloud集成了该库

1》引入ribbon maven依赖

[html]  view plain  copy
  1. <dependency>  
  2.             <groupId>org.springframework.cloud</groupId>  
  3.             <artifactId>spring-cloud-starter-hystrix</artifactId>  
  4.         </dependency>  
2》配置和测试断路器
 添加主类

[html]  view plain  copy
  1. package cn.et;  
  2.   
  3. import org.springframework.boot.SpringApplication;  
  4. import org.springframework.boot.autoconfigure.SpringBootApplication;  
  5. import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;  
  6. import org.springframework.cloud.client.discovery.EnableDiscoveryClient;  
  7. import org.springframework.cloud.client.loadbalancer.LoadBalanced;  
  8. import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;  
  9. import org.springframework.cloud.netflix.ribbon.RibbonClient;  
  10. import org.springframework.context.annotation.Bean;  
  11. import org.springframework.context.annotation.Configuration;  
  12. import org.springframework.web.client.RestTemplate;  
  13.   
  14. import cn.et1.RuleSetting;  
  15.   
  16. @SpringBootApplication  
  17. @EnableDiscoveryClient(autoRegister=true)  
  18. @Configuration  
  19. @EnableCircuitBreaker //启用断路器 必须使用该注解  
  20. @RibbonClient(value="IDSERVER")  
  21. public class EurekaconsumerHyApplication {  
  22.   
  23.     @LoadBalanced  
  24.     @Bean  
  25.     RestTemplate restTemplate() {  
  26.         return new RestTemplate();  
  27.     }  
  28.     public static void main(String[] args) {  
  29.         SpringApplication.run(EurekaconsumerApplication.class, args);  
  30.     }  
  31. }  
添加控制器类 被hystrix保护的类必须添加@HystrixCommand注解

[html]  view plain  copy
  1. package cn.et;  
  2.   
  3. import org.springframework.beans.factory.annotation.Autowired;  
  4. import org.springframework.cloud.client.ServiceInstance;  
  5. import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;  
  6. import org.springframework.web.bind.annotation.RequestMapping;  
  7. import org.springframework.web.bind.annotation.RestController;  
  8. import org.springframework.web.client.RestTemplate;  
  9.   
  10. import com.netflix.appinfo.InstanceInfo;  
  11. import com.netflix.discovery.EurekaClient;  
  12. import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;  
  13.   
  14. @RestController  
  15. public class TestController {     
  16.     @Autowired  
  17.         private RestTemplate restTemplate;  
  18.   
  19.     /**  
  20.      * @HystrixCommand用于标识当前的方法 被断路器保护 一旦出现无法调用 自动fallbackMethod回调指定的方法  
  21.            该方法的参数和返回值 必须和被保护的方法一致  
  22.      * @return  
  23.      */  
  24.     @RequestMapping("invokeServiceBalance")  
  25.     @HystrixCommand(fallbackMethod="invokeServiceBalanceFallBack")    
  26.     public String invokeServiceBalance() {  
  27.         String uuid=restTemplate.getForObject("http://IDSERVER/getId",String.class);  
  28.         return uuid;  
  29.     }  
  30.     public String invokeServiceBalanceFallBack() {  
  31.         return "NOUUID";  
  32.     }  
  33. }  


正常启动 eurekaserver和eurekapub  然后启动 eurekaconsume 使用了注解@EnableCircuitBreaker启用了断路器 可以通过添加actutor来导出

路径  /hystrix.stream 查看断路器的状态

添加maven依赖

[html]  view plain  copy
  1. <dependency>  
  2.           <groupId>org.springframework.boot</groupId>  
  3.           <artifactId>spring-boot-starter-actuator</artifactId>  
  4.         </dependency>  
先访问一次 http://localhost:8888/invokeServiceBalance 发现正常输出uuid

访问  http://localhost:8888//hystrix.stream 查看 发现打印一堆统计流信息(Metrics Stream) 看第一行就行

[html]  view plain  copy
  1. data: {"type":"HystrixCommand","name":"invokeServiceBalance","group":"TestController","currentTime":1507519121493,"isCircuitBreakerOpen":false,"  
属性解释:

  • type:表示使用HystrixCommand定义断路器
  • name:表示断路器保护的方法
  • group:表示方法所在的类
  • isCircuitBreakerOpen: 表示断路器是否打开 因为pub是可以连接的所以断路器是关闭的

此时关闭 eurekapub服务 第一次访问 http://localhost:8888/invokeServiceBalance

发现调用了指定的回调方法 输出了 NOUUID 此时查看 hystrix.stream发现 

isCircuitBreakerOpen":false

说明 需要到达一定的访问量后 断路器才会打开 默认是 1s 5次失败 多刷新一次 

isCircuitBreakerOpen":true


三。feign使用断路器

 feign作为客户端调用工具也存在 一章节提到的断路问题  如果在类路径中存在断路器的jar 自动设置 feign.hystrix.enabled=true 启用了断路器

查看FeignClientsConfiguration源代码 发现

[html]  view plain  copy
  1. @Configuration  
  2.     @ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })  
  3.     protected static class HystrixFeignConfiguration {  
  4.         @Bean  
  5.         @Scope("prototype")  
  6.         @ConditionalOnMissingBean//如果设置了feign.hystrix.enabled 创建HystrixFeign.Builder  
  7.         @ConditionalOnProperty(name = "feign.hystrix.enabled"matchIfMissing = false)    
  8.         public Feign.Builder feignHystrixBuilder() {  
  9.             return HystrixFeign.builder();  
  10.         }  
  11.     }  
如果希望不使用Hystrix 可以在feign指定的配置类中添加 返回 Feign.Builder

[html]  view plain  copy
  1. @Configuration  
  2. public class FooConfiguration {  
  3.     @Bean  
  4.     @Scope("prototype")  
  5.     public Feign.Builder feignBuilder() {  
  6.         return Feign.builder();  
  7.     }  
  8. }  
1》引入feign-hystrix maven依赖
 同ribbon引入hystrix即可

2》配置和测试断路器
 测试场景 沿用 纪录篇3代码  http://blog.csdn.net/liaomin416100569/article/details/78164024

 main类添加

[html]  view plain  copy
  1. @EnableCircuitBreaker  
 TestController类无需修改 依然两个调用

[html]  view plain  copy
  1. /**    
  2.      * 使用Feign调用client的get方式    
  3.      * @return    
  4.      */    
  5.     @RequestMapping("getUser")    
  6.     public User getUser(String id) {    
  7.         User user=client.getId(id);    
  8.         return user;    
  9.     }    
  10.     /**    
  11.      * 使用Feign调用client的post方法    
  12.      * @return    
  13.      */    
  14.     @RequestMapping("saveUser")    
  15.     public User saveUser(User user) {    
  16.         User muser=client.saveUser(user);    
  17.         return muser;    
  18.     }     


修改客户端类 TestClient

[html]  view plain  copy
  1. package cn.feign;  
  2.   
  3. import org.springframework.cloud.netflix.feign.FeignClient;  
  4. import org.springframework.web.bind.annotation.PathVariable;  
  5. import org.springframework.web.bind.annotation.RequestBody;  
  6. import org.springframework.web.bind.annotation.RequestMapping;  
  7. import org.springframework.web.bind.annotation.RequestMethod;  
  8. /**  
  9.  * fallback指定断路器的回调 该类必须实现 当前的接口  
  10.  * @author jiaozi  
  11.  *  
  12.  */  
  13. @FeignClient(name="idserver",fallback=TestClientFallback.class)  
  14. public interface TestClient {  
  15.     @RequestMapping(method = RequestMethod.GET, value = "/getUser/{id}")  
  16.     public User getId(@PathVariable("id") String id) ;  
  17.       
  18.     @RequestMapping(method = RequestMethod.POST, value = "/saveUser",consumes="application/json")  
  19.     public User saveUser(@RequestBody User user) ;  
  20. }  
  21. /**  
  22.  * 断路器全部返回 空的用户  
  23.  * @author jiaozi  
  24.  *  
  25.  */  
  26. class TestClientFallback implements TestClient{  
  27.   
  28.     @Override  
  29.     public User getId(String id) {  
  30.         User user=new User();  
  31.         return user;  
  32.     }  
  33.   
  34.     @Override  
  35.     public User saveUser(User user) {  
  36.         User user1=new User();  
  37.         return user1;  
  38.     }  
  39.       
  40. }  
同ribbon测试方式  此时无法启动 说无法找到TestClientFallback实例 

必须在TestClientFallback上添加

[html]  view plain  copy
  1. @Component  


如果希望在feign断路器中 捕获到错误 可以feignClient注解上指定fallbackFactory 具体参考

http://cloud.spring.io/spring-cloud-static/Dalston.SR4/single/spring-cloud.html#spring-cloud-feign-hystrix-fallback

四。断路器仪表盘(Hystrix Dashboard)

  仪表盘是统计流信息的可视化界面(web界面)

1》引入Hystrix Dashboard maven依赖

eurekaconsume中添加依赖

[html]  view plain  copy
  1. <dependency>  
  2.             <groupId>org.springframework.cloud</groupId>  
  3.             <artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>  
  4.         </dependency>  
主类上添加

[html]  view plain  copy
  1. @EnableHystrixDashboard  

2》测试访问

启动后访问 http://localhost:8888/hystrix


第一个文本框中 输入统计流信息地址 title自己定义一个标题 点击 monitor stream (必须先访问一次被保护的方法  http://localhost:8888/invokeServiceBalance 否则  一致阻塞)


hystrixdashboard 只能在开始界面输入一个监控地址 检测某个客户端的调用 不能检测多个 tubine可以对整个eurreka集群上的断路器调用进行检测

五。分布式监测仪表盘Turbine

turbine是个单独应用 可以注册到eurekaserver 并且发现所有的断路器客户端  并监测 原理是聚合所有服务的/hystrix.stream到/turbine.stream 显示到hystrix仪表盘上

turbine所有配置参考 https://github.com/Netflix/Turbine/wiki/Configuration-(1.x)

注意点(可以配置后面的代码后再看以下内容):

    这里很重要的是 eurekaconsume之前用的application.properteis 后来用turbine调试半天发现 改成 application.yml才能被turbine发现 

解决的步骤 分析源码:

通过发现注解

[html]  view plain  copy
  1. @Import(TurbineHttpConfiguration.class)  
  2. public @interface EnableTurbine {  
  3.   
  4. }  
看到导入了TurbineHttpConfiguration类 该类源码

[html]  view plain  copy
  1. /*  
  2.  * Copyright 2013-2015 the original author or authors.  
  3.  *  
  4.  * Licensed under the Apache License, Version 2.0 (the "License");  
  5.  * you may not use this file except in compliance with the License.  
  6.  * You may obtain a copy of the License at  
  7.  *  
  8.  *      http://www.apache.org/licenses/LICENSE-2.0  
  9.  *  
  10.  * Unless required by applicable law or agreed to in writing, software  
  11.  * distributed under the License is distributed on an "AS IS" BASIS,  
  12.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
  13.  * See the License for the specific language governing permissions and  
  14.  * limitations under the License.  
  15.  */  
  16.   
  17. package org.springframework.cloud.netflix.turbine;  
  18.   
  19. import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;  
  20. import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;  
  21. import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;  
  22. import org.springframework.boot.context.properties.EnableConfigurationProperties;  
  23. import org.springframework.boot.web.servlet.ServletRegistrationBean;  
  24. import org.springframework.cloud.client.actuator.HasFeatures;  
  25. import org.springframework.cloud.client.discovery.DiscoveryClient;  
  26. import org.springframework.cloud.client.discovery.EnableDiscoveryClient;  
  27. import org.springframework.context.annotation.Bean;  
  28. import org.springframework.context.annotation.Configuration;  
  29.   
  30. import com.netflix.discovery.EurekaClient;  
  31. import com.netflix.turbine.discovery.InstanceDiscovery;  
  32. import com.netflix.turbine.streaming.servlet.TurbineStreamServlet;  
  33.   
  34. /**  
  35.  * @author Spencer Gibb  
  36.  */  
  37. @Configuration  
  38. @EnableConfigurationProperties  
  39. @EnableDiscoveryClient  
  40. public class TurbineHttpConfiguration {  
  41.   
  42.     @Bean  
  43.     public HasFeatures Feature() {  
  44.         return HasFeatures.namedFeature("Turbine (HTTP)", TurbineHttpConfiguration.class);  
  45.     }  
  46.         //注册了servlet  映射的路径是 /turbin.stream  
  47.     @Bean  
  48.     public ServletRegistrationBean turbineStreamServlet() {  
  49.         return new ServletRegistrationBean(new TurbineStreamServlet(), "/turbine.stream");  
  50.     }  
  51.   
  52.     @Bean  
  53.     public TurbineProperties turbineProperties() {  
  54.         return new TurbineProperties();  
  55.     }  
  56.   
  57.     @Bean  
  58.     public TurbineLifecycle turbineLifecycle(InstanceDiscovery instanceDiscovery) {  
  59.         return new TurbineLifecycle(instanceDiscovery);  
  60.     }  
  61.   
  62.     @Configuration  
  63.     @ConditionalOnClass(EurekaClient.class)  
  64.     protected static class EurekaTurbineConfiguration {  
  65.   
  66.         @Bean  
  67.         @ConditionalOnMissingBean  
  68.         public InstanceDiscovery instanceDiscovery(TurbineProperties turbineProperties, EurekaClient eurekaClient) {  
  69.             return new EurekaInstanceDiscovery(turbineProperties, eurekaClient);  
  70.         }  
  71.   
  72.     }  
  73.   
  74.     @Configuration  
  75.     @ConditionalOnMissingClass("com.netflix.discovery.EurekaClient")  
  76.     protected static class DiscoveryClientTurbineConfiguration {  
  77.                 //通过配置和eureka去发现 指定需要检测的服务  
  78.         @Bean  
  79.         @ConditionalOnMissingBean  
  80.         public InstanceDiscovery instanceDiscovery(TurbineProperties turbineProperties, DiscoveryClient discoveryClient) {  
  81.             return new CommonsInstanceDiscovery(turbineProperties, discoveryClient);  
  82.         }  
  83.     }  
  84. }  

该类 CommonsInstanceDiscovery 中

[html]  view plain  copy
  1. @Override  
  2.     public Collection<Instance> getInstanceList() throws Exception {  
  3.         List<Instance> instances = new ArrayList<>();  
  4.         List<String> appNames = getTurbineProperties().getAppConfigList();  
  5.         if (appNames == null || appNames.size() == 0) {  
  6.             log.info("No apps configured, returning an empty instance list");  
  7.             return instances;  
  8.         }  
  9.         log.info("Fetching instance list for apps: " + appNames);  
  10.         for (String appName : appNames) {  
  11.             try {//所有配置的appname通过 eurekaClient搜索实例 发现application.properteis配置的实例都无法搜索到  
  12.                 instances.addAll(getInstancesForApp(appName));   
  13.             }  
  14.             catch (Exception ex) {  
  15.                 log.error("Failed to fetch instances for app: " + appName  
  16.                         + ", retrying once more", ex);  
  17.                 try {  
  18.                     instances.addAll(getInstancesForApp(appName));  
  19.                 }  
  20.                 catch (Exception retryException) {  
  21.                     log.error("Failed again to fetch instances for app: " + appName  
  22.                             + ", giving up", ex);  
  23.                 }  
  24.             }  
  25.         }  
  26.         return instances;  
  27.     }  
getInstancesForApp 最后一行是发现服务 如果myclient是使用application.properties返回集合为空 如果使用 application.yml 成功返回  但是在eurekaserver都可以看到  
[html]  view plain  copy
  1. protected List<Instance> getInstancesForApp(String serviceId) throws Exception {  
  2.         List<Instance> instances = new ArrayList<>();  
  3.         log.info("Fetching instances for app: " + serviceId);  
  4.         List<ServiceInstance> serviceInstances = discoveryClient.getInstances(serviceId);  

1 配置启动断路器 场景1 turbine检测单个集群

  启动 eurekaserver和eurekapub 启动之前feign断路器的eurekaconsume主类(启动两个  appname相同(myclient) 端口不同(8888,8878))

 这里将之前application.properties修改为 application.yml

[html]  view plain  copy
  1. server:  
  2.     port: 8878  
  3. #spring.cloud.service-registry.auto-registration.enabled=true  
  4. spring:  
  5.     application:  
  6.         name: myclient   
  7. #eureka.client.healthcheck.enabled=true  
  8. #eureka.client.fetch-registry=true  
  9. #eureka.client.registryFetchIntervalSeconds=5    
  10. eureka:  
  11.     client:  
  12.         serviceUrl:  
  13.             defaultZone: http://jiaozi:jiaozi123@mymaster:8761/eureka/  
  14.   
  15. IDSERVER:  
  16.     ribbon:  
  17.         NFLoadBalancerRuleClassName: cn.et1.MyRule  
  18. logging:  
  19.     level:  
  20.         cn.feign.TestClient2: DEBUG  
  21. feign:  
  22.     hystrix:  
  23.         enabled: true  
2 配置turbine
 添加maven项目添加turbine和eureka依赖 因为tubine项目也会被注册发现

[html]  view plain  copy
  1. <dependency>  
  2.             <groupId>org.springframework.cloud</groupId>  
  3.             <artifactId>spring-cloud-starter-eureka</artifactId>   
  4.         </dependency>   
  5.         <dependency>  
  6.             <groupId>org.springframework.cloud</groupId>  
  7.             <artifactId>spring-cloud-starter-turbine</artifactId>  
  8.         </dependency>  
  9.           
  10.     <dependency>  
  11.             <groupId>org.springframework.boot</groupId>  
  12.             <artifactId>spring-boot-starter-web</artifactId>  
  13.         </dependency>  
  14.         <dependency>  
  15.           <groupId>org.springframework.boot</groupId>  
  16.           <artifactId>spring-boot-starter-actuator</artifactId>  
  17.         </dependency>   

 添加主类 添加注解  @EnableTurbine

[html]  view plain  copy
  1. package cn.et;  
  2.   
  3. import org.springframework.boot.SpringApplication;  
  4. import org.springframework.boot.autoconfigure.SpringBootApplication;  
  5. import org.springframework.cloud.netflix.turbine.EnableTurbine;  
  6. @SpringBootApplication  
  7. @EnableTurbine  
  8. public class TurbineApplication {  
  9.   
  10.     public static void main(String[] args) {  
  11.         SpringApplication.run(TurbineApplication.class, args);  
  12.     }  
  13. }  
application.yml配置文件

[html]  view plain  copy
  1. server:  
  2.   port: 8899  
  3. spring:  
  4.   application:  
  5.     name: turbine  
  6. eureka:  
  7.   client:  
  8.     serviceUrl:  
  9.       defaultZone: http://jiaozi:jiaozi123@mymaster:8761/eureka/  
  10.   instance:  
  11.     hostname: mymaster  #可以通过修改 c:/windows/system32/drivers/etc/hosts文件  
  12.     preferIpAddress: false  #未配置hostname时 使用本机ip地址作为主机名 ipAddress是ip地址  
  13. turbine:  
  14.   aggregator:  
  15.     clusterConfig: MYCLIENT  #集群的名字 是serviceid的大小 turbine.stream访问时  turbine.stream?cluster=MYCLIENT获取监听到的数据  
  16.   appConfig: myclient   #需要被监控的serviceid  

启动 turbine

访问 两个客户端feign的getUser和 saveUser方法 启动断路器

[html]  view plain  copy
  1. http://localhost:8888/saveUser?userName=aa&password=vv  
  2. http://localhost:8888/getUser?id=123  
  3. http://localhost:8878/saveUser?userName=aa&password=vv  
  4. http://localhost:8878/getUser?id=123  

访问 turbine暴露的地址

[html]  view plain  copy
  1. http://localhost:8899/turbine.stream?cluster=MYCLIENT  
将该地址 粘贴到 仪表盘 地址中
[html]  view plain  copy
  1. http://localhost:8888/hystrix 打开仪表盘  

2 监听多个集群

  上面的配置是监听同一个集群 间不同的主机  如果存在多个微服务 都存在断路器 需要监听 可以 这样配置

[html]  view plain  copy
  1. turbine:  
  2.   aggregator:  
  3.     clusterConfig: MYCLIENT,MYTEST  
  4.   appConfig: myclient,mytest  
上面配置 可以分别使用
 http://localhost:8899/turbine.stream?cluster=MYCLIENT
或者 http://localhost:8899/turbine.stream?cluster=MYTEST分别查看 但是仪表盘始终只能输入一个地址 

可以指定 url去掉cluster 默认值 扫描所有定义的serviceid

[html]  view plain  copy
  1. turbine:  
  2.   aggregator:  
  3.     clusterConfig: default  
  4.   appConfig: myclient,myclient1  
  5.   clusterNameExpression: "'default'"  

以上通过地址 http://localhost:8899/turbine.stream 就可以聚合 myeclinet和myclient1所有的断路器应用

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值