SpringCloud 系列 --5.Hystrix

本贴最后更新于 1470 天前,其中的信息可能已经天翻地覆

Hystrix 功能:

  • 当所依赖的网络服务发生延迟或者失败时,对访问的客户端程序进行保护。
  • 在分布式系统中,停止级联故障
  • 网络服务恢复正常后,可以快速恢复客户端的访问能力
  • 调用失败时,执行服务回退
  • 支持实时监控、报警和其他操作。

先简单用示例看下 Hystrix 的作用

搭建一个简单的服务端接口,端口 8080

package org.crazyit.cloud;

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

搭建客户端来调用

客户端先继承 HystrixComman 类

package org.crazyit.cloud;

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;

/**
 * 调用一个正常的服务
 * @author 杨恩雄
 *
 */
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";
	}
	
}

正常调用服务端

package org.crazyit.cloud;

public class HelloMain {

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

输出: 请求正常的服务,结果:Hello World

调用超时的服务

package org.crazyit.cloud;

public class HelloErrorMain {

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

输出:
执行 HelloCommand 的回退方法
请求异常的服务,结果:error

可以看到超时之后会触发 HelloCommand 中 getFallback 方法执行回退操作(默认是超过 1 秒则回退)。

Hystrix 运作流程图:
image.png

上面例子中我们使用了 HystrixCommand 的 execute 方法命令,除此外还有以下方法来执行命令:

  • toObservable: 返回一个最原始的课观察的实例(Observable),Observable 是 RxJava 的类,使用该对象可以观察命令的执行过程,并且将执行的信息传递给订阅者。
  • observe: 调用 toObservable 方法,获得一个原始的 Observable 实例,使用 ReplaySubject 作为原始 Observable 的订阅者。
  • queue: 通过 toObservable 方法获取原始的 Observable 实例,再调用 Observable 的 toBlocking 方法得到一个 BlockingObservable 实例,最后调用 BlockingObservable 的 toFuture 方法返回 Future 实例,调用 Future 的 get 方法得到执行结果
  • execute: 调用 queue 的 get 方法返回命令的执行结果,该方法同步执行。

以上 4 个方法,除 execute 方法外,其他方法均为异步执行.observe 与 toOBservable 方法的区别在于,toObservalbe 被调用后,命令不会立即执行,只有当返回的 Observable 实例被订阅后,才会真正执行命令。而 observe 方法的实现中,会调用 toObservable 得到的 Observable 实例,在对其进行订阅,因此调用 observ 方法后会立即执行命令(异步).

代码参考:

package org.crazyit.cloud.run;

import rx.Observable;
import rx.Observer;

import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;

public class RunTest {

	public static void main(String[] args) throws Exception {
		// 使用execute方法
		RunCommand c1 = new RunCommand("使用execute方法执行命令");
		c1.execute();		
		// 使用queue方法
		RunCommand c2 = new RunCommand("使用queue方法执行命令");
		c2.queue();
		// 使用 observe 方法
		RunCommand c3 = new RunCommand("使用 observe 方法执行命令");
		c3.observe();		
		// 使用 toObservable 方法
		RunCommand c4 = new RunCommand("使用 toObservable 方法执行命令");
		// 调用 toObservable 方法后,命令不会马上执行
		Observable<String> ob = c4.toObservable();
		// 进行订阅,此时会执行命令
		ob.subscribe(new Observer<String>() {

			public void onCompleted() {
				System.out.println("    命令执行完成");
			}

			public void onError(Throwable e) {
				
			}

			public void onNext(String t) {
				System.out.println("    命令执行结果:" + t);
			}
			
		});
		Thread.sleep(100);
	}

	// 测试命令
	static class RunCommand extends HystrixCommand<String> {

		String msg;
		
		public RunCommand(String msg) {
			super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
			this.msg = msg;
		}

		protected String run() throws Exception {
			System.out.println(msg);
			return "success";
		}
	}
}

输出:
使用 execute 方法执行命令
使用 queue 方法执行命令
使用 observe 方法执行命令
使用 toObservable 方法执行命令
命令执行结果:success
命令执行完成

Hystrix 可以配置一些属性可以参考 https://github.com/Netflix/Hystrix/wiki/Configuration

关于回退需要注意的是,回退方法 A 中也可以触发另一个命令 B,如果 B 执行失败也会触发 B 回退

特殊的情况:
image.png

断路器开启需要满足两个条件:

  • 整个链路达到一定阈值,默认情况下,10s 内产生超过 20 次请求,则符合第一个条件。
  • 满足第一个条件的情况下,如果请求的错误百分比大于阈值,则会打开断路器,默认为 50%
package org.crazyit.cloud.breaker;

import com.netflix.config.ConfigurationManager;
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixCommandMetrics.HealthCounts;
import com.netflix.hystrix.HystrixCommandProperties;
import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext;

/**
 * 断路器开启测试
 * @author 杨恩雄
 *
 */
public class OpenTest {

	public static void main(String[] args) throws Exception {
		/**
		 * 配置了三项
		 * 1.配置了数据的统计时间10000毫秒=10s
		 * 2.配置了阈值10个请求
		 * 3.配置了错误百分比50%,
		 * 注: MyCommand中配置了超时时间为500毫秒,命令执行睡眠800毫秒,也就是该命令总会超时(因为500+800 = 1.3 > 1s)
		 */
		// 10秒内有10个请求,则符合第一个条件
		ConfigurationManager.getConfigInstance().setProperty(
				"hystrix.command.default.metrics.rollingStats.timeInMilliseconds", 10000);
		ConfigurationManager.getConfigInstance().setProperty(
				"hystrix.command.default.circuitBreaker.requestVolumeThreshold", 10);
		ConfigurationManager.getConfigInstance().setProperty(
				"hystrix.command.default.circuitBreaker.errorThresholdPercentage", 50);
		for(int i = 0; i < 15; i++) {
			// 执行的命令全部都会超时
			MyCommand c = new MyCommand();
			c.execute();
			// 断路器打开后输出信息
			if(c.isCircuitBreakerOpen()) {
				System.out.println("断路器被打开,执行第 " + (i + 1) + " 个命令");
			}
		}
	}

	/**
	 * 模拟超时的命令
	 * @author 杨恩雄
	 *
	 */
	static class MyCommand extends HystrixCommand<String> {
		// 设置超时的时间为500毫秒
		public MyCommand() {
		    super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
		            .andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
		            		.withExecutionTimeoutInMilliseconds(500))
		    		);
		}

		protected String run() throws Exception {
			// 模拟处理超时
			Thread.sleep(800);
			return "";
		}

		@Override
		protected String getFallback() {
			return "";
		}
	}
}

运行结果:
断路器被打开,执行第 11 个命令
断路器被打开,执行第 12 个命令
断路器被打开,执行第 13 个命令
断路器被打开,执行第 14 个命令
断路器被打开,执行第 15 个命令
可见前 10 个命令没有开启断路器,到了第 11 个命令,断路器被打开。

断路器关闭

断路器打开后,在一段时间内,命令不会再执行(一直触发回退),这段时间我们称作"休眠期",休眠期默认为 5s,休眠期结束后,Hystrix 会尝试性地执行一次命令,此时断路器的状态不是开启,也不是关闭,而是一个半开的状态,如果这一次命令执行成功,则会关闭断路器并清空链路的健康信息; 如果执行失败,断路器会继续保持打开的状态。

package org.crazyit.cloud.breaker;

import com.netflix.config.ConfigurationManager;
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixCommandMetrics.HealthCounts;
import com.netflix.hystrix.HystrixCommandProperties;

public class CloseTest {

	public static void main(String[] args) throws Exception {
		// 10秒内有3个请求就满足第一个开启断路器的条件
		ConfigurationManager.getConfigInstance().setProperty(
				"hystrix.command.default.metrics.rollingStats.timeInMilliseconds", 10000);
		ConfigurationManager.getConfigInstance().setProperty(
				"hystrix.command.default.circuitBreaker.requestVolumeThreshold", 3);
		// 请求的失败率,默认值为50%
		ConfigurationManager.getConfigInstance().setProperty(
				"hystrix.command.default.circuitBreaker.errorThresholdPercentage", 50);
		// 设置休眠期,断路器打开后,这段时间不会再执行命令,默认值为5秒,此处设置为3秒
		ConfigurationManager.getConfigInstance().setProperty(
				"hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds", 3000);
		// 该值决定是否执行超时
		boolean isTimeout = true;		
		for(int i = 0; i < 10; i++) {
			// 执行的命令全部都会超时
			MyCommand c = new MyCommand(isTimeout);
			c.execute();		
			// 输出健康状态等信息
			HealthCounts hc = c.getMetrics().getHealthCounts();
			System.out.println("断路器状态:" + c.isCircuitBreakerOpen() + 
					", 请求总数:" + hc.getTotalRequests());
			if(c.isCircuitBreakerOpen()) {
				// 断路器打开,让下一次循环成功执行命令
				isTimeout = false;
				System.out.println("=====  断路器打开了,等待休眠期结束   =====");
				// 休眠期会在3秒后结束,此处等待4秒,确保休眠期结束
				Thread.sleep(4000);
			}	
		}
	}

	/**
	 * 模拟超时的命令
	 * @author 杨恩雄
	 *
	 */
	static class MyCommand extends HystrixCommand<String> {
		
		private boolean isTimeout;
		
		// 设置超时的时间为500毫秒
		public MyCommand(boolean isTimeout) {
		    super(
		    		Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
		            .andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
		            		.withExecutionTimeoutInMilliseconds(500))
		    	 );
		    this.isTimeout = isTimeout;
		}

		protected String run() throws Exception {
			// 让外部决定是否超时
			if(isTimeout) {
				// 模拟处理超时
				Thread.sleep(800);
			} else {
				Thread.sleep(200);
			}
			return "";
		}

		@Override
		protected String getFallback() {
			return "";
		}
	}
}

运行结果:

断路器状态:false, 请求总数:0
断路器状态:false, 请求总数:1
断路器状态:false, 请求总数:2
断路器状态:true, 请求总数:3
===== 断路器打开了,等待休眠期结束 =====
断路器状态:false, 请求总数:0
断路器状态:false, 请求总数:0
断路器状态:false, 请求总数:0
断路器状态:false, 请求总数:3
断路器状态:false, 请求总数:3
断路器状态:false, 请求总数:5

我们再代码中配置休眠期为 3s,循环 10 次,创建 10 个命令并执行,在执行完第 4 个命令后,断路器被打开,此时我们等待休眠期结束,让下一次循环命令执行成功。第 5 次会执行成功,此时断路器会被关闭,剩下的命令全部都可以正常执行,在循环体内使用了 HealthCounts 对象,用于记录链路的健康信息,如果断路器关闭(链路恢复健康),HealthCounts 里面的健康信息会被重置。

隔离机制

命令真正执行除了要保证断路器关闭外,还需要判断执行命令的线程池或者信号量是否满载的情况,如果满载则不会执行,直接回退,这样的机制在控制命令的执行上,实现了错误的隔离,Hystrix 提供两种隔离策略:

  • THREAD(线程)默认值,由线程池来决定命令的执行,如果线程池满载则不会执行命令.Hystrix 使用了 ThreadPoolExecutor 来控制线程池行为,线程池的默认大小为 10.
  • SEMAPHORE(信号量):由信号量来决定命令的执行,当请求的并发数高于阈值时,就不在执行命令,相当于线程池策略,信号量策略开销更小,但是该策略不支持超时以及异步,除非对调用的服务有足够的信任,否则不建议使用该策略进行隔离。
package org.crazyit.cloud.isolation;

import java.util.concurrent.ThreadPoolExecutor;

import com.netflix.hystrix.HystrixCircuitBreaker;
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixCommandProperties;

public class MyCommand extends HystrixCommand<String> {

	int index;

	public MyCommand(int index) {
		super(Setter.withGroupKey(HystrixCommandGroupKey.Factory
				.asKey("ExampleGroup")));
		this.index = index;
	}

	protected String run() throws Exception {
		Thread.sleep(500);
		System.out.println("执行方法,当前索引:" + index);
		return "";
	}

	@Override
	protected String getFallback() {
		System.out.println("执行 fallback,当前索引:" + index);
		return "";
	}
}

测试线程池

package org.crazyit.cloud.isolation;

import java.util.concurrent.ThreadPoolExecutor;

import com.netflix.config.ConfigurationManager;
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixCommandProperties;

/**
 * 线程隔离策略测试类
 * @author 杨恩雄
 *
 */
public class ThreadIso {

	public static void main(String[] args) throws Exception {
		// 配置线程池大小为3
		ConfigurationManager.getConfigInstance().setProperty(
				"hystrix.threadpool.default.coreSize", 3);
		
		for(int i = 0; i < 6; i++) {
			MyCommand c = new MyCommand(i);
			c.queue();
		}
		Thread.sleep(5000);
	}
}

配置了线程池大小为 3,进行 6 次循环,意味着有 3 次会回退。
输出:
执行 fallback,当前索引:3
执行 fallback,当前索引:4
执行 fallback,当前索引:5
执行方法,当前索引:1
执行方法,当前索引:0
执行方法,当前索引:2

测试信号量

package org.crazyit.cloud.isolation;

import java.util.concurrent.ThreadPoolExecutor;

import com.netflix.config.ConfigurationManager;
import com.netflix.hystrix.HystrixCircuitBreaker;
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixCommandProperties;
import com.netflix.hystrix.HystrixCommandProperties.ExecutionIsolationStrategy;

/**
 * 信号量隔离策略测试类
 * 
 * @author 杨恩雄
 *
 */
public class SemaphoreIso {

	public static void main(String[] args) throws Exception {
		// 配置使用信号量的策略进行隔离
		ConfigurationManager.getConfigInstance().setProperty(
				"hystrix.command.default.execution.isolation.strategy",
				ExecutionIsolationStrategy.SEMAPHORE);
		// 设置最大并发数,默认值为10
		ConfigurationManager
				.getConfigInstance()
				.setProperty(
						"hystrix.command.default.execution.isolation.semaphore.maxConcurrentRequests",
						2);
		// 设置执行回退方法的最大并发,默认值为10
		ConfigurationManager
				.getConfigInstance()
				.setProperty(
						"hystrix.command.default.fallback.isolation.semaphore.maxConcurrentRequests",
						20);
		for (int i = 0; i < 6; i++) {
			final int index = i;
			Thread t = new Thread() {
				public void run() {
					MyCommand c = new MyCommand(index);
					c.execute();
				}
			};
			t.start();
		}
		Thread.sleep(5000);
	}
}

配置了线程池最大并发数为 2,循环 6 次,意味着有 4 次会回退
输出:
执行 fallback,当前索引:4
执行 fallback,当前索引:0
执行 fallback,当前索引:1
执行 fallback,当前索引:3
执行方法,当前索引:5
执行方法,当前索引:2

请求合并

 对于URL相同但是参数不同的请求,Hystrix提供了合并请求的功能,减少线程开销和网络连接,提高性能,有点像批处理功能。 实现合并请求功能,至少包含以下3个条件:
  • 需要有一个执行请求的命令,将全部参数进行整理,然后调用外部服务。
  • 需要有一个合并处理器,用于手机请求,以及处理结果。
  • 外部接口提供支持,例如一个接口是根据姓名查询人员信息/person/{personName} ,另外服务端还提供批量查询/persons 用于查找多个 Person

这种情况,如果有多个请求单个 Person 的请求,就可以合并为一个批量查询进行处理。

package org.crazyit.cloud.collapse;

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();
	}

	/**
	 * 合并执行的命令类
	 * 
	 * @author 杨恩雄
	 *
	 */
	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;
		}
	}

	/**
	 * 合并处理器
	 * 第一个类型为批处理返回的结果类型
	 * 第二个为单请求返回的结果类型
	 * 第三个是请求参数类型
	 * @author 杨恩雄
	 */
	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);
			}
		}
	}

}

设置了"时间段",在 1 秒内执行的请求将会被合并到一起执行,该"时间段"的默认值为 10 毫秒。
执行结果:
收集参数后执行命令,参数数量:4
id: 0a41cb54-fc48-470a-89bd-964f3d4dbb03, name: Angus, age: 28
id: 9a7cfdf5-eab8-47f3-ae0b-e17948cb54e6, name: Crazyit, age: 4
id: 943de7e9-5c0e-4629-9b95-6ff5c9a469f9, name: Sune, age: 29
id: 59282c89-d0d5-4dde-9296-0478b72df8c9, name: Paris, age: 8

一般来书合并请求进行批处理,比发送多个请求快,对于 URL 相同、参数不同的请求,推荐使用合并请求功能。

请求缓存

Hystrix 支持缓存功能,一次请求过程中,多个地方调用同一个接口考虑使用缓存,缓存打开后下一次命令不会执行直接从缓存中获取响应;开启缓存较为简单在命令中重写父类的 getCacheKey 即可。

package org.crazyit.cloud.cache;

import org.crazyit.cloud.HelloCommand;

import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixCommandKey;
import com.netflix.hystrix.HystrixCommandProperties;
import com.netflix.hystrix.HystrixRequestCache;
import com.netflix.hystrix.HystrixCommand.Setter;
import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy;
import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategyDefault;
import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext;

public class CacheMain {

	public static void main(String[] args) {	
		// 初始化请求上下文
		HystrixRequestContext context = HystrixRequestContext.initializeContext();
		// 请求正常的服务
		String key = "cache-key";
		MyCommand c1 = new MyCommand(key);
		MyCommand c2 = new MyCommand(key);
		MyCommand c3 = new MyCommand(key);
		// 输出结果
		System.out.println(c1.execute() + "c1 是否读取缓存: " + c1.isResponseFromCache());
		System.out.println(c2.execute() + "c2 是否读取缓存: " + c2.isResponseFromCache());
		System.out.println(c3.execute() + "c3 是否读取缓存: " + c3.isResponseFromCache());		
		// 获取缓存实例
		HystrixRequestCache cache = HystrixRequestCache.getInstance(
				HystrixCommandKey.Factory.asKey("MyCommandKey"), 
				HystrixConcurrencyStrategyDefault.getInstance());
		// 清空缓存
		cache.clear(key);		
		// 重新执行命令
		MyCommand c4 = new MyCommand(key);
		System.out.println(c4.execute() + "c4 是否读取缓存: " + c4.isResponseFromCache());
		
		context.shutdown();
	}
	
	static class MyCommand extends HystrixCommand<String> {
		
		private String key;

		public MyCommand(String key) {
		    super(
		    		Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
		    		.andCommandKey(HystrixCommandKey.Factory.asKey("MyCommandKey"))
		    		);
		    this.key = key;
		}

		protected String run() throws Exception {
			System.out.println("执行命令");
			return "";
		}

		@Override
		protected String getCacheKey() {
			return this.key;
		}
	}
}

输出:
执行命令
c1 是否读取缓存: false
c2 是否读取缓存: true
c3 是否读取缓存: true
执行命令
c4 是否读取缓存: false

可见 c2,c3 都读取了缓存。c4 因为执行前清空了缓存,所有没有读取缓存。

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

// 初始化请求上下文
		HystrixRequestContext context = HystrixRequestContext.initializeContext();
xxxxxxxxxx

context.shutdown();
  • Spring

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

    938 引用 • 1456 回帖 • 163 关注
  • Hystrix
    7 引用

相关帖子

欢迎来到这里!

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

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