在服務化系統中,對於上下游服務的依賴調用每每是經過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業務進行捕獲。
若是你有更好的方式歡迎留言賜教。