接口調用實現請求超時中斷,你有幾種方法?

背景

在服務化系統中,對於上下游服務的依賴調用每每是經過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業務進行捕獲。

其餘方式有哪些?

  • timingwheel,dubbo已經在用了
  • 定時通知+線程掃描

若是你有更好的方式歡迎留言賜教。

相關文章
相關標籤/搜索