Spark RPC 到底是個什麼鬼?

hahadelphi發表於2021-09-09

本文會為大家介紹Spark中的RPC通訊機制,詳細闡述“Spark RPC到底是個什麼鬼?”,閒話少敘,讓我們來進入Spark RPC的世界!

Spark RPC三劍客

Spark RPC中最為重要的三個抽象(“三劍客”)為:RpcEnv、RpcEndpoint、RpcEndpointRef,這樣做的好處有:

  • 對上層的API來說,遮蔽了底層的具體實現,使用方便

  • 可以透過不同的實現來完成指定的功能,方便擴充套件

  • 促進了底層實現層的良性競爭,Spark 1.6.3中預設使用了Netty作為底層的實現,但Akka的依賴依然存在;而Spark 2.1.0中的底層實現只有Netty,這樣使用者可以方便的使用不同版本的Akka或者將來某種更好的底層實現

下面我們就結合Netty和“三劍客”來具體分析他們是如何來協同工作的。

Send a message locally

我們透過Spark原始碼中的一個Test()來分析一下傳送本地訊息的具體流程,原始碼如下(對原始碼做了一些修改):

  test("send a message locally") {    @volatile var message: String = null
    val rpcEndpointRef = env.setupEndpoint("send-locally", new RpcEndpoint {      override val rpcEnv = env      override def receive = {        //case msg: String => message = msg
        case msg: String => println(message)  //我們直接將接收到的訊息列印出來
      }
    })
    rpcEndpointRef.send("hello")    //下面是原來的程式碼
    //eventually(timeout(5 seconds), interval(10 millis)) {
    //  assert("hello" === message)
    //}
  }

為了方便理解,先把流程圖貼出來,然後詳細進行闡述:

圖片描述

下面我們來詳細闡述上例的具體過程:

首先是RpcEndpoint建立並註冊的流程:(圖中的藍色線條部分)

  • 1、建立RpcEndpoint,並初始化rpcEnv的引用(RpcEnv已經建立好,底層實際上是例項化了一個NettyRpcEnv,而NettyRpcEnv是透過工廠方法NettyRpcEnvFactory建立的)

  • 2、例項化RpcEndpoint之後需要向RpcEnv註冊該RpcEndpoint,底層實現是向NettyRpcEnv進行註冊,而實際上是透過呼叫Dispatcher的registerRpcEndpoint方法向Dispatcher進行註冊

  • 3、具體的註冊就是向endpoints、endpointRefs、receivers中插入記錄:而receivers中插入的資訊會被Dispatcher中的執行緒池中的執行緒執行:會將記錄take出來然後呼叫Inbox的process方法透過模式匹配的方法進行處理,註冊的時候透過匹配到OnStart型別的message,去執行RpcEndpoint的onStart方法(例如Master、Worker註冊時,就要執行各自的onStart方法),本例中未做任何操作

  • 4、註冊完成後返回RpcEndpointRef,我們透過RpcEndpointRef就可以向其代表的RpcEndpoint傳送訊息

下面就是透過RpcEndpointRef向其代表的RpcEndpoint傳送訊息的具體流程:(圖中的紅色線條部分)

  • 1、2、呼叫RpcEndpointRef的send方法,底層實現是呼叫Netty的NettyRpcEndpointRef的send方法,而實際上又是呼叫的NettyRpcEnv的send方法,傳送的訊息使用RequestMessage進行封裝:

nettyEnv.send(RequestMessage(nettyEnv.address, this, message))
  • 3、4、NettyRpcEnv的send方法首先會根據RpcAddress判斷是本地還是遠端呼叫,此處是同一個RpcEnv,所以是本地呼叫,即呼叫Dispatcher的postOneWayMessage方法

  • 5、postOneWayMessage方法內部呼叫Dispatcher的postMessage方法

  • 6、postMessage會向具體的RpcEndpoint傳送訊息,首先透過endpointName從endpoints中獲得註冊時的EndpointData,如果不為空就執行EndpointData中Inbox的post(message)方法,向Inbox的mesages中插入一條InboxMessage,同時向receivers中插入一條記錄,此處將Inbox單獨畫出來是為了方便大家理解

  • 7、Dispatcher中的執行緒池會拿出一條執行緒用來迴圈receivers中的訊息,首先使用take方法獲得receivers中的一條記錄,然後呼叫Inbox的process方法來執行這條記錄,而process將messages中的一條InboxMessage(第6步中插入的)拿出來進行處理,具體的處理方法就是透過模式匹配的方法,匹配到訊息的型別(此處是OneWayMessage),然後來執行RpcEndpoint中對應的receive方法,在此例中我們只列印出這條訊息(步驟8)

至此,一個簡單的傳送本地訊息的流程執行完成。

什麼,上面的圖太複雜了?我也覺得,下面給出一張簡潔的圖:

圖片描述

我們透過NettyRpcEndpointRef來發出一個訊息,訊息經過NettyRpcEnv、Dispatcher、Inbox的共同處理最終將訊息傳送到NettyRpcEndpoint,NettyRpcEndpoint收到訊息後進行處理(一般是透過模式匹配的方式進行不同的處理)

如果進一步的進行抽象就得到了我們剛開始所講的“三劍客”:RpcEnv、RpcEndpoint、RpcEndpointRef

圖片描述

RpcEndpointRef傳送訊息給RpcEnv,RpcEnv查詢註冊資訊將訊息路由到指定的RpcEndpoint,RpcEndpoint接收到訊息後進行處理(模式匹配的方式)

RpcEndpoint的宣告週期:constructor -> onStart -> receive* -> onStop

其中receive*包括receive和receiveAndReply

本文我們只是透過一個簡單的測試程式分析了Spark Rpc底層的實現,叢集中的其它通訊(比如Master和Woker的通訊)的原理和這個測試類似,只不過具體的傳送方式有所不同(包括ask、askWithRetry等),而且遠端發訊息的時候使用了OutBox和NIO等相關的內容,感興趣的朋友可以對原始碼進行詳細的閱讀,本文不一一說明,目的就是透過簡單的測試理解大致流程,不再為“Spark Rpc到底是什麼”而糾結,一句話總結:Spark Rpc就是Spark中對分散式訊息通訊系統的高度抽象。

本文為原創,歡迎轉載,轉載請註明出處、作者,謝謝!



作者:sun4lower
連結:


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/2471/viewspace-2818838/,如需轉載,請註明出處,否則將追究法律責任。

相關文章