SpringCloud 系列 --5.SpringCloud-Hystrix

本贴最后更新于 1492 天前,其中的信息可能已经沧海桑田

#搭建 Eureka 服务
pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>org.crazyit.cloud</groupId>
	<artifactId>first-hystrix-server</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
			<version>1.5.4.RELEASE</version>
		</dependency>
	</dependencies>	
	
</project>

applicaiton.yml

server:
  port: 8761
eureka:
  client:
    registerWithEureka: false
    fetchRegistry: false
logging:
  level:
    com.netflix: INFO

启动类

package org.crazyit.cloud;

import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer
public class ServerApplication {

	public static void main(String[] args) {
		new SpringApplicationBuilder(ServerApplication.class).run(args);
	}

}

搭建 provider

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>org.crazyit.cloud</groupId>
	<artifactId>spring-hystrix-provider</artifactId>
	<version>0.0.1-SNAPSHOT</version>

	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>Dalston.SR1</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>

	<dependencies>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-config</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-eureka</artifactId>
		</dependency>
	</dependencies>

</project>

application.yml

spring:
  application:
    name: spring-hystrix-provider
eureka:
  instance:
    hostname: localhost
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/

实体类 Person

package org.crazyit.cloud;

public class Person {

	private Integer id;
	
	private String name;

	private Integer age;
	
	private String message;
	
	public Integer getId() {
		return id;
	}

	public void setId(Integer id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Integer getAge() {
		return age;
	}

	public void setAge(Integer age) {
		this.age = age;
	}

	public String getMessage() {
		return message;
	}

	public void setMessage(String message) {
		this.message = message;
	}
	
	
}

启动类

package org.crazyit.cloud;

import java.util.Scanner;

import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@SpringBootApplication
@EnableEurekaClient
public class ProviderApplication {

	public static void main(String[] args) {
		// 设置启动的服务器端口
		new SpringApplicationBuilder(ProviderApplication.class).properties(
				"server.port=8080").run(args);
	}
}

Controller 类

package org.crazyit.cloud;


import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.UUID;

import javax.servlet.http.HttpServletRequest;

import org.springframework.http.MediaType;
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;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class PersonController {
	
	@RequestMapping(value = "/person/{personId}", method = RequestMethod.GET, 
			produces = MediaType.APPLICATION_JSON_VALUE)
	public Person findPerson(@PathVariable("personId") Integer personId, HttpServletRequest request) {
		Person person = new Person();
		person.setId(personId);
		person.setName("Crazyit");
		person.setAge(33);
		person.setMessage(request.getRequestURL().toString());		
		return person;
	}
	
	@RequestMapping(value = "/hello", method = RequestMethod.GET)
	public String hello() throws Exception {
		Thread.sleep(800);
		return "Hello World";
	}
	
	@RequestMapping(value = "/persons", method = RequestMethod.GET, 
			produces = MediaType.APPLICATION_JSON_VALUE)
	public List<Person> findPersons(@RequestBody List<Integer> personIds, HttpServletRequest request) {
		List<Person> result = new ArrayList<Person>();
		for(Integer id : personIds) {
			Person person = new Person();
			person.setId(id);
			person.setName("angus");
			person.setAge(new Random().nextInt(30));
			person.setMessage(request.getRequestURL().toString());
			result.add(person);
		}
		return result;
	}
}

搭建调用方

pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>org.crazyit.cloud</groupId>
	<artifactId>spring-hystrix-invoker</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	
	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>Dalston.SR1</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>

	<dependencies>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-config</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-eureka</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-ribbon</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-hystrix</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-feign</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-actuator</artifactId>
			<version>1.5.3.RELEASE</version>
		</dependency>
		 <dependency>
			<groupId>org.apache.httpcomponents</groupId>
			<artifactId>httpclient</artifactId>
			<version>4.5.2</version>
		</dependency>
	</dependencies>
	
</project>
application.yml
server:
  port: 9000
spring:
  application:
    name: spring-hystrix-invoker
eureka:
  instance:
    hostname: localhost
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
feign:
  hystrix:
    enabled: true
hystrix:
  command:
    HelloClient#hello():
      execution:
        isolation:
          thread: 
            timeoutInMilliseconds: 500
      circuitBreaker:
        requestVolumeThreshold: 3
InvokerApplication 在启动类上面加入断路器注解 @EnableCircuitBreaker
package org.crazyit.cloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
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.feign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
@ServletComponentScan
@EnableFeignClients
public class InvokerApplication {

	@LoadBalanced
	@Bean
	public RestTemplate getRestTemplate() {
		return new RestTemplate();
	}
	
	public static void main(String[] args) {
		SpringApplication.run(InvokerApplication.class, args);
	}
}

InvokerController
package org.crazyit.cloud;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Future;

import org.crazyit.cloud.cache.CacheService;
import org.crazyit.cloud.collapse.CollapseService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
@Configuration
public class InvokerController {

	@Autowired
	private PersonService personService;
	
	@Autowired
	private CacheService cacheService;
	
	@Autowired
	private CollapseService collapseService;

	@RequestMapping(value = "/router/{personId}", method = RequestMethod.GET, 
			produces = MediaType.APPLICATION_JSON_VALUE)
	public Person router(@PathVariable Integer personId) {
		Person p = personService.getPerson(personId);
		return p;
	}
	
	@RequestMapping(value = "/testConfig", method = RequestMethod.GET)
	public String testConfig() {
		String result = personService.testConfig();
		return result;
	}
	
	@RequestMapping(value = "/testException", method = RequestMethod.GET)
	public String testException() {
		String result = personService.testException();
		return result;
	}
	
	@RequestMapping(value = "/cache1/{personId}", method = RequestMethod.GET, 
			produces = MediaType.APPLICATION_JSON_VALUE)
	public Person testCacheResult(@PathVariable Integer personId) {
		// 调用多次服务
		for(int i = 0; i < 3; i++) {
			Person p = cacheService.getPerson(personId);
			System.out.println("控制器调用服务 " + i);
		}
		return new Person();
	}
	
	@RequestMapping(value = "/cache2", method = RequestMethod.GET, 
			produces = MediaType.APPLICATION_JSON_VALUE)
	public String testCacheRemove() {
		for(int i = 0; i < 3; i++) {
			cacheService.cacheMethod("a");
			System.out.println("控制器调用服务 " + i);
		}
		// 清空缓存
		cacheService.updateMethod("a");		
		System.out.println("==========  清空了缓存");
		// 再执行多次
		for(int i = 0; i < 3; i++) {
			cacheService.cacheMethod("a");
			System.out.println("控制器调用服务 " + i);
		}		
		return "";
	}
	
	@RequestMapping(value = "/collapse", method = RequestMethod.GET, 
			produces = MediaType.APPLICATION_JSON_VALUE)
	public String testCollapse() throws Exception {
		// 连续执行3次请求
		Future<Person> f1 = collapseService.getSinglePerson(1);
		Future<Person> f2 = collapseService.getSinglePerson(2);
		Future<Person> f3 = collapseService.getSinglePerson(3);
		Person p1 = f1.get();
		Person p2 = f2.get();
		Person p3 = f3.get();
		System.out.println(p1.getId() + "---" + p1.getName());
		System.out.println(p2.getId() + "---" + p2.getName());
		System.out.println(p3.getId() + "---" + p3.getName());		
		return "";
	}
}

Person
package org.crazyit.cloud;

public class Person {

	Integer id;
	String name;
	int age;
	String message;
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public String getMessage() {
		return message;
	}
	public void setMessage(String message) {
		this.message = message;
	}
	
}

PersonService
package org.crazyit.cloud;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;

@Component
public class PersonService {

	@Autowired
	private RestTemplate restTemplate;


	@HystrixCommand(fallbackMethod = "getPersonFallback")
	public Person getPerson(Integer id) {
		// 使用RestTemplate调用Eureka服务
		Person p = restTemplate.getForObject(
				"http://spring-hystrix-provider/person/{personId}",
				Person.class, id);
		return p;
	}

	/**
	 * 回退方法,返回一个默认的Person
	 */
	public Person getPersonFallback(Integer id) {
		Person p = new Person();
		p.setId(0);
		p.setName("Crazyit");
		p.setAge(-1);
		p.setMessage("request error");
		return p;
	}
	
	/**
	 * 测试配置,对3个key进行命名
	 * 设置命令执行超时时间为1000毫秒
	 * 设置命令执行的线程池大小为1
	 */
	@HystrixCommand(
			fallbackMethod="testConfigFallback", groupKey="MyGroup", 
			commandKey="MyCommandKey", threadPoolKey="MyCommandPool", 
			commandProperties={
					@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", 
							value = "1000")
			}, 
			threadPoolProperties={
					@HystrixProperty(name = "coreSize", 
							value = "1")
			})
	public String testConfig() {
		try {
			Thread.sleep(500);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return "ok";
	}
	
	public String testConfigFallback() {
		return "error";
	}
	
	/**
	 * 声明了忽略MyException,如果方法抛出MyException,则不会触发回退
	 */
	@HystrixCommand(ignoreExceptions = {MyException.class}, 
			fallbackMethod="testExceptionFallBack")
	public String testException() {
		throw new MyException();
	}
	
	public String testExceptionFallBack() {
		return "error";
	}
	
	
}

HystrixFilter
package org.crazyit.cloud;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;

import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext;

@WebFilter(urlPatterns = "/*", filterName = "hystrixFilter")
public class HystrixFilter implements Filter {

	public void init(FilterConfig filterConfig) throws ServletException {
	}

	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {
		HystrixRequestContext context = HystrixRequestContext
				.initializeContext();
		try {
			chain.doFilter(request, response);
		} finally {
			context.shutdown();
		}
	}

	public void destroy() {
	}
}

CacheService
package org.crazyit.cloud.cache;

import org.crazyit.cloud.Person;
import org.springframework.stereotype.Component;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheRemove;
import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheResult;

@Component
public class CacheService {

	@CacheResult
	@HystrixCommand
	public Person getPerson(Integer id) {
		System.out.println("执行 getPerson 方法");
		Person p = new Person();
		p.setId(id);
		p.setName("angus");
		return p;
	}

	/**
	 * 测试删除缓存
	 * 
	 * @param name
	 * @return
	 */
	@CacheResult()
	@HystrixCommand(commandKey = "removeKey")
	public String cacheMethod(String name) {
		System.out.println("执行命令");
		return "hello";
	}

	@CacheRemove(commandKey = "removeKey")
	@HystrixCommand
	public String updateMethod(String name) {
		return "update";
	}
}

CollapseService
package org.crazyit.cloud.collapse;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Future;

import org.crazyit.cloud.Person;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCollapser;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;

@Component
public class CollapseService {

	// 配置收集1秒内的请求
	@HystrixCollapser(batchMethod = "getPersons", collapserProperties = 
		{ 
			@HystrixProperty(name = "timerDelayInMilliseconds", value = "1000") 
		}
	)
	public Future<Person> getSinglePerson(Integer id) {
		System.out.println("执行单个获取的方法");
		return null;
	}

	@HystrixCommand
	public List<Person> getPersons(List<Integer> ids) {
		System.out.println("收集请求,参数数量:" + ids.size());
		List<Person> ps = new ArrayList<Person>();
		for (Integer id : ids) {
			Person p = new Person();
			p.setId(id);
			p.setName("crazyit");
			ps.add(p);
		}
		return ps;
	}
}

feign 相关
HelloClient
package org.crazyit.cloud.feign;

import org.crazyit.cloud.feign.HelloClient.HelloClientFallback;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import feign.Feign;


@FeignClient(name = "spring-hystrix-provider", fallback = HelloClientFallback.class)
public interface HelloClient {

	@RequestMapping(method = RequestMethod.GET, value = "/hello")
	public String hello();

	@Component
	static class HelloClientFallback implements HelloClient {

		public String hello() {
			return "error hello";
		}
	}
}

HelloController
package org.crazyit.cloud.feign;

import java.util.Collection;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import com.netflix.hystrix.HystrixCircuitBreaker;
import com.netflix.hystrix.HystrixCommandKey;
import com.netflix.hystrix.HystrixCommandMetrics;

/**
 * Feign与Hystrix整合
 * 
 * @author 杨恩雄
 *
 */
@RestController
public class HelloController {

	@Autowired
	HelloClient helloClient;

	@RequestMapping(value = "/feign/hello", method = RequestMethod.GET)
	public String feignHello() {
		// hello方法会超时
		String helloResult = helloClient.hello();
		// 获取断路器
		HystrixCircuitBreaker breaker = HystrixCircuitBreaker.Factory
				.getInstance(HystrixCommandKey.Factory
						.asKey("HelloClient#hello()"));		
		System.out.println("断路器状态:" + breaker.isOpen());
		return helloResult;
	}
}

TestFeignClient
package org.crazyit.cloud.feign;

import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.slf4j.LoggerFactory;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;

public class TestFeignClient {
	
	public static void main(String[] args) throws Exception {
		LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
		Logger logger = loggerContext.getLogger("root");
		logger.setLevel(Level.toLevel("INFO"));		
		// 创建默认的HttpClient
		final CloseableHttpClient httpclient = HttpClients.createDefault();		
		// 调用多次服务并输出结果
		for(int i = 0; i < 6; i++) {
			// 建立线程访问接口
			Thread t = new Thread() {
				public void run() {
					try {
						String url = "http://localhost:9000/feign/hello";
						// 调用 GET 方法请求服务
						HttpGet httpget = new HttpGet(url);
						// 获取响应
						HttpResponse response = httpclient.execute(httpget);
						// 根据 响应解析出字符串
						System.out.println(EntityUtils.toString(response.getEntity()));
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			};
			t.start();
		}
		// 等待完成
		Thread.sleep(15000);
	}
}

访问 http://localhost:9000/router/111

image.png

把 provider 服务停止后

image.png

可见 @HystrixCommand 注解的 PersonService.getPerson 方法断路器生效了。

默认配置
 对于一些默认的配置,例如命令组的key等,可以使用@DefaultProperties注解,这样就减少了@HystrixCommand注解的代码量
package org.crazyit.cloud;

import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;

@DefaultProperties(groupKey = "GroupPersonKey")
public class PersonService2 {
   @HystrixCommand //groupKey将使用GroupPersonKey
   public String hello(){
       return "";
   }
}

缓存注解:
 * 缓存与合并请求功能需要先初始化请求上下文才能实现,所有才有了上面的HystrixFilter
 * CacheService中getPerson方法使用注解`@CacheResult`

调用 http://localhost:9000/cache1/22

输出:

执行 getPerson 方法   	
控制器调用服务 0  
控制器调用服务 1  
控制器调用服务 2 

可见 @CacheResult 注解的 getPerson 方法其实只执行了一次,缓存生效。

缓存主要 3 个注解:

  • @CacheResult: 注释方法,标识被注解的方法返回结构将会被缓存,需要与 @HystrixCommand 一起使用
  • @CacheRemove: 用于修饰方法让缓存失效,需要与 @CacheResult 的缓存 key 关联。
  • @CacheKey: 用于修饰方法参数,标识该参数作为缓存的 key.

如上述代码 CacheService 中,先调用 cacheMethod 方法会把结果存储起来,再次调用则不会执行方法,然后调用 updateMethod 方法可以清除对应的缓存,下次再调用 cacheMethod 方法则会执行具体的方法逻辑。

访问 http://localhost:9000/cache2
输出如下:

执行命令  
控制器调用服务 0  
控制器调用服务 1  
控制器调用服务 2  
==========  清空了缓存  
执行命令  
控制器调用服务 0  
控制器调用服务 1  
控制器调用服务 2

注意: 这里说的缓存指的是在一次请求中,如果单独请求多次,则每次都会执行对应的方法。

合并请求注解 见代码 CollapseService

最后真实执行的方法为 getPersons,getSinglePerson 方法使用了 @HystrixCollapser 注解,会收集 1s 内调用 getSinglePerson 的请求,放到 getPersons 方法中进行批处理。

访问 http://localhost:9000/collapse
控制台输出:

收集请求,参数数量:3
1---crazyit
2---crazyit
3---crazyit
Feign 与 Hystrix 整合
<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-feign</artifactId>
		</dependency>
feign:
  hystrix:
    enabled: true

启动类加对应注解:

@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
@ServletComponentScan
@EnableFeignClients

新建 Feign 接口

package org.crazyit.cloud.feign;

import org.crazyit.cloud.feign.HelloClient.HelloClientFallback;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import feign.Feign;


@FeignClient(name = "spring-hystrix-provider", fallback = HelloClientFallback.class)
public interface HelloClient {

	@RequestMapping(method = RequestMethod.GET, value = "/hello")
	public String hello();

	@Component
	static class HelloClientFallback implements HelloClient {

		public String hello() {
			return "error hello";
		}
	}
}

调用服务提供者 spring-hystrix-provider 的 /hello 服务,默认情况下 Hystrix 超时时间为 1s,让服务端睡眠 800 毫秒

@RequestMapping(value = "/hello", method = RequestMethod.GET)
	public String hello() throws Exception {
		Thread.sleep(800);
		return "Hello World";
	}

然后配置 hystrix 超时为 500 毫秒

hystrix:
  command:
    HelloClient#hello():
      execution:
        isolation:
          thread: 
            timeoutInMilliseconds: 500
      circuitBreaker:
        requestVolumeThreshold: 3

如果全局配置超时可以使用
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds
默认时间段内发生的请求数:
hystrix.command.default.circuitBreaker.requestVolumeThreshold
如果针对某个客户端,使用下面的配置片段(CommandKey):
hystrix.command.CommandKey.circuitBreaker.requestVolumeThreshold

Feign 与 Hystrix 整合使用时,会自动帮我们生产 CommandKey,格式为:Feign 客户端接口名#房法名().例如本例中的客户端为 HelloClient,方法为 hello,生成的 CommandKey 为 HelloClient#hello()。默认情况下,生成的 GroupKey 为 @FeignClient 注解的 name 属性。

在 HelloController 中调用 helloClient 的 hello 方法. 由于配置请求超过 3 次并且失败率超过 50% 断路器将会被打开。

运行 TestFeignClient 输出:

断路器状态:false
断路器状态:false
断路器状态:false
断路器状态:false
断路器状态:true
断路器状态:true

可见到第 4 个请求之后断路器被打开了,但是为什么是第四个之后 ,莫非是设置的 requestVolumeThreshold + 1 得到的?(经测试如果设置 requestVolumeThreshold 为 1,则(第二个之后)第三个会输出断路器状态 true).

Hystrix 监控
加入Actuator 
<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-actuator</artifactId>
			<version>1.5.3.RELEASE</version>
		</dependency>

访问 http://localhost:9000/hystrix.stream 会输出 stream 数据
image.png

新建一个监控项目加入依赖:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>org.crazyit.cloud</groupId>
	<artifactId>hystrix-dashboard</artifactId>
	<version>0.0.1-SNAPSHOT</version>

	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>Dalston.SR1</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>

	<dependencies>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-hystrix</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-actuator</artifactId>
			<version>1.5.3.RELEASE</version>
		</dependency>
	</dependencies>

</project>

打开 @EnableHystrixDashboard 注解:

package org.crazyit.cloud;

import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;

@SpringBootApplication
@EnableHystrixDashboard
public class MyApplication {

	public static void main(String[] args) {
		// 设置启动的服务器端口
		new SpringApplicationBuilder(MyApplication.class).properties(
				"server.port=8082").run(args);
	}
}

启动项目访问: http://localhost:8082/hystrix
并输入我们刚才的 stream 地址:
image.png

image.png
可以看到 HelloClient#hello()断路器被打开了。

如果需要监控整个集群的情况,可以使用 Turbine 框架。

  • Spring

    Spring 是一个开源框架,是于 2003 年兴起的一个轻量级的 Java 开发框架,由 Rod Johnson 在其著作《Expert One-On-One J2EE Development and Design》中阐述的部分理念和原型衍生而来。它是为了解决企业应用开发的复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许使用者选择使用哪一个组件,同时为 JavaEE 应用程序开发提供集成的框架。

    940 引用 • 1458 回帖 • 157 关注
  • Hystrix
    7 引用

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...