介面呼叫超時的實現原理

尋覓beyond發表於2020-06-28

  平常開發過程中,如果涉及到RPC呼叫,對於服務呼叫方和服務提供方,都是可以設定介面超時時間的。以呼叫方為例,呼叫方需要呼叫遠端的一個介面,為了保證服務的質量,一般會設定呼叫介面的超時時間,比如將呼叫介面的超時時間設定為1秒,當呼叫遠端介面後,經過1秒還未拿到結果,那麼就認為是超時了,呼叫方就不會繼續等待服務提供方返回結果,而是直接丟擲一個SocketTimeOutException。

  其實不僅僅是RPC介面呼叫需要設定超時時間,資料庫、快取這些一樣的,一般都會設定超時時間,不能讓程式無休止的等待下去。

  那麼問題來了!!應用或者說框架,是如何設定超時時間的呢?我們設定超時時間為1秒,那麼程式是怎麼保證1秒後就停止呼叫了呢?這也就是本文要介紹的,介面呼叫設定超時時間的原理。

  本文將以設定呼叫資料庫超時為例,介紹設定超時時間是怎麼實現的,明白這個原理後,其他比如RPC呼叫、快取呼叫的超時原理也就明白。

 

CallUtils-帶有超時的執行工具類

  下面是一個CallUtils,包含一個執行緒池和execute方法,execute方法中預設超時時間為1秒。

package cn.ganlixin.util;
import java.util.concurrent.*;

/**
 * 帶有超時的任務執行器
 */
public class CallUtils {

    /**
     * 用於執行任務的執行緒池
     */
    private static ExecutorService executorService = Executors.newFixedThreadPool(5);

    /**
     * 執行任務,並且超時時間為1秒
     *
     * @param callable 需要執行的任務
     * @param <T>      任務執行完畢後返回值型別
     * @return 任務結果
     */
    public static <T> T execute(Callable<T> callable) {

        // 提交任務
        Future<T> future = executorService.submit(callable);

        try {
            return future.get(1, TimeUnit.SECONDS);
        } catch (InterruptedException | ExecutionException | TimeoutException e) {
            e.printStackTrace();
        }

        return null;
    }
}

  

UserService-將呼叫資料庫

  UserService使用CallUtils來執行資料庫操作,因為CallUtils設定有呼叫超時:

package cn.ganlixin.service;

import cn.ganlixin.dao.UserDAO;
import cn.ganlixin.model.User;
import cn.ganlixin.util.CallUtils;

import java.util.List;

public class UserService {

    private UserDAO userDAO = new UserDAO();

    public List<User> getAllUser() {
        return CallUtils.execute(() -> userDAO.getAllUserFromDB());
    }
}

  

UserDAO-耗時的資料庫操作

  userDAO中,作為測試,只提供了一個getAllUserFromDB方法,休眠2秒來模擬資料庫操作,因為UserService使用CallUtils來呼叫該方法,CallUtils的超時時間為1秒,所以該資料庫操作必定會超時:

package cn.ganlixin.dao;
import cn.ganlixin.model.User;
import java.util.List;
import java.util.concurrent.TimeUnit;

public class UserDAO {

    /**
     * 從DB獲取全量user列表
     */
    public List<User> getAllUserFromDB() {
        try {
            // 模擬資料庫操作,耗時2秒
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        return null;
    }
}

  

進行測試

  建立測試類,測試UserService的getAllUser方法,因為getAllUser方法中使用CallUtils來呼叫資料庫操作,CallUtils的超時時間為1秒,而資料庫操作需要2秒,所以getAllUser方法必然會超時。

package cn.ganlixin.test;
import cn.ganlixin.service.UserService;
import org.junit.Test;

public class TestUserService {

    public UserService userService = new UserService();

    @Test
    public void testGetAllUser() {
        userService.getAllUser();
    }
}

  執行測試,輸出如下:

  

 

 

總結

  本文演示了介面超時呼叫的原理,實現介面呼叫超時,無非是通過將任務提交到執行緒池後,使用future.get,設定超時時間即可。

  上面的程式碼很多細節都不太規範,比如涉及到資料庫的超時,應該是資料庫連線池的超時配置,而我在演示時是直接使用CallUtils來替代了,但是明白這個原理就OK。

 

相關文章