Spring Cloud中使用Hystrix(十五)

Spring Cloud整合Hystrix

首先还是从Spring Cloud服务管理框架Eureka简单示例(三)这篇博客的底部拿到我们的Eureka简单集群代码,改写eureka-consumer项目,在com.init.springCloud包下添加PersonService类,这个类其实就是原本ConsumerController控制器里面请求方法的抽取:

package com.init.springCloud;

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

@Service
public class PersonService {
	
	@Autowired
	private RestTemplate restTemplate;

	public Person getPersonById(Integer id){
		return restTemplate.getForObject("http://eureka-provider/search/{id}", Person.class, id);
	};
	
}

方法中需要返回Person实体类,从eureka-provider项目中拷贝过来(lombok需要自己添加依赖,也从eureka-provider项目的pom.xml文件中拷贝);还需要需用RestTemplate,我们在这个实体类作为Spring组件注册到Spring中,所以在eureka-consumer项目的ConsumerApp中添加这个bean组件:

@Bean
@LoadBalanced
public RestTemplate getRestTemplate(){
	return new RestTemplate();
}

之后改写ConsumerController控制器,引入PersonService:

package com.init.springCloud;

import org.springframework.beans.factory.annotation.Autowired;
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
public class ConsumerController {
	
	@Autowired
	private PersonService personService;
	
	@RequestMapping(value = "/router/{id}", method = RequestMethod.GET,
			produces = MediaType.APPLICATION_JSON_VALUE)
	public Person router(@PathVariable Integer id){
		return personService.getPersonById(id);
	}
	
}

之后依次运行三个项目的**App类里面的main方法,启动三个项目。访问:http://localhost:8081/router/1,测试我们改写的方法是否能够正常运行:


到这里,我们完成了在Spring Cloud中使用Hystrix的准备工作。

接下来,先在eureka-consumer项目中添加Spring Cloud整合的Hystrix依赖:

<dependency>
	<groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency> 

之后在ConsumerApp启动类中使用@EnableCircuitBreaker注解开启对Hystrix的开关:

package com.init.springCloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
public class ConsumerApp {
	
	@Bean
	@LoadBalanced
	public RestTemplate getRestTemplate(){
		return new RestTemplate();
	}

	public static void main(String[] args) {
		SpringApplication.run(ConsumerApp.class, args);
	}

}

之后在PersonService类的getPersonById()方法上添加@HystrixCommand注解,并且为Hystrix添加一个回退方法:

package com.init.springCloud;

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

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

@Service
public class PersonService {
	
	@Autowired
	private RestTemplate restTemplate;

	@HystrixCommand(fallbackMethod = "getPersonFallback")
	public Person getPersonById(Integer id){
		return restTemplate.getForObject("http://eureka-provider/search/{id}", Person.class, id);
	};
	
	public Person getPersonFallback(Integer id){
		Person person = new Person();
		person.setId(id);
		person.setName("angels");
		return person;
	}
	
}

这个时候,我们停止eureka-provider项目的服务,再次访问:http://localhost:8081/router/1,可以看到请求未成功,走了回退逻辑:


Spring Cloud整合Hystrix就是如此的简单!

Spring Cloud中Hystrix的常用配置

Hystrix的配置可以用注解或yml的形式来做到。譬如我们常用到的groupKey、commandKey、commandProperties、threadPoolProperties等都可以在注解上面完成配置:

@HystrixCommand(fallbackMethod = "getPersonFallback", groupKey = "myGroupKey", 
		commandKey = "myCommandKey",
		commandProperties = {
			@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "500")
		},
		threadPoolProperties = {
			@HystrixProperty(name = "coreSize", value = "5")
		})
注意,在@HystrixProperty属性里,前面三个对作用域进行限制的命令空间得去除。

上面的示例是在方法级上的注解,即使用@HystrixCommand来修饰方法,同样也可以把这个写到整个类上,这个时候使用在类上使用@DefaultProperties注解,就可以完成对整个类里面所有方法的修饰了。只不过不能再配置commandKey,以及使用默认回退(defaultFallback)来供类里面所有方法调用。

同样,我们可以在yml配置文件中配置这些属性,譬如超时等待时间:

hystrix:
  command:
    myCommandKey:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 500

其中,“myCommandKey”对应的是我们单个方法的超时等待时间,改成“default”之后就是全局生效。

更多详细的配置还要参见Spring Cloud官网或者是Netflix在GitHub上托管的代码和文档

缓存

实际上,在上一篇博客里面我们已经做过缓存了,只不过那时是单独使用的Hystrix,我们在这里演示一下如何在Spring Cloud的Hystrix中使用缓存,先在eureka-consumer项目的com.init.springCloud下创建cache包,新建MyFilter类,实现Filter接口:

package com.init.springCloud.cache;

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 MyFilter implements Filter {

	@Override
	public void destroy() {

	}

	@Override
	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {
		HystrixRequestContext context = HystrixRequestContext.initializeContext();//开启一个上下文
		try {
			System.out.println("这里是过滤器");
			chain.doFilter(request, response);
		} catch (Exception e) {
			e.printStackTrace();
		} finally{
			context.shutdown();
		}
	}

	@Override
	public void init(FilterConfig arg0) throws ServletException {

	}

}

之后在启动类ConsumerApp顶上添加@ServletComponentScan注解,让Spring可以扫描到Servlet提供的bean组件,重启eureka-consumer项目,访问:http://localhost:8081/router/1,看看我们的过滤器是否配置成功:


在cache包下新建CacheService类,和PersonService类类似,只不过我们这里不去调用具体的服务了,只打印一下是否请求了这个服务,要使用缓存,需要在方法上面添加@HystrixCommand和@CacheResult注解,两者联合在一起,才能使用缓存:

package com.init.springCloud.cache;

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

import com.init.springCloud.Person;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheResult;

@Service
public class CacheService {

	@Autowired
	private RestTemplate restTemplate;
	
	@CacheResult
	@HystrixCommand
	public Person getCachePerson(Integer id){
		System.out.println("这里是缓存服务");
		return null;
	};
	
}

再创建CacheController控制器,用于调用这个服务,我们在一次请求里,多次调用同一个方法(注意参数不能变化),查看是否在这一个请求上下文里使用了缓存(注意我们在MyFilter里面针对每次请求都给了一个请求上下文):

package com.init.springCloud.cache;

import org.springframework.beans.factory.annotation.Autowired;
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
public class CacheController {

	@Autowired CacheService cacheService;
	
	@RequestMapping(value = "/myCache/{id}", method = RequestMethod.GET,
			produces = MediaType.APPLICATION_JSON_VALUE)
	public String router(@PathVariable Integer id){
		for(int i=0; i < 4; i++){
			System.out.println("这是第"+(i+1)+"次调用");
			cacheService.getCachePerson(id);
		}
		return "success";
	}
	
}

重启eureka-consumer项目,访问:http://localhost:8081/myCache/1,查看控制台输出,针对单次请求中的相同服务调用,确实是使用了缓存的:


接下来测试删除缓存。在CacheService类中添加两个方法,一个用户返回缓存数据,另一个是清除缓存的,注意要使用同一个commandKey来联动:

@CacheResult
@HystrixCommand(commandKey = "myCacheKey")
public String getCache(Integer id){
	System.out.println("===>获取缓存数据");
	return null;
}

@CacheRemove(commandKey = "myCacheKey")
@HystrixCommand
public String clearCache(Integer id){
	System.out.println("===>清除缓存");
	return null;
}

在CacheController控制器中新增测试方法:

@RequestMapping(value = "/clearCache/{id}", method = RequestMethod.GET,
		produces = MediaType.APPLICATION_JSON_VALUE)
public String clearCache(@PathVariable Integer id){
	for(int i=0; i < 4; i++){
		cacheService.getCache(id);
	}
	System.out.println("<<下面执行清除缓存>>");
	cacheService.clearCache(id);
	for(int i=0; i < 4; i++){
		cacheService.getCache(id);
	}
	return "success";
}

重启项目,访问:http://localhost:8081/clearCache/1,查看控制台输出:


请求合并

如果对于同一个接口,在短时间内请求了数次,只是请求的参数略有不同,那我们可以考虑使用请求合并。譬如,同样是查询商品信息,在10ms内发生了30次请求,而每次的请求只是商品的ID不同而已,那么,我们在使用了请求合并以后,就可以把30次请求合并成一次请求,一次性返回结果,而这10ms的延迟影响,用户是不可感知的,但对于服务器来说,就节省了网络的开销,以及减缓了数据库压力。

在eureka-consumer项目的com.init.springCloud包下创建collapser包,新建CollapserService类,先编写一个getPerson()方法,返回一个Future对象,这个方法用于收集请求的参数,具体的处理需要再编写一个getPersons()方法,用于实际业务处理。然后需要再请求合并器上添加@HystrixCollapser注解,同时设置批处理的具体方法(这里还设置了合并10ms内的请求),批处理的具体方法用于执行命令,添加@HystrixCommand注解:

package com.init.springCloud.collapser;

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

import org.springframework.stereotype.Service;

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

@Service
public class CollapserService {

	@HystrixCollapser(batchMethod = "getPersons", 
			collapserProperties = {
				@HystrixProperty(name = "timerDelayInMilliseconds", value = "10")
			})
	public Future<Person> getPerson(Integer id){
		System.out.println("进入请求收集方法");
		return null;
	}
	
	@HystrixCommand
	public List<Person> getPersons(List<Integer> ids){
		List<Person> persons = new ArrayList<Person>(ids.size());
		System.out.println("====>这是某一次的请求");
		for (Integer id : ids) {
			System.out.println("用户ID:"+id);
			Person person = new Person();
			person.setId(id);
			person.setName("spirit"+id);
			persons.add(person);
		}
		return persons;
	}
	
}

之后创建一个CollapserController控制器,多次请求我们编写的合并器:

package com.init.springCloud.collapser;

import java.util.concurrent.Future;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import com.init.springCloud.Person;

@RestController
public class CollapserController {
	
	@Autowired
	private CollapserService collapserService;
	
	@RequestMapping(value = "/collapser", method = RequestMethod.GET, 
			produces = MediaType.APPLICATION_JSON_VALUE)
	public String getPerson() throws Exception{
		Future<Person> f1 = collapserService.getPerson(1);
		Future<Person> f2 = collapserService.getPerson(2);
		Future<Person> f3 = collapserService.getPerson(3);
		
		Person p1 = f1.get();
		Person p2 = f2.get();
		Person p3 = f3.get();
		
		System.out.println(p1);
		System.out.println(p2);
		System.out.println(p3);
		return "success";
	}
	
}

重启eureka-consumer项目,访问:http://localhost:8081/collapser,查看控制台输出:


可以看到,我们虽然请求了同一个方法三次,但是最终是被合并成了一次请求进行处理的。

Spring Cloud中Hystrix和Feign整合使用

首先我们在eureka-consumer项目的pom.xml文件中引入Feign的依赖:

<dependency>
	<groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-feign</artifactId>
</dependency>

之后在ConsumerApp启动类中使用@EnableFeignClients注解,打开Feign客户端的开关。

我们先创建一个Feign客户端的接口,已经对应的Controller控制器,先确保Feign是可以使用的(对于Feign的讲解以及如何在Spring Cloud中使用Feign,可以参考以前的博客,这里就不赘述了)。

在eureka-consumer项目的com.init.springCloud包下创建FeignHystrixSup包,新建PersonClient接口:

package com.init.springCloud.FeignHystrixSup;

import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import com.init.springCloud.Person;

@FeignClient("eureka-provider")
public interface PersonClient {
	
	@RequestMapping(value = "/search/{id}", method = RequestMethod.GET,
			consumes = "application/json")
	public Person getPersonById(@PathVariable("id") Integer id);
	
}

接着创建FeignController控制器,注入上面的接口并提供一个访问方法:

package com.init.springCloud.FeignHystrixSup;

import org.springframework.beans.factory.annotation.Autowired;
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;

import com.init.springCloud.Person;

@RestController
public class FeignController {

	@Autowired
	private PersonClient personClient;
	
	@RequestMapping(value = "/feign/{id}", method = RequestMethod.GET, 
			produces = MediaType.APPLICATION_JSON_VALUE)
	public Person getPersonById(@PathVariable Integer id){
		return personClient.getPersonById(id);
	}
	
}

重启eureka-consumer项目,访问:http://localhost:8081/feign/1,浏览器返回了我们的数据,则Spring中Feign成功整合。

接下来就是需要将Hystrix整合到Feign中,先打开Feign对Hystrix的支持,因为默认是关闭的,在application.yml文件中打开这个开关:

feign:
  hystrix:
    enabled: true

如果PersonClient接口的方法能够正常使用,则不会有异常抛出,在使用了Hystrix之后,如果方法发生异常,就需要寻找一个回退的方法,我们编写一个PersonClientFallback类,去实现上面的PersonClient接口,PersonClientFallback实现类里面的实现方法对应的就是回退方法:

package com.init.springCloud.FeignHystrixSup;

import org.springframework.context.annotation.Configuration;

import com.init.springCloud.Person;

@Configuration
public class PersonClientFallback implements PersonClient {

	@Override
	public Person getPersonById(Integer id) {
		Person person = new Person();
		person.setId(id);
		person.setName("angels");
		return person;
	}

}

接着,在@FeignClient注解里面配置这个回退的类:

@FeignClient(name = "eureka-provider", fallback = PersonClientFallback.class)

注意,PersonClientFallback需要放到Spring容器里面,Feign才能找到这个类。

之后,停止eureka-provider项目,再次访问:http://localhost:8081/feign/1,浏览器就返回了我们回退方法的JSON字符串:


Hystrix的监控功能

要使用Hystrix的监控功能,需要引入Spring Boot中的Actuator,我们在eureka-consumer项目的pom.xml中引入相关依赖,之后重启eureka-consumer项目:

<!-- Hystrix监控功能 -->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-actuator</artifactId>
	<version>1.5.3.RELEASE</version>
</dependency>

由于我们监控的是eureka-consumer项目,所以是需要把Actuator的依赖项加入到这个项目中的。接下来创建一个简单java的maven新项目hystrix-dashboard,同样去引入Actuator的依赖,同时引入Hystrix的监控依赖,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.init.springcloud</groupId>
	<artifactId>hystrix-dashboard</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<build />

	<dependencyManagement>
	    <dependencies>
	        <dependency>
	            <groupId>org.springframework.cloud</groupId>
	            <artifactId>spring-cloud-dependencies</artifactId>
	            <version>Dalston.SR5</version>
	            <type>pom</type>
	            <scope>import</scope>
	        </dependency>
	    </dependencies>
	</dependencyManagement>
	<dependencies>
		<!-- Hystrix监控功能 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-actuator</artifactId>
			<version>1.5.9.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
		</dependency>
	</dependencies>

</project>

创建com.init.springCloud包,新建DashboardApp启动类,开启Hystrix监控:

package com.init.springCloud;

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

@SpringBootApplication
@EnableHystrixDashboard
public class DashboardApp {

	public static void main(String[] args) {
		SpringApplication.run(DashboardApp.class, args);
	}
	
}

启动hystrix-dashboard项目,我们测试一下Hystrix提供出来的其中一个Endpoints(端点):健康端点(/health),首先访问:http://localhost:8081/hystrix.stream,这是被监控客户端向监控端推送的stream流,浏览器会不停地打印推送信息:


我们接着访问:http://localhost:8082/hystrix,进入Hystrix控制台管理界面


在第一个url地址栏里填入我们上面那个推流地址:http://localhost:8081/hystrix.stream,“Delay”延迟时间可以用默认的2秒,“Title”监控的标题可以随便起一个,之后进入监控端页面:


我们在被监控那端发起一个请求,譬如:http://localhost:8081/feign/1,那监控界面就会展示出这些信息:


上图展示了我们请求的是哪一个方法,请求了几次,成功率是多少,断路器是否开启等等的信息。

Spring Cloud中使用Hystrix的知识就讲到这里了。

源码点击这里

猜你喜欢

转载自blog.csdn.net/mrspirit/article/details/80434058
今日推荐