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

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

1》引入ribbon maven依赖

<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-hystrix</artifactId>
		</dependency>
2》配置和测试断路器
 添加主类

package cn.et;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

import cn.et1.RuleSetting;

@SpringBootApplication
@EnableDiscoveryClient(autoRegister=true)
@Configuration
@EnableCircuitBreaker //启用断路器 必须使用该注解
@RibbonClient(value="IDSERVER")
public class EurekaconsumerHyApplication {

	@LoadBalanced
    @Bean
    RestTemplate restTemplate() {
        return new RestTemplate();
    }
	public static void main(String[] args) {
		SpringApplication.run(EurekaconsumerApplication.class, args);
	}
}
添加控制器类 被hystrix保护的类必须添加@HystrixCommand注解

package cn.et;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import com.netflix.appinfo.InstanceInfo;
import com.netflix.discovery.EurekaClient;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;

@RestController
public class TestController {	
	@Autowired
        private RestTemplate restTemplate;

	/**
	 * @HystrixCommand用于标识当前的方法 被断路器保护 一旦出现无法调用 自动fallbackMethod回调指定的方法
           该方法的参数和返回值 必须和被保护的方法一致
	 * @return
	 */
	@RequestMapping("invokeServiceBalance")
	@HystrixCommand(fallbackMethod="invokeServiceBalanceFallBack")  
	public String invokeServiceBalance() {
		String uuid=restTemplate.getForObject("http://IDSERVER/getId",String.class);
		return uuid;
	}
	public String invokeServiceBalanceFallBack() {
		return "NOUUID";
	}
}


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

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

添加maven依赖

<dependency>
		  <groupId>org.springframework.boot</groupId>
		  <artifactId>spring-boot-starter-actuator</artifactId>
		</dependency>
先访问一次 http://localhost:8888/invokeServiceBalance 发现正常输出uuid

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

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源代码 发现

@Configuration
	@ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })
	protected static class HystrixFeignConfiguration {
		@Bean
		@Scope("prototype")
		@ConditionalOnMissingBean//如果设置了feign.hystrix.enabled 创建HystrixFeign.Builder
		@ConditionalOnProperty(name = "feign.hystrix.enabled", matchIfMissing = false)  
		public Feign.Builder feignHystrixBuilder() {
			return HystrixFeign.builder();
		}
	}
如果希望不使用Hystrix 可以在feign指定的配置类中添加 返回 Feign.Builder

@Configuration
public class FooConfiguration {
    @Bean
	@Scope("prototype")
	public Feign.Builder feignBuilder() {
		return Feign.builder();
	}
}
1》引入feign-hystrix maven依赖
 同ribbon引入hystrix即可

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

 main类添加

@EnableCircuitBreaker
 TestController类无需修改 依然两个调用

/**  
     * 使用Feign调用client的get方式  
     * @return  
     */  
    @RequestMapping("getUser")  
    public User getUser(String id) {  
        User user=client.getId(id);  
        return user;  
    }  
    /**  
     * 使用Feign调用client的post方法  
     * @return  
     */  
    @RequestMapping("saveUser")  
    public User saveUser(User user) {  
        User muser=client.saveUser(user);  
        return muser;  
    }   


修改客户端类 TestClient

package cn.feign;

import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
/**
 * fallback指定断路器的回调 该类必须实现 当前的接口
 * @author jiaozi
 *
 */
@FeignClient(name="idserver",fallback=TestClientFallback.class)
public interface TestClient {
	@RequestMapping(method = RequestMethod.GET, value = "/getUser/{id}")
	public User getId(@PathVariable("id") String id) ;
	
	@RequestMapping(method = RequestMethod.POST, value = "/saveUser",consumes="application/json")
	public User saveUser(@RequestBody User user) ;
}
/**
 * 断路器全部返回 空的用户
 * @author jiaozi
 *
 */
class TestClientFallback implements TestClient{

	@Override
	public User getId(String id) {
		User user=new User();
		return user;
	}

	@Override
	public User saveUser(User user) {
		User user1=new User();
		return user1;
	}
	
}
同ribbon测试方式  此时无法启动 说无法找到TestClientFallback实例 

必须在TestClientFallback上添加

@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中添加依赖

<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
		</dependency>
主类上添加

@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发现 

解决的步骤 分析源码:

通过发现注解

@Import(TurbineHttpConfiguration.class)
public @interface EnableTurbine {

}
看到导入了TurbineHttpConfiguration类 该类源码

/*
 * Copyright 2013-2015 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.cloud.netflix.turbine;

import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.cloud.client.actuator.HasFeatures;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.netflix.discovery.EurekaClient;
import com.netflix.turbine.discovery.InstanceDiscovery;
import com.netflix.turbine.streaming.servlet.TurbineStreamServlet;

/**
 * @author Spencer Gibb
 */
@Configuration
@EnableConfigurationProperties
@EnableDiscoveryClient
public class TurbineHttpConfiguration {

	@Bean
	public HasFeatures Feature() {
		return HasFeatures.namedFeature("Turbine (HTTP)", TurbineHttpConfiguration.class);
	}
        //注册了servlet  映射的路径是 /turbin.stream
	@Bean
	public ServletRegistrationBean turbineStreamServlet() {
		return new ServletRegistrationBean(new TurbineStreamServlet(), "/turbine.stream");
	}

	@Bean
	public TurbineProperties turbineProperties() {
		return new TurbineProperties();
	}

	@Bean
	public TurbineLifecycle turbineLifecycle(InstanceDiscovery instanceDiscovery) {
		return new TurbineLifecycle(instanceDiscovery);
	}

	@Configuration
	@ConditionalOnClass(EurekaClient.class)
	protected static class EurekaTurbineConfiguration {

		@Bean
		@ConditionalOnMissingBean
		public InstanceDiscovery instanceDiscovery(TurbineProperties turbineProperties, EurekaClient eurekaClient) {
			return new EurekaInstanceDiscovery(turbineProperties, eurekaClient);
		}

	}

	@Configuration
	@ConditionalOnMissingClass("com.netflix.discovery.EurekaClient")
	protected static class DiscoveryClientTurbineConfiguration {
                //通过配置和eureka去发现 指定需要检测的服务
		@Bean
		@ConditionalOnMissingBean
		public InstanceDiscovery instanceDiscovery(TurbineProperties turbineProperties, DiscoveryClient discoveryClient) {
			return new CommonsInstanceDiscovery(turbineProperties, discoveryClient);
		}
	}
}

该类 CommonsInstanceDiscovery 中

@Override
	public Collection<Instance> getInstanceList() throws Exception {
		List<Instance> instances = new ArrayList<>();
		List<String> appNames = getTurbineProperties().getAppConfigList();
		if (appNames == null || appNames.size() == 0) {
			log.info("No apps configured, returning an empty instance list");
			return instances;
		}
		log.info("Fetching instance list for apps: " + appNames);
		for (String appName : appNames) {
			try {//所有配置的appname通过 eurekaClient搜索实例 发现application.properteis配置的实例都无法搜索到
				instances.addAll(getInstancesForApp(appName)); 
			}
			catch (Exception ex) {
				log.error("Failed to fetch instances for app: " + appName
						+ ", retrying once more", ex);
				try {
					instances.addAll(getInstancesForApp(appName));
				}
				catch (Exception retryException) {
					log.error("Failed again to fetch instances for app: " + appName
							+ ", giving up", ex);
				}
			}
		}
		return instances;
	}
getInstancesForApp 最后一行是发现服务 如果myclient是使用application.properties返回集合为空 如果使用 application.yml 成功返回  但是在eurekaserver都可以看到  
protected List<Instance> getInstancesForApp(String serviceId) throws Exception {
		List<Instance> instances = new ArrayList<>();
		log.info("Fetching instances for app: " + serviceId);
		List<ServiceInstance> serviceInstances = discoveryClient.getInstances(serviceId);

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

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

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

server:
    port: 8878
#spring.cloud.service-registry.auto-registration.enabled=true
spring:
    application:
        name: myclient 
#eureka.client.healthcheck.enabled=true
#eureka.client.fetch-registry=true
#eureka.client.registryFetchIntervalSeconds=5  
eureka:
    client:
        serviceUrl:
            defaultZone: http://jiaozi:jiaozi123@mymaster:8761/eureka/

IDSERVER:
    ribbon:
        NFLoadBalancerRuleClassName: cn.et1.MyRule
logging:
    level:
        cn.feign.TestClient2: DEBUG
feign:
    hystrix:
        enabled: true
2 配置turbine
 添加maven项目添加turbine和eureka依赖 因为tubine项目也会被注册发现

<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-eureka</artifactId> 
		</dependency> 
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-turbine</artifactId>
		</dependency>
		
	<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
		  <groupId>org.springframework.boot</groupId>
		  <artifactId>spring-boot-starter-actuator</artifactId>
		</dependency> 

 添加主类 添加注解  @EnableTurbine

package cn.et;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.turbine.EnableTurbine;
@SpringBootApplication
@EnableTurbine
public class TurbineApplication {

	public static void main(String[] args) {
		SpringApplication.run(TurbineApplication.class, args);
	}
}
application.yml配置文件

server:
  port: 8899
spring:
  application:
    name: turbine
eureka:
  client:
    serviceUrl:
      defaultZone: http://jiaozi:jiaozi123@mymaster:8761/eureka/
  instance:
    hostname: mymaster  #可以通过修改 c:/windows/system32/drivers/etc/hosts文件
    preferIpAddress: false  #未配置hostname时 使用本机ip地址作为主机名 ipAddress是ip地址
turbine:
  aggregator:
    clusterConfig: MYCLIENT  #集群的名字 是serviceid的大小 turbine.stream访问时  turbine.stream?cluster=MYCLIENT获取监听到的数据
  appConfig: myclient   #需要被监控的serviceid

启动 turbine

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

http://localhost:8888/saveUser?userName=aa&password=vv
http://localhost:8888/getUser?id=123
http://localhost:8878/saveUser?userName=aa&password=vv
http://localhost:8878/getUser?id=123

访问 turbine暴露的地址

http://localhost:8899/turbine.stream?cluster=MYCLIENT
将该地址 粘贴到 仪表盘 地址中
http://localhost:8888/hystrix 打开仪表盘

2 监听多个集群

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

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

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

turbine:
  aggregator:
    clusterConfig: default
  appConfig: myclient,myclient1
  clusterNameExpression: "'default'"

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





原文地址:https://www.cnblogs.com/liaomin416100569/p/9331186.html