首頁 > Java > Java面試題 > 主體

5個Dubbo面試題,含金量超高!

發布: 2023-08-17 16:04:16
轉載
1010 人瀏覽過

今天,帶給大家一篇關於Dubbo IO互動的文章。

本文是一位同事所寫,用有趣的文字把枯燥的知識點寫出來,通俗易懂,非常有意思,所以迫不及待找作者授權然後分享給大家:


一些有趣的問題

Dubbo是一個優秀的RPC框架,其中有錯綜複雜的線程模型,這篇文章筆者從自己淺薄的認知中,來剖析Dubbo的整個IO過程。在開始之前,我們先來看以下幾個問題:

  • 業務方法執行之後,資料包就發出去了嗎?
  • netty3和netty4在執行緒模型上有什麼不同?
  • 封包到了作業系統socket buffer,經歷了什麼?
  • Provider打出的log耗時很小,而Consumer端卻超時了,怎麼可以排查到問題?
  • 封包在物理層是一根管道就直接發過去嗎?
  • Consumer 業務執行緒await在Condition上,在哪個時機被喚醒?
  • ……

接下來筆者將以Dubbo2.5.3 作為Consumer,2.7.3作為Provider來講述整個互動過程,筆者站在資料包視角,用第一人稱來敘述,繫上安全帶,我們出發咯。

有意思的旅行

#1、Dubbo2.5.3 Consumer端發起請求

#我是一個資料包,出生在一個叫Dubbo2.5.3 Consumer的小鎮,我的使命是是傳遞訊息,同時也喜歡出門旅行。

某一天,我即將被送出去,據說是要去一個叫Dubbo 2.7.3 Provider的地方。

這一天,業務線程發起發起方法調用,在FailoverClusterInvoker#doInvoke我選擇了一個Provider,然後經過各種Consumer Filter,再經過Netty3的pipeline,最後通過NioWorker#scheduleWriteIfNecessary 方法,我來到了NioWorker的writeTaskQueue隊列中。

當我回頭看主線程時,發現他在DefaultFuture中的Condition等待,我不知道他在等什麼,也不知道他要等多久。

我在writeTaskQueue隊列排了一會兒隊,看到netty3 IO worker線程在永不停歇的執行run方法,大家都稱這個為死循環。

最後,我很幸運,NioWorker#processWriteTaskQueue選擇了我,我被寫到作業系統的Socket緩衝區,我在緩衝區等待,反正時間充足,我回味一下今天的旅行,期間我輾轉了兩個旅行團,分別叫主線程和netty3 IO worker線程,嗯,兩個旅行團服務都不錯,效率很高。

索性我把今天的見聞記錄下來,畫成一張圖,當然不重要的地方我就忽略了。

5個Dubbo面試題,含金量超高!

2、作業系統發送封包

我在作業系統socket緩衝區,經過了很多神奇的事情。

  1. 在一個叫傳輸層的地方給我追加上了目標連接埠號碼、來源連接埠號碼

  2. #在一個叫做網路層的地方為我追加上了目標IP、來源IP,同時透過目標IP與遮罩做與運算,找到「下一跳」的IP

  3. #在一個叫資料鏈結層的地方透過ARP協定給我追加上了「下一跳」的目標MAC位址、來源MAC位址

最有意思的是,我們坐的都是一段一段纜車,每換一個纜車,就要修改目標MAC位址、源MAC位址,後來問了同行的封包小夥伴,這個模式叫做“下一跳”,一跳一跳的跳過去。這裡有很多資料包,體型大的單獨一個纜車,體型小的幾個擠一個纜車,還有一個可怕的事情,體型再大一點,要分拆做多個纜車(雖然這對我們數據包沒啥問題),這個叫拆包和黏包。期間我們經過交換器、路由器,這些地方玩起來很Happy。

當然也有不愉快的事情,就是擁堵,目的地纜車滿了,來不及被拉走,只能等待咯。

3、在Provider端的經歷

#好不容易,我來到了目的地,我坐上了一個叫「零拷貝」號的快艇,迅速到了netty4,netty4果然富麗堂皇,經過NioEventLoop#processSelectedKeys,再經過pipeline中的各種入站handler,我來到了AllChannelHandler的線程池,當然我有很多選擇,但是我隨便選了一個目的地,這裡會經歷解碼、一系列的Filter,才會來的目的地“業務方法”,NettyCodecAdapter#InternalDecoder解碼器很厲害,他可以處理拆包和粘包。

5個Dubbo面試題,含金量超高!

在AllChannelHandler的執行緒池中我會停留一會兒,所以我也畫了一張圖,記錄旅程。

5個Dubbo面試題,含金量超高!

自此,我的旅行結束,新的故事將由新的資料包續寫。

4、Provider端產生了新的封包

我是一個封包,出生在一個叫Dubbo2.7.3 Provider的小鎮,我的使命是去喚醒命中註定的線程,接下來我會開始一段旅行,去一個叫Dubbo2.5.3 Consumer的地方。

在Provider業務方法執行之後

  • 由業務執行緒經過io.netty.channel.AbstractChannelHandlerContext#writeAndFlush
  • ##再經過
    io.netty.util .concurrent.SingleThreadEventExecutor#execute 執行addTask
  • 將任務放入佇列
    io.netty.util.concurrent.SingleThreadEventExecutor#taskQueue
  • 我便跟著
    io.netty.channel.AbstractChannelHandlerContext$WriteTask等待NioEventLoop發車,等待的過程中,我記錄了走過的腳步。
5個Dubbo面試題,含金量超高!
在這裡,我看到NioEventLoop是一個死循環,不停地從任務隊列取任務,執行任務

AbstractChannelHandlerContext.WriteAndFlushTask ,然後指引我們到socket緩衝區等候,永不知疲倦,我似乎領略到他身上有一種倔強的、追求極致的匠人精神。

經過

io.netty.channel.AbstractChannel.AbstractUnsafe#write,我到達了作業系統socket緩衝區。在作業系統層面和大多數資料包一樣,也是做纜車達到目的地。

5、到達dubbo 2.5.3 Consumer端

到達dubbo 2.5.3 Consumer端,我在作業系統socket緩衝區等了一會兒,同樣是坐了「零拷貝號」快艇,到達了真正的目的地dubbo 2.5.3 Consumer,在這裡我發現,NioWorker#run是一個死循環,然後執行NioWorker#processSelectedKeys ,透過NioWorker#read方式讀出來,我就到達了AllChannelHandler的執行緒池,這是一個業務執行緒池。

我在這裡等待一會,等任務被調度,我看見com.alibaba.dubbo.remoting.exchange.support.DefaultFuture#doReceived被執行了,同時Condition的signal被執行了。我在遠處看到了一個被阻塞線程被喚醒,我似乎明白,因為我的到來,喚醒了一個沉睡的線程,我想這應該是我生命的意義。

至此,我的使命也完成了,這次旅程結束。

總結netty3和netty4的執行緒模型

#我們根據兩個封包的自述,來總結一下netty3和netty4的執行緒模型。

1、netty3寫入過程

5個Dubbo面試題,含金量超高!

#2、Netty4的讀寫過程

5個Dubbo面試題,含金量超高!

說明:這裡沒有netty3的讀取過程,netty3讀過程和netty4相同,pipeline是由IO執行緒執行。

總結:netty3與netty4線程模型的差異在於寫入過程,netty3中pipeline由業務線程執行,而netty4無論讀寫,pipeline統一由IO線程執行。

netty4中ChannelPipeline中的Handler鏈統一由I/O執行緒串列調度,無論是讀取或寫入操作,netty3中的write操作時由業務執行緒處理Handler鏈。 netty4中可以降低執行緒之間的上下文切換所帶來的時間消耗,但是netty3中業務執行緒可以並發執行Handler鏈。如果有一些耗時的Handler操作會導致netty4的效率低下,但是可以考慮將這些耗時操作放在業務執行緒最先執行,不放在Handler裡處理。由於業務執行緒可以並發執行,同樣也可以提高效率。

一些疑難題排查

有遇到一些比較典型的疑難問題,例如當Provider答應的didi.log耗時正常,而Consumer端超時了,此時有如下排查方向,didi.log的Filter其實處於非常裡層,往往無法反映真實的業務方法執行情況。

  1. Provider除了業務方向執行外,序列化也有可能是耗時的,所以可以用arthas監控最外側方法org.apache.dubbo.remoting.transport.DecodeHandler #received,排除業務方法耗時高的問題

  2. Provider中資料包寫入是否耗時,監控io.netty.channel.AbstractChannelHandlerContext#invokeWrite方法

  3. 透過netstat 也能查看目前tcp socket的一些訊息,例如Recv-Q, Send-Q,Recv-Q是已經到了接受緩衝區,但是還沒被應用程式碼讀走的資料。 Send-Q是已經到了發送緩衝區,但是對方還沒有回覆Ack的資料。這兩種數據正常一般不會堆積,如果堆積了,可能就有問題了。

5個Dubbo面試題,含金量超高!
  1. #看Consumer NioWorker#processSelectedKeys (dubbo2.5.3)方法是否耗時高。

  2. 直到最終整個連結的所有細節…問題肯定是可以解決的。

尾聲

#在整個互動過程中,筆者省略執行緒堆疊呼叫的一些細節和來源程式碼的細節,例如序列化與反序列化,dubbo怎麼讀出完整的資料包的,業務方法執行前那些Filter是怎麼排序和分佈的,netty的Reactor模式是如何實現的。這些都是非常有趣的問題… 

以上是5個Dubbo面試題,含金量超高!的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:Java后端技术全栈
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!