springcloud:集群保护框架Hystrix特性、工作原理以及整合流程

微服务集群中,服务端和客户端都支持集群部署。而庞大的集群中,部分服务出现问题不可避免,如何减少故障的影响以及确保集群高可用,是一个需要解决的问题。Hystrix就是Spring Cloud提供的一个集群保护框架。

Hystrix 是一个延迟和容错库,旨在隔离远程系统、服务和第三方库的访问点,停止级联故障,并在故障不可避免的复杂分布式系统中实现弹性。Hystrix通过添加延迟阈值和容错逻辑,帮助控制分布式系统组建的交互。

一、实现的功能
1、当所依赖的网络服务发生延迟或者失败时,对访问的客户端程序进行保护。如下图基础服务无法访问,则立即执行回退逻辑。

2、在分布式系统中,停止级联故障。

3、网络恢复后,可以快速恢复客户端的访问能力。

4、调用失败时执行服务回退。

5、可以支持实时监控、报警以及其他操作。

二、运行流程


第一步:在命令开始执行时,会准备一些工作,比如命令会创建相应的线程池。
第二步:判断是否打开了缓存,打开缓存的情况会直接查找缓存并返回结果。
第三步:判断断路器是否打开,打开的话说明链路不可用,直接执行回退,响应用户请求。
第四步:判断线程池、信号量(计数器)等条件,如线程池超负荷则直接执行回退方法,否则就去执行命令的内容。
第五步:执行命令,计算是否要对断路器进行处理。执行完毕后,如果满足一定的条件,则需求开启断路器。如果执行成功,则返回结果,否则执行回退。

 整个Hystrix运行流程中,断路器开启状态是最核心的。

三、使用要点

1、命令属性配置

超时配置

命令组名称(GroupKey),必须配置。默认情况下,全局维护的线程池Map作为该值的key,该Map的value为执行命令的线程池。

命令名称(CommanKey),可选配置。

线程池名称(ThreadPoolKey),指定线程池的key后,全局维护的线程池Map将作为该值的Key。

2、回退

触发回退的三种情况:
   a、断路器被打开

   b、线程池、信号量满载

   c、实际执行失败

满足三种条件之一即可进行回退。执行父类HystrixCommand的getFallback方法即可实现回退。

回退模式

回退机制灵活,特别注意的是,如果A和B两个命令时在一个事务内的,在控制类中调用回退方法,A和B不能有回退方法,不然就不能执行控制类的回退方法了。

断路器开启
如果断路器开启了,将不再执行命令,也不更新链路的健康状况,在一段时间内一直在回退。
触发断路器开启的需要同时满足两个条件:
     第一个:整个链路达到一定阈值。默认10秒内产生20个请求,则符合该条件。

     第二个:达到第一个条件的同时,如果请求失败的百分比大于阈值,则开启断路器。失败百分比默认为50%。

断路器关闭

断路器打开后,在一段时间内(默认为5秒),命令不在执行而是在不断触发回退,这段时间被称为休眠期。

休眠期结束后,此时断路器处于半开状态(非开启也非关闭),hystrix会尝试执行一次命令,如果命令执行成功,那么将关闭断路器并清空链路的健康信息,如果执行失败,则断路器依旧保持打开的状态。

隔离机制
除了断路器要处于关闭状态外,命令真正执行还需要考虑命令的线程池或者信号量是否满载。如果满载则不会执行命令,而是触发回退,从机制上控制了命令的执行,实现了错误的隔离。

隔离策略

THRED:默认策略,有线程池来决定是否执行命令,如果线程池满载,则不会执行命令。默认线程池大小为10。
SEMAPHORE:由信号量来决定命令的执行。当请求并发数高于阈值时,不再执行命令。默认最大并发数为10.
信号量策略开销小,但是不支持超时和异步,一般不建议使用。

合并请求
在线程隔离策略下,可以将一段时间内的同类请求(URL一致,参数不同)收集到一个命令中执行。

这样做的好处:节省线程开销、减少网络连接。

合并请求的三个条件:

a、需要一个执行请求的命令,将全部的参数进行整理,然后调用外部服务。
b、需求一个合并处理器,用于收集请求,以及处理结果
c、外部接口提供支持。(提供批量查询等接口)


import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.Future;

import com.netflix.config.ConfigurationManager;
import com.netflix.hystrix.HystrixCollapser;
import com.netflix.hystrix.HystrixCollapser.CollapsedRequest;
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext;

public class CollapseTest {

	public static void main(String[] args) throws Exception {
		// 收集 1 秒内发生的请求,合并为一个命令执行
		ConfigurationManager.getConfigInstance().setProperty(
				"hystrix.collapser.default.timerDelayInMilliseconds", 1000);
		// 请求上下文
		HystrixRequestContext context = HystrixRequestContext
				.initializeContext();
		// 创建请求合并处理器
		MyHystrixCollapser c1 = new MyHystrixCollapser("Angus");
		MyHystrixCollapser c2 = new MyHystrixCollapser("Crazyit");
		MyHystrixCollapser c3 = new MyHystrixCollapser("Sune");
		MyHystrixCollapser c4 = new MyHystrixCollapser("Paris");
		// 异步执行
		Future<Person> f1 = c1.queue();
		Future<Person> f2 = c2.queue();
		Future<Person> f3 = c3.queue();
		Future<Person> f4 = c4.queue();
		System.out.println(f1.get());
		System.out.println(f2.get());
		System.out.println(f3.get());
		System.out.println(f4.get());
		context.shutdown();
	}

	/**
	 * 合并执行的命令类
	 */
	static class CollapserCommand extends HystrixCommand<Map<String, Person>> {
		// 请求集合,第一个类型是单个请求返回的数据类型,第二是请求参数的类型
		Collection<CollapsedRequest<Person, String>> requests;

		private CollapserCommand(
				Collection<CollapsedRequest<Person, String>> requests) {
			super(Setter.withGroupKey(HystrixCommandGroupKey.Factory
					.asKey("ExampleGroup")));
			this.requests = requests;
		}

		@Override
		protected Map<String, Person> run() throws Exception {
			System.out.println("收集参数后执行命令,参数数量:" + requests.size());
			// 处理参数
			List<String> personNames = new ArrayList<String>();
			for(CollapsedRequest<Person, String> request : requests) {
				personNames.add(request.getArgument());
			}
			// 调用服务(此处模拟调用),根据名称获取Person的Map
			Map<String, Person> result = callService(personNames);
			return result;
		}
		
		// 模拟服务返回
		private Map<String, Person> callService(List<String> personNames) {
			Map<String, Person> result = new HashMap<String, Person>();
			for(String personName : personNames) {
				Person p = new Person();
				p.id = UUID.randomUUID().toString();
				p.name = personName;
				p.age = new Random().nextInt(30);
				result.put(personName, p);
			}
			return result;
		}
	}

	static class Person {
		String id;
		String name;
		Integer age;

		public String toString() {
			// TODO Auto-generated method stub
			return "id: " + id + ", name: " + name + ", age: " + age;
		}
	}

	/**
	 * 合并处理器
	 * 第一个类型为批处理返回的结果类型
	 * 第二个为单请求返回的结果类型
	 * 第三个是请求参数类型
	 */
	static class MyHystrixCollapser extends
			HystrixCollapser<Map<String, Person>, Person, String> {

		String personName;

		public MyHystrixCollapser(String personName) {
			this.personName = personName;
		}

		@Override
		public String getRequestArgument() {
			return personName;
		}

		@Override
		protected HystrixCommand<Map<String, Person>> createCommand(
				Collection<CollapsedRequest<Person, String>> requests) {
			return new CollapserCommand(requests);
		}

		@Override
		protected void mapResponseToRequests(Map<String, Person> batchResponse,
				Collection<CollapsedRequest<Person, String>> requests) {
			// 让结果与请求进行关联
			for (CollapsedRequest<Person, String> request : requests) {
				// 获取单个响应返回的结果
				Person singleResult = batchResponse.get(request.getArgument());
				// 关联到请求中
				request.setResponse(singleResult);
			}
		}
	}

}

其中,CollapserCommand是命令内部类,MyHystrixCollapser是合并处理器。


请求缓存
针对同一次请求中,多个地方调用同一个接口的情况,可以考虑使用缓存。

缓存被打开后,下次命令不会执行,而是直接从缓存中获取响应并返回。

开启:在命令中重写父类的getCacheKey方法即可。

合并请求、请求缓存需要在一次请求过程中才能实现,因此需要先初始化请求上下文。

四、单独使用Hystrix

1、服务提供方

import javax.servlet.http.HttpServletRequest;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MyController {

	@GetMapping("/normalHello")
	@ResponseBody
	public String normalHello(HttpServletRequest request) {
		return "Hello World";
	}
	
	@GetMapping("/errorHello")
	@ResponseBody
	public String errorHello(HttpServletRequest request) throws Exception {
		// 模拟需要处理10秒
		Thread.sleep(10000);
		return "Error Hello World";
	}
}

2、服务调用方

第一步:添加hystrix依赖

        <dependency>
		   <groupId>com.netflix.hystrix</groupId>
			<artifactId>hystrix-core</artifactId>
			<version>1.5.12</version>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<version>1.7.25</version>
			<artifactId>slf4j-log4j12</artifactId>
		</dependency>
		<dependency>
			<groupId>commons-logging</groupId>
			<artifactId>commons-logging</artifactId>
			<version>1.2</version>
		</dependency>
		<dependency>
			<groupId>org.apache.httpcomponents</groupId>
			<artifactId>httpclient</artifactId>
			<version>4.5.2</version>
		</dependency>

第二步:编写命令类,需要继承HystrixCommand

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 com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;

/**
 * 调用一个正常的服务
 *
 */
public class HelloCommand extends HystrixCommand<String> {

	private String url;
	
	CloseableHttpClient httpclient;
	
	public HelloCommand(String url) {
		// 调用父类的构造器,设置命令组的key,默认用来作为线程池的key
		super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
		// 创建HttpClient客户端
		this.httpclient = HttpClients.createDefault();
		this.url = url;
	}
	
	protected String run() throws Exception {
		try {
			// 调用 GET 方法请求服务
			HttpGet httpget = new HttpGet(url);
			// 得到服务响应
			HttpResponse response = httpclient.execute(httpget);
			// 解析并返回命令执行结果
			return EntityUtils.toString(response.getEntity());
		} catch (Exception e) {
			e.printStackTrace();
		}
		return "";
	}

	protected String getFallback() {
		System.out.println("执行 HelloCommand 的回退方法");
		return "error";
	}
	
}

第三步:编写调用类

public class HelloMain {

	public static void main(String[] args) {
		// 请求正常的服务
		String url = "http://localhost:8080/normalHello";
		HelloCommand command = new HelloCommand(url );
		String result = command.execute();
		System.out.println("请求正常的服务,结果:" + result);

        
		// 请求异常的服务
		String url = "http://localhost:8080/errorHello";
		command  = new HelloCommand(url );
		result = command.execute();
		System.out.println("请求异常的服务,结果:" + result);
	}
}

五、在Spring Cloud中使用Hystrix

1、整合Hystrix流程(服务调用方)

第一步:添加Hystrix依赖,添加eureka、ribbon以及hystrix

        <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>

第二步:在启动类中,加入启用断路器的注解



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
public class InvokerApplication {

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

第三步:编写服务类



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";
	}
	
	
}

第四步:编写控制器

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 "";
	}
}

 

2、常用注解

缓存注解:在使用@CacheResult和@HystrixCommand共同注释服务方法,如下:

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

合并请求注解:使用@HystrixCollapser修饰方法,如下图:在一秒内(collapserProperties属性指定)收集到的请求,交由指定了方法getPersons(batchMethod属性指定)进行批处理。


@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;
	}
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值