在服务化系统中,对于上下游服务的依赖调用每每是经过RPC接口调用实现的,为了系统稳定性,防止被上游服务超时hang死,咱们须要对接口调用设置超时,若是在设置的超时时间内没有响应,则须要提前中断该请求并返回。tomcat
好比下游接口对于咱们的超时时间限制是150ms,由于业务特色缘由,咱们须要对上游服务某个接口调用设置50ms超时,若是在指定时间内没有返回,则返回降级数据。并发
Future超时ide
说到超时中断不少人第一个想到的是Future中断。好比请求线程是一个tomcat线程池中的线程,能够经过线程池返回Future,能够轻松实现超时中断返回,这种方式也是咱们使用比较多的方案,由于线程池并行调用在高并发场景下有不少的应用,因此直接借助Future方式中断是最早想到的方法。高并发
若是有些场景不想额外引入线程池,又拿不到Future有什么其余方式吗?性能
线程中断线程
之前线程提供了Thread.stop,Thread.suspend,Thread,resume方法,可是这几个方法都已经废弃了。目前实现线程中断最早想到的就是interrupt()方法。code
interrupt()方法并非进行线程中断,而仅仅是通知线程你能够中断了,可是是否中断仍是取决于线程的运行状态,由其自身决定。接口
好比调用一个线程的interrupt()以后,若是线程处于阻塞状态(包括:wait,sleep,join等方法),则线程会退出并返回InterruptedException异常,代码中catch这个异常后就能够继续处理了。rpc
若是线程一直在执行没有处于阻塞,则不会中断线程。可是在RPC调用场景中,请求线程通常会处于阻塞状态等待数据,因此能够经过interrupt()方法执行中断。get
知道了中断方法了,如何经过指定超时时间进行中断呢?
首先想到的是单独有一个延迟task专门去搞定线程中断的事情。
ScheduledFuture<?> f = executor.scheduleAtFixedRate(task,timeout,timeout,TimeUnit.MILLISECONDS);
Runnable task = new Runnable(){ @Override public void run(){ try{ thread.interrupt(); // 取消定时器任务 f.cancel(); } catch(Exception e){ logger.error("Failed while ticking TimerListener",e); } } };
在进行rpc调用时,同时提交一个中断检测任务到ScheduledFuture中等待执行,若是在指定时间内rpc没有返回,则会触发延迟任务,执行请求线程的interrupt()方法,实现了请求线程的中断了,以后清除掉定时任务就OK了。
若是RPC调用在指定时间内返回,也须要清除定时任务,同时恢复请求线程中的中断标识,执行当前线程(即请求线程)的isInterrupted方法。
Thread.interrupted();
这种方式实现中断的问题是,在QPS很高状况下会存在额外性能损失,由于须要开一个任务线程池等待执行。
ReentrantLock.lock(),Condition.await,Condition.signalAll()
另外一种方式是采用线程wait和notify方式。也是我比较喜欢的方式。
private final Lock lock = new ReentrantLock(); private final Condition done = lock.newCondition(); private volatile Object response;
提交请求同时提交检测任务:
private void invokeTimeout(int timeout){ if(timeout <= 0){ timeout = 20; } // 检测服务提供方是否成功返回了调用结果 if (!isDone()) { long start = System.currentTimeMillis(); System.out.println("lock get message start"); lock.lock(); try { // 循环检测服务提供方是否成功返回了调用结果 while (!isDone()) { // 若是调用结果还没有返回,这里等待一段时间 System.out.println("lock get message wait"); done.await(timeout, TimeUnit.MILLISECONDS); // 若是调用结果成功返回,或等待超时,此时跳出 while 循环,执行后续的逻辑 if (isDone() || System.currentTimeMillis() - start > timeout) { break; } } } catch (InterruptedException e) { throw new RuntimeException(e); } finally { lock.unlock(); } // 若是调用结果仍未返回,则抛出超时异常 if (!isDone()) { throw new RuntimeException("timeout"); } } System.out.println("lock get message finish"); } private boolean isDone(){ // 经过检测 response 字段为空与否,判断是否收到了调用结果 return response != null; }
// RPC-Invoke response = new Object(); done.signalAll();
在RPC调用过程当中,若是结果返回,发送signal(),通知await()同时复制response,执行break返回。若是在指定await()时间内没有返回,同时response无值,则抛出RuntimeException业务进行捕获。
若是你有更好的方式欢迎留言赐教。