dubbo的異步調用

        不可否認dubbo是一款十分棒的架構設計,但是可能對它瞭解的不夠,在使用過程中也是遇到了很多問題。發現時間久了之前一些遇到的問題沒有落文檔,慢慢也都忘了,這是一種損失我認爲。所以就最近一次遇到的問題做一下筆記,如果有大佬看到有問題的地方可以指正一下。

       目前的項目由於應用分的比較徹底,各應用之間的dubbo也是比較常見頻繁,於是也就出現了一系列的服務治理問題,期間在服務器上搭建了dubbo官方提供的admin和monitor(監視器)等來觀察和調整dubbo服務的一些健康狀況。比如某些服務的響應時間是否過長,服務調用的機器是否權重合理等等。不過這一般也是在做技術優化時纔有時間去搞,就比如這次,想做一次頁面數據接口的優化。

      優化過程如下:習慣性首先對db層做了一些sql優化,對於一些索引失效倖存下來的語句改善了一下,發現效果不是很明顯。然後讀了一遍自己寫的業務代碼(還好有註釋,不然優化方案可能到此結束了,直接選擇升配置了...)。發現的問題如下:

1.首先是我們的數據緩存問題,由於redis是自己在一臺服務器上搭建的,所以一直感覺性能有問題,主要體現的redis的請求連接上,這個一直還沒找到很好的解決方案,打算切到一些官方的redis服務上去。但是暫時不算整個切換(因爲歷史原因,不是所有緩存都做了持久化,估計是當時的前輩在看到RDB或AOF開啓後服務器的cpu後又關閉了)所以在查看數據和日誌之後把一部分冷數據從redis中切到es裏,這步結束時接口整體性能快了有十幾毫秒平均...可見自己搭建的redis性能有多可怕。

2.再者是之前提到的由於目前的項目分的比較徹底,相應的各種模塊應用比較多,所以在一次業務請求的接口中可能涉及了多個dubbo服務, 但是很多服務調用回來的數據彼此之間並不存在前後依賴關係,但是由於業務的流程導致它們串行化(dubbo請求的響應時間被累加了)。打算做異步化。

       dubbo的異步化調用其實在官方的給出的api文檔也有的,它內部大致的實現就是如下了,會去讀取當前的服務是否需要支持異步(調用方來配置,走xml或者springboot註解化都可以,其實就是給某個服務bean加入一個配置參數),看下面的源碼大致是這樣處理的:首先用一些反射、代理的手段去獲取需要執行的遠程方法和執行器,中間ExchangeClient做了一下負載均衡處理,然後會根據你的配置是否需要異步化和服務是否有返回值來走不通的邏輯,實現異步的原理大體上也和多線程的Callable一樣,只不過它把一些處理結果放到rpc的上下文裏的。

public class DubboInvoker<T> extends AbstractInvoker<T> {
    
    private final ExchangeClient[] clients;
    
    protected Result doInvoke(final Invocation invocation) throws Throwable {
        RpcInvocation inv = (RpcInvocation) invocation;
        final String methodName = RpcUtils.getMethodName(invocation);
        // 設置 path 和 version 到 attachment 中
        inv.setAttachment(Constants.PATH_KEY, getUrl().getPath());
        inv.setAttachment(Constants.VERSION_KEY, version);

        ExchangeClient currentClient;
        if (clients.length == 1) {
            // 從 clients 數組中獲取 ExchangeClient
            currentClient = clients[0];
        } else {
            currentClient = clients[index.getAndIncrement() % clients.length];
        }
        try {
            // 獲取異步配置
            boolean isAsync = RpcUtils.isAsync(getUrl(), invocation);
            // isOneway 爲 true,表示“單向”通信
            boolean isOneway = RpcUtils.isOneway(getUrl(), invocation);
            int timeout = getUrl().getMethodParameter(methodName, Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);

            // 異步無返回值
            if (isOneway) {
                boolean isSent = getUrl().getMethodParameter(methodName, Constants.SENT_KEY, false);
                // 發送請求
                currentClient.send(inv, isSent);
                // 設置上下文中的 future 字段爲 null
                RpcContext.getContext().setFuture(null);
                // 返回一個空的 RpcResult
                return new RpcResult();
            } 

            // 異步有返回值
            else if (isAsync) {
                // 發送請求,並得到一個 ResponseFuture 實例
                ResponseFuture future = currentClient.request(inv, timeout);
                // 設置 future 到上下文中
                RpcContext.getContext().setFuture(new FutureAdapter<Object>(future));
                // 暫時返回一個空結果
                return new RpcResult();
            } 

            // 同步調用
            else {
                RpcContext.getContext().setFuture(null);
                // 發送請求,得到一個 ResponseFuture 實例,並調用該實例的 get 方法進行等待
                return (Result) currentClient.request(inv, timeout).get();
            }
        } catch (TimeoutException e) {
            throw new RpcException(..., "Invoke remote method timeout....");
        } catch (RemotingException e) {
            throw new RpcException(..., "Failed to invoke remote method: ...");
        }
    }
    
    // 省略其他方法
}

        文檔看完後發覺好像還有些問題,因爲業務的特殊,在某幾個應用中同一個dubbo服務bean我希望它只在一些接口中展現它的異步化能力。最後內部給的配置配起來有點困難...所以最後只好,外部“配置”了。

       解決方案如下:其實也就是對於原本串行調用的一些業務在不影響業務邏輯的情況下,用多線程改爲並行,最大可能使得dubbo調用所產生的的網絡消耗時間最小化。第一版用“閥門”寫了一版,後面是直接用Callable寫了,因爲其實我需要的只是把dubbo調用的過程並行化,最終這些接口的返回還是需要所有這些生產“原料”齊全纔可以的,而有返回值的Callable,它結果的獲取方法中future.get()又是阻塞的,所以沒必要用CountDownLatch/CyclicBarrier這些閥門來卡主線程。中間可以根據業務的需要加一些超時和異常處理機制。優化結果是再減少十幾毫秒。dubbo的異步化感覺還是像策略模式一樣可以自由選擇配置會舒服點,之前用的hsf好像是可以自由選擇的,實現源碼還沒來得及看血虧...

ExecutorService EXECUTOR=new ThreadPoolExecutor(
                20,
                20,
                0,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(),
                new DefaultThreadFactory("業務線程池"));

.......
//並行化調用多個資源服務
Future businessFuture1 = EXECUTOR.submit(()->{
            //查詢業務資源1
            return dubboServie1.method(...);});
Future businessFuture2 = EXECUTOR.submit(()->{
            //查詢業務資源2
            return dubboServie1.method(...);});
...
...
//數據資源收集
Object o1;
Object o2;
...
try { 
o1=businessFuture1.get(); 
o2=businessFuture2.get();
...
}catch(Exception e){
//異常時(主要是超時)自定義返回結果
}
//數據組裝

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章