Hystrix从入门到放弃

Hystrix框架简介

Hystrix翻译成中文是“豪猪”的意思。豪猪身上长满了刺,能保护自己不受天敌的伤害,代表了一种防御机制。因此Hystrix 的logo也是定义成了豪猪。


假设有如下场景:

比如我们现在有3个业务调用,分别是查询订单、查询商品、查询用户,且这三个业务请求都是依赖第三方服务—订单服务、商品服务、用户服务。三个服务均是通过RPC调用。当查询订单服务,假如线程阻塞了,这个时候后续有大量的查询订单请求过来,那么容器中的线程数量则会持续增加直至CPU资源耗尽,整个服务对外不可用,集群环境下就是雪崩。

Hystrix核心功能


线程隔离

  • 正常情况下,tomcat或其他容器的线程对外提供服务,接收用户请求。
  • Hystrix线程隔离的做法是:将tomcat线程处理的任务转交给Hystrix内部的线程去执行,这样tomcat线程就可以去做其他事情了。当Hystrix的线程将任务执行完后,将执行结果返回给tomcat线程。

信号量隔离

信号量的资源隔离只是起到一个开关的作用,例如,服务X的信号量大小为10,那么同时只允许10个tomcat的线程(此处是tomcat的线程,而不是服务X的独立线程池里面的线程)来访问服务X,其他的请求就会被拒绝,从而达到限流保护的作用。

线程隔离和信号量隔离对比



降级策略

  • 当请求出现了异常,超时,或者服务不可用的时候,一般情况你会怎么做?返回空,抛异常给调用方还是什么都不做?
  • Hystrix可以让你自定义降级策略。当发生异常的时候,返回你事先定义好的策略。比如空对象/默认值

熔断技术

  • 一般是指软件系统中,由于某些原因使得服务出现了过载现象,为防止造成整个系统故障,从而采用的一种保护措施,所以很多地方把熔断亦称为过载保护。很多时候刚开始可能只是系统出现了局部的、小规模的故障,然而由于种种原因,故障影响的范围越来越大,最终导致了全局性的后果。
  • 如果某个目标服务调用慢或者有大量超时,此时,熔断该服务的调用,对于后续调用请求,不在继续调用目标服务,直接返回,快速释放资源。如果目标服务情况好转则恢复调用。

请求缓存

  • 将请求缓存下来,当后续有相同的请求到来的时候,直接返回缓存中的响应,从而避免直接对服务进行调用,增加服务的压力
  • 比如根据用户id查询用户信息,根据商品id查询商品信息等,查询全国所有城市的邮编。这类实体的属性不会频繁的变动。

请求合并

将相同类型的请求合并成一次调用,而不是分别调用服务提供方,目的是降低服务提供方的压力。



Hystrix demo

HelloWorld demo:

public class CommandHelloWorld extends HystrixCommand<String> {

    private final String name;

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

    @Override
    protected String run() {
        return "Hello " + name + "!";
    }

    public static class UnitTest {

        @Test
        public void testSynchronous() {
            assertEquals("Hello World!", new CommandHelloWorld("World").execute());
            assertEquals("Hello Bob!", new CommandHelloWorld("Bob").execute());
        }

        @Test
        public void testAsynchronous1() throws Exception {
            assertEquals("Hello World!", new CommandHelloWorld("World").queue().get());
            assertEquals("Hello Bob!", new CommandHelloWorld("Bob").queue().get());
        }

        @Test
        public void testAsynchronous2() throws Exception {

            Future<String> fWorld = new CommandHelloWorld("World").queue();
            Future<String> fBob = new CommandHelloWorld("Bob").queue();

            assertEquals("Hello World!", fWorld.get());
            assertEquals("Hello Bob!", fBob.get());
        }

        @Test
        public void testObservable() throws Exception {

            Observable<String> fWorld = new CommandHelloWorld("World").observe();
            Observable<String> fBob = new CommandHelloWorld("Bob").observe();

            // blocking
            assertEquals("Hello World!", fWorld.toBlocking().single());
            assertEquals("Hello Bob!", fBob.toBlocking().single());

            // non-blocking
            // - this is a verbose anonymous inner-class approach and doesn't do assertions
            fWorld.subscribe(new Observer<String>() {

                @Override
                public void onCompleted() {
                    System.out.println("onCompleted here");
                }

                @Override
                public void onError(Throwable e) {
                    e.printStackTrace();
                }

                @Override
                public void onNext(String v) {
                    System.out.println("onNext: " + v);
                }

            });

            // non-blocking
            // - also verbose anonymous inner-class
            // - ignore errors and onCompleted signal
            fBob.subscribe(new Action1<String>() {

                @Override
                public void call(String v) {
                    System.out.println("onNext: " + v);
                }

            });

            // non-blocking
            // - using closures in Java 8 would look like this:

            //            fWorld.subscribe((v) -> {
            //                System.out.println("onNext: " + v);
            //            })

            // - or while also including error handling

            //            fWorld.subscribe((v) -> {
            //                System.out.println("onNext: " + v);
            //            }, (exception) -> {
            //                exception.printStackTrace();
            //            })

            // More information about Observable can be found at https://github.com/Netflix/RxJava/wiki/How-To-Use

        }
    }

}

上例中execute()方法是通过同步的方式执行任务;queue()方法是通过异步的防止执行任务。

new Observer<String>() {
    @Override
    public void onCompleted() {
        System.out.println("onCompleted here");
    }

    @Override
    public void onError(Throwable e) {
        e.printStackTrace();
    }

    @Override
    public void onNext(String v) {
        System.out.println("onNext: " + v);
    }
}

是对任务注册的回调事件。onCompleted是在任务执行完的时候回调,onError是在出现异常时候回调,onNext是获取结果后回调。三者的执行顺序是:onNext/onError完成之后最后回调onCompleted。

线程隔离和信号量隔离的demo:

package com.example.demo.hystrixdemo.isolation;

import com.netflix.hystrix.*;
import org.junit.Test;

import java.io.IOException;
import java.util.Map;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

/**
 *
 * 测试线程池隔离策略
 * 设置线程池里的线程数=3,然后循环>3次和<3次,最后查看当前所有线程名称
 * 
 */
public class HystrixCommand4ThreadPoolTest extends HystrixCommand<String> {

    private final String name;

    public HystrixCommand4ThreadPoolTest(String name) {
//        super(HystrixCommandGroupKey.Factory.asKey("ThreadPoolTestGroup"));
        super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ThreadPoolTestGroup"))  
                .andCommandKey(HystrixCommandKey.Factory.asKey("testCommandKey"))
                .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("ThreadPoolTest"))
                .andCommandPropertiesDefaults(
                	HystrixCommandProperties.Setter()
                		.withExecutionTimeoutInMilliseconds(5000)
                )
                .andThreadPoolPropertiesDefaults(
                	HystrixThreadPoolProperties.Setter()
                		.withCoreSize(3)	// 配置线程池里的线程数
                )
        );
        this.name = name;
    }

    @Override
    protected String run() throws Exception {
    	/*---------------会触发fallback的case-------------------*/
//    	int j = 0;
//    	while (true) {
//    		j++;
////    		return "a";
//    	}
    	// 除零异常
//    	int i = 1/0;
    	
    	// 主动抛出异常
//        throw new HystrixTimeoutException();
//        throw new RuntimeException("this command will trigger fallback");
//        throw new Exception("this command will trigger fallback");
//    	throw new HystrixRuntimeException(FailureType.BAD_REQUEST_EXCEPTION, commandClass, message, cause, fallbackException);
        
    	/*---------------不会触发fallback的case-------------------*/
    	// 被捕获的异常不会触发fallback
//    	try {
//    		throw new RuntimeException("this command never trigger fallback");
//    	} catch(Exception e) {
//    		e.printStackTrace();
//    	}
        
    	// HystrixBadRequestException异常由非法参数或非系统错误引起,不会触发fallback,也不会被计入熔断器
//        throw new HystrixBadRequestException("HystrixBadRequestException is never trigger fallback");
    	TimeUnit.MILLISECONDS.sleep(2000);
		System.out.println(Thread.currentThread().getName() + ": " + name);
		return name;
    }

    @Override
    protected String getFallback() {
        return "fallback: " + name;
    }

    public static class UnitTest {

        @Test
        public void testSynchronous() throws IOException {
        	for(int i = 0; i < 3; i++) {
	        	try {
//	        		assertEquals("fallback: Hlx", new HystrixCommand4ThreadPoolTest("Hlx").execute());
//	        		System.out.println("===========" + new HystrixCommand4ThreadPoolTest("Hlx").execute());
					//占有线程池中的线程
	        		Future<String> future = new HystrixCommand4ThreadPoolTest("get available thread" + i).queue();
	        		//强制阻塞
//					System.out.println("future返回:" + future.get());
	        	} catch(Exception e) {
	        		System.out.println("run()抛出HystrixBadRequestException时,被捕获到这里" + e.getCause());
	        	}
        	}
        	for(int i = 0; i < 10; i++) {
	        	try {
	//        		assertEquals("fallback: Hlx", new HystrixCommand4ThreadPoolTest("Hlx").execute());
	        		System.out.println("===========" + new HystrixCommand4ThreadPoolTest(" not get available thread" + i).execute());
//	        		Future<String> future = new HystrixCommand4ThreadPoolTest("Hlx1"+i).queue();
//	        		System.out.println("===========" + future);
	        	} catch(Exception e) {
	        		System.out.println("run()抛出HystrixBadRequestException时,被捕获到这里" + e.getCause());
	        	}
        	}
        	try {
        		TimeUnit.MILLISECONDS.sleep(2000);
        	}catch(Exception e) {}
        	System.out.println("------开始打印现有线程---------");
        	Map<Thread, StackTraceElement[]> map=Thread.getAllStackTraces();
        	for (Thread thread : map.keySet()) {
				System.out.println(thread.getName());
			}
        	System.out.println(map);
        	System.out.println("thread num: " + map.size());
//        	int numExecuted = HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size();
//            System.out.println("num executed: " + numExecuted);
        }
    }

}
package com.example.demo.hystrixdemo.isolation;

import com.example.demo.hystrixdemo.HelloWorldHystrixCommand;
import com.netflix.hystrix.*;
import com.netflix.hystrix.HystrixCommandProperties.ExecutionIsolationStrategy;
import org.junit.Test;

import java.io.IOException;

/**
 * 测试信号量隔离策略
 * 默认执行run()用的是主线程,为了模拟并行执行command,这里我们自己创建多个线程来执行command
 * 设置ExecutionIsolationSemaphoreMaxConcurrentRequests为3,意味着信号量最多允许执行run的并发数为3,超过则触发降级,即不执行run而执行getFallback
 * 设置FallbackIsolationSemaphoreMaxConcurrentRequests为1,意味着信号量最多允许执行fallback的并发数为1,超过则抛异常fallback execution rejected
 */
public class HystrixCommand4SemaphoreTest extends HystrixCommand<String> {

    private final String name;

    public HystrixCommand4SemaphoreTest(String name) {
        super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("SemaphoreTestGroup"))
                        .andCommandKey(HystrixCommandKey.Factory.asKey("SemaphoreTestKey"))
                        .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("SemaphoreTestThreadPoolKey"))
                        .andCommandPropertiesDefaults(    // 配置信号量隔离
                                HystrixCommandProperties.Setter()
                                        .withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE)    // 信号量隔离
                                        .withExecutionIsolationSemaphoreMaxConcurrentRequests(3)
                                        .withFallbackIsolationSemaphoreMaxConcurrentRequests(1)
                        )
                // 设置了信号量隔离后,线程池配置将变无效
//                .andThreadPoolPropertiesDefaults(
//                		HystrixThreadPoolProperties.Setter()
//                		.withCoreSize(13)	// 配置线程池里的线程数
//                )
        );
        this.name = name;
    }

    @Override
    protected String run() throws Exception {
        return "run(): name=" + name + ",线程名是" + Thread.currentThread().getName();
    }

    @Override
    protected String getFallback() {
        return "getFallback(): name=" + name + ",线程名是" + Thread.currentThread().getName();
    }

    public static class UnitTest {

        @Test
        public void testSynchronous() throws IOException, InterruptedException {

            try {
                Thread.sleep(2000);
                for (int i = 0; i < 5; i++) {
                    final int j = i;
                    // 自主创建线程来执行command,创造并发场景
                    //	                    @Override
                    Thread thread = new Thread(() -> {
                        // 这里执行两类command:HystrixCommand4SemaphoreTest设置了信号量隔离、HelloWorldHystrixCommand未设置信号量
                        System.out.println("-----------" + new HelloWorldHystrixCommand("HelloWorldHystrixCommand" + j).execute());
                        System.out.println("===========" + new HystrixCommand4SemaphoreTest("HystrixCommand4SemaphoreTest" + j).execute());    // 被信号量拒绝的线程从这里抛出异常
                        System.out.println("-----------" + new HelloWorldHystrixCommand("HelloWorldHystrixCommand" + j).execute());    // 被信号量拒绝的线程不能执行到这里

                    });
                    thread.start();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
//        	try {
//        		TimeUnit.MILLISECONDS.sleep(2000);
//        	}catch(Exception e) {}
//            System.out.println("------开始打印现有线程---------");
//            Map<Thread, StackTraceElement[]> map = Thread.getAllStackTraces();
//            for (Thread thread : map.keySet()) {
//                System.out.println(thread.getName());
//            }
//            System.out.println("thread num: " + map.size());
//        	System.in.read();
            Thread.sleep(4000);
        }
    }

}

降级策略demo:

/**
 *
 * 以下四种情况将触发getFallback调用:
 * 1)run()方法抛出非HystrixBadRequestException异常
 * 2)run()方法调用超时
 * 3)熔断器开启拦截调用
 * 4)线程池/队列/信号量是否跑满
 *
 * 实现getFallback()后,执行命令时遇到以上4种情况将被fallback接管,不会抛出异常或其他
 */
public class HystrixFallback4ExceptionTest extends HystrixCommand<String> {

    private final String name;

    public HystrixFallback4ExceptionTest(String name) {
        super(HystrixCommandGroupKey.Factory.asKey("FallbackGroup"));
        this.name = name;
    }

    @Override
    protected String run() throws Exception {

    	/*---------------会触发fallback的case-------------------*/
        // 无限循环,实际上属于超时
//    	int j = 0;
//    	while (true) {
//    		j++;
//    	}


        // 除零异常
//    	int i = 1/0;


        // 主动抛出异常
//        throw new HystrixTimeoutException();
//        throw new RuntimeException("this command will trigger fallback");
//        throw new Exception("this command will trigger fallback");
//    	throw new HystrixRuntimeException(FailureType.BAD_REQUEST_EXCEPTION, commandClass, message, cause, fallbackException);

    	/*---------------不会触发fallback的case-------------------*/
        // 被捕获的异常不会触发fallback
//    	try {
//    		throw new RuntimeException("this command never trigger fallback");
//    	} catch(Exception e) {
//    		e.printStackTrace();
//    	}

        // HystrixBadRequestException异常由非法参数或非系统错误引起,不会触发fallback,也不会被计入熔断器
        throw new HystrixBadRequestException("HystrixBadRequestException is never trigger fallback");

//		return name;
    }

    @Override
    protected String getFallback() {
        return "fallback: " + name;
    }

    public static class UnitTest {

        @Test
        public void testSynchronous() throws IOException {
            try {
                System.out.println(new HystrixFallback4ExceptionTest("ExceptionTest").execute());
            } catch(Exception e) {
                System.out.println("run()抛出HystrixBadRequestException时,会被捕获到这里" + e.getCause());
            }
//        	System.in.read();
        }
    }

}
package com.example.demo.hystrixdemo.fallback;

import com.netflix.hystrix.*;
import org.junit.Test;

import java.io.IOException;
import java.util.Map;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

/**
 * 
 * 设置线程池里的线程数=3,然后循环>3次和<3次,最后查看当前所有线程名称
 * 
 */
public class HystrixCommand4ThreadPoolTest extends HystrixCommand<String> {

    private final String name;

    public HystrixCommand4ThreadPoolTest(String name) {
//        super(HystrixCommandGroupKey.Factory.asKey("ThreadPoolTestGroup"));
        super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ThreadPoolTestGroup"))  
                .andCommandKey(HystrixCommandKey.Factory.asKey("testCommandKey"))
                .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("ThreadPoolTest"))
                .andCommandPropertiesDefaults(
                	HystrixCommandProperties.Setter()
                		.withExecutionTimeoutInMilliseconds(5000)
                )
                .andThreadPoolPropertiesDefaults(
                	HystrixThreadPoolProperties.Setter()
                		.withCoreSize(3)	// 配置线程池里的线程数
                )
        );
        this.name = name;
    }

    @Override
    protected String run() throws Exception {
    	/*---------------会触发fallback的case-------------------*/
//    	int j = 0;
//    	while (true) {
//    		j++;
////    		return "a";
//    	}
    	// 除零异常
//    	int i = 1/0;
    	
    	// 主动抛出异常
//        throw new HystrixTimeoutException();
//        throw new RuntimeException("this command will trigger fallback");
//        throw new Exception("this command will trigger fallback");
//    	throw new HystrixRuntimeException(FailureType.BAD_REQUEST_EXCEPTION, commandClass, message, cause, fallbackException);
        
    	/*---------------不会触发fallback的case-------------------*/
    	// 被捕获的异常不会触发fallback
//    	try {
//    		throw new RuntimeException("this command never trigger fallback");
//    	} catch(Exception e) {
//    		e.printStackTrace();
//    	}
        
    	// HystrixBadRequestException异常由非法参数或非系统错误引起,不会触发fallback,也不会被计入熔断器
//        throw new HystrixBadRequestException("HystrixBadRequestException is never trigger fallback");
    	TimeUnit.MILLISECONDS.sleep(2000);
		System.out.println(Thread.currentThread().getName() + ": " + name);
		return name;
    }

    @Override
    protected String getFallback() {
        return "fallback: " + name;
    }

    public static class UnitTest {

        @Test
        public void testSynchronous() throws IOException {
        	for(int i = 0; i < 3; i++) {
	        	try {
//	        		assertEquals("fallback: Hlx", new HystrixCommand4ThreadPoolTest("Hlx").execute());
//	        		System.out.println("===========" + new HystrixCommand4ThreadPoolTest("Hlx").execute());
					//占有线程池中的线程
	        		Future<String> future = new HystrixCommand4ThreadPoolTest("get available thread" + i).queue();
	        		//强制阻塞
//					System.out.println("future返回:" + future.get());
	        	} catch(Exception e) {
	        		System.out.println("run()抛出HystrixBadRequestException时,被捕获到这里" + e.getCause());
	        	}
        	}
        	for(int i = 0; i < 10; i++) {
	        	try {
	//        		assertEquals("fallback: Hlx", new HystrixCommand4ThreadPoolTest("Hlx").execute());
	        		System.out.println("===========" + new HystrixCommand4ThreadPoolTest(" not get available thread" + i).execute());
//	        		Future<String> future = new HystrixCommand4ThreadPoolTest("not get available thread" + i).queue();
//	        		System.out.println("===========" + future.get());
	        	} catch(Exception e) {
	        		System.out.println("run()抛出HystrixBadRequestException时,被捕获到这里" + e.getCause());
	        	}
        	}
        	try {
        		TimeUnit.MILLISECONDS.sleep(2000);
        	}catch(Exception e) {}
        	System.out.println("------开始打印现有线程---------");
        	Map<Thread, StackTraceElement[]> map=Thread.getAllStackTraces();
        	for (Thread thread : map.keySet()) {
				System.out.println(thread.getName());
			}
        	System.out.println(map);
        	System.out.println("thread num: " + map.size());
//        	int numExecuted = HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size();
//            System.out.println("num executed: " + numExecuted);
        }
    }

}
package com.example.demo.hystrixdemo.fallback;

import com.netflix.hystrix.*;
import org.junit.Test;

import java.io.IOException;
import java.util.Map;

/**
 * 
 * CircuitBreakerRequestVolumeThreshold设置为3,意味着10s内请求超过3次就触发熔断器
 * run()中无限循环使命令超时进入fallback,执行3次run后,将被熔断,进入降级,即不进入run()而直接进入fallback
 * 如果未熔断,但是threadpool被打满,仍然会降级,即不进入run()而直接进入fallback
 */
public class HystrixCommand4CircuitBreakerTest extends HystrixCommand<String> {

    private final String name;

    public HystrixCommand4CircuitBreakerTest(String name) {
        super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("CircuitBreakerTestGroup"))  
                .andCommandKey(HystrixCommandKey.Factory.asKey("CircuitBreakerTestKey"))
                .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("CircuitBreakerTest"))
                .andThreadPoolPropertiesDefaults(	// 配置线程池
                		HystrixThreadPoolProperties.Setter()
                		.withCoreSize(200)	// 配置线程池里的线程数,设置足够多线程,以防未熔断却打满threadpool
                )
                .andCommandPropertiesDefaults(	// 配置熔断器
                		HystrixCommandProperties.Setter()
                		.withCircuitBreakerEnabled(true)
                		.withCircuitBreakerRequestVolumeThreshold(3)
                		.withCircuitBreakerErrorThresholdPercentage(80)
//                		.withCircuitBreakerForceOpen(true)	// 置为true时,所有请求都将被拒绝,直接到fallback
//                		.withCircuitBreakerForceClosed(true)	// 置为true时,将忽略错误
//                		.withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE)	// 信号量隔离
//                		.withExecutionTimeoutInMilliseconds(5000)
                )
        );
        this.name = name;
    }

    @Override
    protected String run() throws Exception {
    	System.out.println("running run():" + name);
    	int num = Integer.valueOf(name);
    	if(num % 2 == 0 && num < 10) {	// 直接返回
    		return name;
    	} else {	// 无限循环模拟超时
    		int j = 0;
        	while (true) {
        		j++;
        	}
    	}
//		return name;
    }

    @Override
    protected String getFallback() {
        return "CircuitBreaker fallback: " + name;
    }

    public static class UnitTest {

        @Test
        public void testSynchronous() throws IOException {
        	for(int i = 0; i < 50; i++) {
	        	try {
	        		System.out.println("===========" + new HystrixCommand4CircuitBreakerTest(String.valueOf(i)).execute());
//	        		try {
//	            		TimeUnit.MILLISECONDS.sleep(1000);
//	            	}catch(Exception e) {}
//	        		Future<String> future = new HystrixCommand4CircuitBreakerTest("Hlx"+i).queue();
//	        		System.out.println("===========" + future);
	        	} catch(Exception e) {
	        		System.out.println("run()抛出HystrixBadRequestException时,被捕获到这里" + e.getCause());
	        	}
        	}

        	System.out.println("------开始打印现有线程---------");
        	Map<Thread, StackTraceElement[]> map=Thread.getAllStackTraces();
        	for (Thread thread : map.keySet()) {
				System.out.println(thread.getName());
			}
        	System.out.println("thread num: " + map.size());
        	
        	System.in.read();
        }
    }

}

Hystrix执行流程图:

请求合并的demo:

package com.example.demo.hystrixdemo.collapsing;

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

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * Sample {@link HystrixCollapser} that automatically batches multiple requests to execute()/queue()
 * into a single {@link HystrixCommand} execution for all requests within the defined batch (time or size).
 */
public class HelloWorldHystrixCollapser extends HystrixCollapser<List<String>, String, Integer> {

    private final Integer key;

    public HelloWorldHystrixCollapser(Integer key) {
        this.key = key;
    }

    @Override
    public Integer getRequestArgument() {
        return key;
    }

    // 创建一个批量请求命令
    @Override
    protected HystrixCommand<List<String>> createCommand(final Collection<CollapsedRequest<String, Integer>> requests) {
        return new BatchCommand(requests); // 把批量请求传给command类
    }

    // 把批量请求的结果和对应的请求一一对应起来
    @Override
    protected void mapResponseToRequests(List<String> batchResponse, Collection<CollapsedRequest<String, Integer>> requests) {
        int count = 0;
        for (CollapsedRequest<String, Integer> request : requests) {
            request.setResponse(batchResponse.get(count++));
        }
    }

    // command类
    private static final class BatchCommand extends HystrixCommand<List<String>> {
        private final Collection<CollapsedRequest<String, Integer>> requests;

        private BatchCommand(Collection<CollapsedRequest<String, Integer>> requests) {
            super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("CollepsingGroup"))
                    .andCommandKey(HystrixCommandKey.Factory.asKey("CollepsingKey")));
            this.requests = requests;
        }

        @Override
        protected List<String> run() {
            ArrayList<String> response = new ArrayList<String>();
            // 处理每个请求,返回结果
            for (CollapsedRequest<String, Integer> request : requests) {
                // artificial response for each argument received in the batch
                response.add("ValueForKey: " + request.getArgument() + " thread:" + Thread.currentThread().getName());
            }
            return response;
        }
    }
}
package com.example.demo.hystrixdemo.collapsing;

import com.netflix.hystrix.HystrixEventType;
import com.netflix.hystrix.HystrixInvokableInfo;
import com.netflix.hystrix.HystrixRequestLog;
import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext;
import org.junit.Test;

import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

/**
 * 相邻两个请求可以自动合并的前提是两者足够“近”:启动执行的间隔时间足够小,默认10ms
 * 
 */
public class HystrixCommand4RequestCollapsingTest {
        @Test
        public void testCollapser() throws Exception {
            HystrixRequestContext context = HystrixRequestContext.initializeContext();
            try {
               
                Future<String> f1 = new HelloWorldHystrixCollapser(1).queue();
                Future<String> f2 = new HelloWorldHystrixCollapser(2).queue();
//                System.out.println(new HelloWorldHystrixCollapser(1).execute()); // 这条很可能会合并到f1和f2的批量请求中
//                System.out.println(new HelloWorldHystrixCollapser(1).execute()); // 由于上面有IO打印,这条很可能不会合并到f1和f2的批量请求中
                Future<String> f3 = new HelloWorldHystrixCollapser(3).queue();
                Future<String> f4 = new HelloWorldHystrixCollapser(4).queue();

                Future<String> f5 = new HelloWorldHystrixCollapser(5).queue();
                // f5和f6,如果sleep时间够小则会合并,如果sleep时间够大则不会合并,默认10ms
//                TimeUnit.MILLISECONDS.sleep(10);
                Future<String> f6 = new HelloWorldHystrixCollapser(6).queue();              
          
                System.out.println(System.currentTimeMillis() + " : " + f1.get());
                System.out.println(System.currentTimeMillis() + " : " + f2.get());
                System.out.println(System.currentTimeMillis() + " : " + f3.get());
                System.out.println(System.currentTimeMillis() + " : " + f4.get());
                System.out.println(System.currentTimeMillis() + " : " + f5.get());
                System.out.println(System.currentTimeMillis() + " : " + f6.get());
                // 下面3条都不在一个批量请求中
//                System.out.println(new HelloWorldHystrixCollapser(7).execute());
//                System.out.println(new HelloWorldHystrixCollapser(8).queue().get());
//                System.out.println(new HelloWorldHystrixCollapser(9).queue().get());

                // note:numExecuted表示共有几个命令执行,1个批量多命令请求算一个,这个实际值可能比代码写的要多,因为due to non-determinism of scheduler since this example uses the real timer
                int numExecuted = HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size();
                System.out.println(System.currentTimeMillis() + " : " + "num executed: " + numExecuted);
                int numLogs = 0;
                for (HystrixInvokableInfo<?> command : HystrixRequestLog.getCurrentRequest().getAllExecutedCommands()) {
                    numLogs++;
                    
                    // assert the command is the one we're expecting
//                    assertEquals("CollepsingKey", command.getCommandKey().name());

                    System.out.println(System.currentTimeMillis() + " : " + command.getCommandKey().name() + " => command.getExecutionEvents(): " + command.getExecutionEvents());

                    // confirm that it was a COLLAPSED command execution
//                    assertTrue(command.getExecutionEvents().contains(HystrixEventType.COLLAPSED));
                    assertTrue(command.getExecutionEvents().contains(HystrixEventType.SUCCESS));
                }
                assertEquals(numExecuted, numLogs);
            } finally {
                context.shutdown();
            }
        }
}

hystrix支持N个请求自动合并为一个请求,这个功能在有网络交互的场景下尤其有用,比如每个请求都要网络访问远程资源,如果把请求合并为一个,将使多次网络交互变成一次,极大节省开销。重要一点,两个请求能自动合并的前提是两者足够“近”,即两者启动执行的间隔时长要足够小,默认为10ms,即超过10ms将不自动合并。

以demo为例,我们连续发起多个queue请求,依次返回f1~f6共6个Future对象,根据打印结果可知f1~f5同处一个线程,说明这5个请求被合并了,而f6由另一个线程执行,这是因为f5和f6中间隔了一个sleep,超过了合并要求的最大间隔时长。

请求熔断的demo:

package com.example.demo.hystrixdemo.circuitbreak;

import com.netflix.hystrix.*;
import org.junit.Test;

import java.io.IOException;
import java.util.Map;

/**
 * 
 * CircuitBreakerRequestVolumeThreshold设置为3,意味着10s内请求超过3次就触发熔断器
 * run()中无限循环使命令超时进入fallback,执行3次run后,将被熔断,进入降级,即不进入run()而直接进入fallback
 * 如果未熔断,但是threadpool被打满,仍然会降级,即不进入run()而直接进入fallback
 */
public class HystrixCommand4CircuitBreakerTest extends HystrixCommand<String> {

    private final String name;

    public HystrixCommand4CircuitBreakerTest(String name) {
        super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("CircuitBreakerTestGroup"))  
                .andCommandKey(HystrixCommandKey.Factory.asKey("CircuitBreakerTestKey"))
                .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("CircuitBreakerTest"))
                .andThreadPoolPropertiesDefaults(	// 配置线程池
                		HystrixThreadPoolProperties.Setter()
                		.withCoreSize(200)	// 配置线程池里的线程数,设置足够多线程,以防未熔断却打满threadpool
                )
                .andCommandPropertiesDefaults(	// 配置熔断器
                		HystrixCommandProperties.Setter()
                		.withCircuitBreakerEnabled(true)
                		.withCircuitBreakerRequestVolumeThreshold(3)
                		.withCircuitBreakerErrorThresholdPercentage(80)
//                		.withCircuitBreakerForceOpen(true)	// 置为true时,所有请求都将被拒绝,直接到fallback
//                		.withCircuitBreakerForceClosed(true)	// 置为true时,将忽略错误
//                		.withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE)	// 信号量隔离
//                		.withExecutionTimeoutInMilliseconds(5000)
                )
        );
        this.name = name;
    }

    @Override
    protected String run() throws Exception {
    	System.out.println("running run():" + name);
    	int num = Integer.valueOf(name);
    	if(num % 2 == 0 && num < 10) {	// 直接返回
    		return name;
    	} else {	// 无限循环模拟超时
    		int j = 0;
        	while (true) {
        		j++;
        	}
    	}
//		return name;
    }

    @Override
    protected String getFallback() {
        return "CircuitBreaker fallback: " + name;
    }

    public static class UnitTest {

        @Test
        public void testSynchronous() throws IOException {
        	for(int i = 0; i < 50; i++) {
	        	try {
	        		System.out.println("===========" + new HystrixCommand4CircuitBreakerTest(String.valueOf(i)).execute());
//	        		try {
//	            		TimeUnit.MILLISECONDS.sleep(1000);
//	            	}catch(Exception e) {}
//	        		Future<String> future = new HystrixCommand4CircuitBreakerTest("Hlx"+i).queue();
//	        		System.out.println("===========" + future);
	        	} catch(Exception e) {
	        		System.out.println("run()抛出HystrixBadRequestException时,被捕获到这里" + e.getCause());
	        	}
        	}

        	System.out.println("------开始打印现有线程---------");
        	Map<Thread, StackTraceElement[]> map=Thread.getAllStackTraces();
        	for (Thread thread : map.keySet()) {
				System.out.println(thread.getName());
			}
        	System.out.println("thread num: " + map.size());
        	
        }
    }

}
熔断机制相当于电路的跳闸功能,举个栗子,我们可以配置熔断策略为当请求错误比例在10s内>50%时,该服务将进入熔断状态,后续请求都会进入fallback。

以demo为例,我们通过withCircuitBreakerRequestVolumeThreshold配置10s内请求数超过3个时熔断器开始生效,通过withCircuitBreakerErrorThresholdPercentage配置错误比例>80%时开始熔断,然后for循环执行execute()触发run(),在run()里,如果name是小于10的偶数则正常返回,否则超时,通过多次循环后,超时请求占所有请求的比例将大于80%,就会看到后续请求都不进入run()而是进入getFallback(),因为不再打印"running run():" + name了。

请求缓存的demo:

package com.example.demo.hystrixdemo.cache;

import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext;
import org.junit.Test;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

/**
 * cache只有同在一个context中才生效
 * 通过HystrixRequestContext.initializeContext()初始化context,通过shutdown()关闭context
 */
public class HystrixCommand4RequestCacheTest extends HystrixCommand<Boolean> {

    private final int value;
    private final String value1;

    protected HystrixCommand4RequestCacheTest(int value, String value1) {
        super(HystrixCommandGroupKey.Factory.asKey("RequestCacheCommandGroup"));
        this.value = value;
        this.value1 = value1;
    }

    // 返回结果是cache的value
    @Override
    protected Boolean run() {
        return value == 0 || value % 2 == 0;
    }

    // 构建cache的key
    @Override
    protected String getCacheKey() {
        return String.valueOf(value) + value1;
    }

    public static class UnitTest {

        @Test
        public void testWithoutCacheHits() {
            HystrixRequestContext context = HystrixRequestContext.initializeContext();
            try {
                assertTrue(new HystrixCommand4RequestCacheTest(2,"HLX").execute());
                assertFalse(new HystrixCommand4RequestCacheTest(1,"HLX").execute());
                assertTrue(new HystrixCommand4RequestCacheTest(0,"HLX").execute());
                assertTrue(new HystrixCommand4RequestCacheTest(58672,"HLX").execute());
            } finally {
                context.shutdown();
            }
        }

        @Test
        public void testWithCacheHits() {
            HystrixRequestContext context = HystrixRequestContext.initializeContext();
            try {
                HystrixCommand4RequestCacheTest command2a = new HystrixCommand4RequestCacheTest(2,"HLX");
                HystrixCommand4RequestCacheTest command2b = new HystrixCommand4RequestCacheTest(2,"HLX");
                HystrixCommand4RequestCacheTest command2c = new HystrixCommand4RequestCacheTest(2,"HLX1");

                assertTrue(command2a.execute());
                // this is the first time we've executed this command with the value of "2" so it should not be from cache
                assertFalse(command2a.isResponseFromCache());

                assertTrue(command2b.execute());
                // this is the second time we've executed this command with the same value so it should return from cache
                assertTrue(command2b.isResponseFromCache());
                
                assertTrue(command2c.execute());
                assertFalse(command2c.isResponseFromCache());
            } finally {
                context.shutdown();
            }

            // start a new request context
            context = HystrixRequestContext.initializeContext();
            try {
                HystrixCommand4RequestCacheTest command3a = new HystrixCommand4RequestCacheTest(2,"HLX");
                HystrixCommand4RequestCacheTest command3b = new HystrixCommand4RequestCacheTest(2,"HLX");
                assertTrue(command3a.execute());
                // this is a new request context so this should not come from cache
                assertFalse(command3a.isResponseFromCache());
                // 从command3a.execute()执行中得到的cache
                command3b.execute();
                assertTrue(command3b.isResponseFromCache());
            } finally {
                context.shutdown();
            }
        }
    }

}
hystrix支持将一个请求结果缓存起来,下一个具有相同key的请求将直接从缓存中取出结果,减少请求开销。要使用hystrix cache功能,第一个要求是重写getCacheKey(),用来构造cache key;第二个要求是构建context,如果请求B要用到请求A的结果缓存,A和B必须同处一个context。通过HystrixRequestContext.initializeContext()和context.shutdown()可以构建一个context,这两条语句间的所有请求都处于同一个context。
以demo的testWithCacheHits()为例,command2a、command2b、command2c同处一个context,前两者的cache key都是2HLX(见getCacheKey()),所以command2a执行完后把结果缓存,
command2b执行时就不走run()而是直接从缓存中取结果了,而command2c的cache key是2HLX1,无法从缓存中取结果。此外,通过isResponseFromCache()可检查返回结果是否来自缓存。

源码地址:

https://github.com/PerseveranceForever/hystrix_demo.git 

猜你喜欢

转载自blog.csdn.net/shixuetanlang/article/details/80643890