timeout

在调用第三方服务时有些情况需要对服务响应时间进行把控,当超时的情况下进行fallback的处理
下面来看下超时的案例

public class CommandTimeout extends HystrixCommand<String> {

    private final String name;

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

    @Override
    protected String run()  {
    	System.out.println("aaaa");
    	try {
			//sleep10秒强制超时,默认超时时间是1s
			Thread.sleep(10000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
    	System.out.println("run end");
    	return "";
    }
    @Override
    protected String getFallback() {
        return "Hello Failure " + name + "!";
    }
}

接下来是测试方法

@Test
public void testSynchronous() throws InterruptedException {
	System.out.println(new CommandHelloWorld("World").execute());
}
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at hystrix.CommandHelloWorld.run(CommandHelloWorld.java:32)
	at hystrix.CommandHelloWorld.run(CommandHelloWorld.java:1)
	...
run end
Hello Failure World!

可以看到sleep被强制interrupted,并且调用的输出也变成了fallback方法的返回值

如何查看是哪里调用的interrupt方法

这里顺便说下如何看是哪个方法调用的interrupt
根据stackoverflow的一个答案,没有直接的方法来断点到interrupt的方法,只能通过在Thread的interrupt方法上打断点,再反向看栈信息得知哪里中断当前线程。

如何改变timeout设置

在HystrixCommand的够着方法中可以在第二个参数配置一个timeout的毫秒数

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

这个构造方法是在调用AbastractCommand的构造方法时将毫秒数配置在CommandProperties中,如下:

super(group, null, null, null, null, HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(executionIsolationThreadTimeoutInMilliseconds), null, null, null, null, null, null);

Hystrix的timeout是怎么运行的

在运行对应的command时,Hystrix会通过HystrixObservableTimeoutOperator注册一个Timer到一个定时线程池中,当超时后会启用一个HystrixTimer线程来interruptCommand的执行

//创建一个TimerListener
public Reference<TimerListener> addTimerListener(final TimerListener listener) {
    startThreadIfNeeded();
    // add the listener

    Runnable r = new Runnable() {

        @Override
        public void run() {
            try {
                listener.tick();
            } catch (Exception e) {
                logger.error("Failed while ticking TimerListener", e);
            }
        }
    };
	//通过ScheduledThreadPoolExecutor在到达超时时间时运行上面的listener.tick,而时间是从listener的getIntervalTimeInMilliseconds方法中获得的
    ScheduledFuture<?> f = executor.get().getThreadPool().scheduleAtFixedRate(r, listener.getIntervalTimeInMilliseconds(), listener.getIntervalTimeInMilliseconds(), TimeUnit.MILLISECONDS);
    //返回包含timer的Reference,在任务在规定时间内完成是用于cancel超时处理
	return new TimerReference(listener, f);
}
//下面是上面listener的定义
TimerListener listener = new TimerListener() {

    @Override
    public void tick() {
        // if we can go from NOT_EXECUTED to TIMED_OUT then we do the timeout codepath
        // otherwise it means we lost a race and the run() execution completed or did not start
		//这里使用CAS的操作将将状态设置为TIME_OUT,使用CAS的原因是如果运行成功而timeout没有被取消时不会标记任务超时
        if (originalCommand.isCommandTimedOut.compareAndSet(TimedOutStatus.NOT_EXECUTED, TimedOutStatus.TIMED_OUT)) {
            // report timeout failure
            originalCommand.eventNotifier.markEvent(HystrixEventType.TIMEOUT, originalCommand.commandKey);
            // shut down the original request
			//内部会取消当前并调用fallback
            s.unsubscribe();
            timeoutRunnable.run();
            //if it did not start, then we need to mark a command start for concurrency metrics, and then issue the timeout
        }
    }

    @Override
    public int getIntervalTimeInMilliseconds() {
		//这里从command的配置中获得配置的超时时间
        return originalCommand.properties.executionTimeoutInMilliseconds().get();
    }
};

上面是Command超时后的处理操作,当Command在时间内完成时会调用TimeReference的clear方法,内部调用了future的cancel来取消timer的超时任务

private static class TimerReference extends SoftReference<TimerListener> {

    private final ScheduledFuture<?> f;
	//保存scheduledFuture
    TimerReference(TimerListener referent, ScheduledFuture<?> f) {
        super(referent);
        this.f = f;
    }

    @Override
    public void clear() {
        super.clear();
        // stop this ScheduledFuture from any further executions
		//停止scheduledFuture
        f.cancel(false);
    }

}