timeout
在调用第三方服务时有些情况需要对服务响应时间进行把控,当超时的情况下进行fallback的处理
下面来看下超时的案例public class CommandTimeout extends HystrixCommand{ 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 + "!"; }}
接下来是测试方法
@Testpublic 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 endHello Failure World!
可以看到sleep被强制interrupted,并且调用的输出也变成了fallback方法的返回值
如何查看是哪里调用的interrupt方法
这里顺便说下如何看是哪个方法调用的interrupt
根据的一个答案,没有直接的方法来断点到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的执行
//创建一个TimerListenerpublic ReferenceaddTimerListener(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{ 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); }}