Spark2源碼分析系列——RPC(1)

一. Spark rpc概述

首先說明RPC,引用百度百科:

RPC(Remote Procedure Call)—遠程過程調用,它是一種通過網絡從遠程計算機程序上請求服務,而不需要了解底層網絡技術的協議。RPC協議假定某些傳輸協議的存在,如TCP或UDP,爲通信程序之間攜帶信息數據。

Spark RPC可以說 是 Spark 分佈式集羣的基礎,若是將 Spark 類比爲一個人的話,Spark RPC 就是這個人的血液部分。

有一位大神將 Spark RPC 中的 RPC 部分剝離出來,弄成一個新的可運行的 RPC 項目,地址在這Spark RPC

雖然名字不一樣,但這個項目的類和內容基本和 Spark Core 中 RPC 部分的代碼和結構基本是一樣的,可以通過這個來學習 Spark RPC。

PS:所用 spark 版本:spark 2.1.0

二. Spark RPC ,從簡單的例子開始

接下來我們來演示如何下載並運行最簡單的 Hello World 中的例子。

首先,我使用的編譯器是 IDEA ,通過 idea 將 github 上的代碼 clone 下來。
可以看到項目目錄下有兩個模塊,

  • kraps-rpc
  • kraps-rpc-example

我們要做的即是運行 kraps-rpc-example 中的代碼。

啓動 PRC 的話首先需要啓動 Server 端,開啓監聽服務,這裏在 HelloworldServer.scala 中都已經幫我們寫好,不過在 main 方法中需要修改一下內容,就是將 host 改爲本機地址。

  def main(args: Array[String]): Unit = {
//    val host = args(0)
    val host = "localhost"
    val config = RpcEnvServerConfig(new RpcConf(), "hello-server", host, 52345)
    val rpcEnv: RpcEnv = NettyRpcEnvFactory.create(config)
    val helloEndpoint: RpcEndpoint = new HelloEndpoint(rpcEnv)
    rpcEnv.setupEndpoint("hello-service", helloEndpoint)
    rpcEnv.awaitTermination()
  }

然後我們只需要右鍵該文件然後執行即可。

然後到 HelloworldClient 文件中,這裏面提供了同步和異步兩個方法可以運行。代碼同樣都已經寫好,通過修改註釋即可使用不同的方法運行。同樣是右鍵點擊該文件執行。

  def main(args: Array[String]): Unit = {
    //異步方法
    //asyncCall()
    //同步方法
    syncCall()
  }

異步方法中, ask 會返回一個 Future 。在 Future 運行結果出來前,我們可以去做其他事情。scala 中的 Future 和 Java 的 Future 有些不同,不過這可以先不去管,先當作 Java 裏面的 Future 即可。

  def asyncCall() = {
    val rpcConf = new RpcConf()
    val config = RpcEnvClientConfig(rpcConf, "hello-client")
    val rpcEnv: RpcEnv = NettyRpcEnvFactory.create(config)
    val endPointRef: RpcEndpointRef = rpcEnv.setupEndpointRef(RpcAddress("localhost", 52345), "hello-service")
    val future: Future[String] = endPointRef.ask[String](SayHi("neo"))
    future.onComplete {
      case scala.util.Success(value) => println(s"Got the result = $value")
      case scala.util.Failure(e) => println(s"Got error: $e")
    }
    Await.result(future, Duration.apply("3s"))
    //在future結果運行出來前,會先打印這條語句。
    println("print me at first!")
    Thread.sleep(7)
  }

而同步方法是直接將結果返回,並且會阻塞,直到結果返回

  def syncCall() = {
    val rpcConf = new RpcConf()
    val config = RpcEnvClientConfig(rpcConf, "hello-client")
    val rpcEnv: RpcEnv = NettyRpcEnvFactory.create(config)
    val endPointRef: RpcEndpointRef = rpcEnv.setupEndpointRef(RpcAddress("localhost", 52345), "hello-service")
    val result = endPointRef.askWithRetry[String](SayBye("neo"))
    println(result)

  }

很簡單是吧,接下來我們先來了解一些 Spark RPC 運行過程中至關重要的兩個編程模型,以及在這其中使用到的一些主要的類。

三. Spark RPC 中各類說明

Spark RPC 是使用了 Actor 模型和 Reactor 模型的混合模式,我們結合兩種模型分別說明 Spark RPC 中各個類的作用:

首先我們先來看 Spark RPC 的類圖。

image

是不是感覺很亂?沒事,我們來逐步剖析各個類。

Spark RPC 主要用到了 Actor 模型 和 Reactor 模型,我們從這兩個模型的角度來拆解。

Actor 模型

其實之前也有寫過一篇介紹 Actor 模型的文章,感興趣的同學可以點擊這裏查看 Actor模型淺析 一致性和隔離性

其實 Actor 主要就是這副圖的內容:

 

image

RpcEndpoint => Actor

RpcEndpointRef => ActorRef

RpcEnv => ActorSystem

我們逐個來看:

RpcEnv --RPC Environment

RPC Environment 是 RpcEndpoint 的運行環境。它管理 RpcEndpoint 的整個生命週期:

  1. 通過名字或 URI 註冊 RpcEndpoint。
  2. 對到底的消息進行路由,決定分發給哪個 RpcEndpoint。
  3. 停止 RpcEndpoint。

RPC Environment在 akka 已經被移除的2.0後面版本中,RPC Environment 的實現類是 NettyRpcEnv。通常是由 NettyRpcEnvFactory.create 創建。

RpcEndpoint

RpcEndpoint 能通過 callbacks 接收消息。通常需要我們自己寫一個類繼承 RpcEndpoint 。編寫自己的接收信息和返回信息規則。

RpcEndpoint 的生命週期被 RPC Environment 管理。其生命週期包括,onStart, receive 和 onStop。

它是作爲服務端,比如上面例子中的 HelloworldServer 就是一個 RpcEndpoint 。

RpcEndpointRef

RpcEndpointRef 是 RpcEndpoint 在 RPC Environment 中的一個引用。

它包含一個地址(即 Spark URL)和名字。RpcEndpointRef 作爲客戶端向服務端發送請求並接收返回信息,通常可以選擇使用同步或異步的方式進行發送。

Reactor 模型

我們可以從一張圖來看 Reactor 的架構。

image

使用Reactor模型,由底層netty創建的EventLoop做I/O多路複用,這裏使用Multiple Reactors這種形式,如上圖所示,從netty的角度而言,Main Reactor 和 Sub Reactor 對應 BossGroup 和 WorkerGroup 的概念,前者負責監聽 TCP 連接、建立和斷開,後者負責真正的 I/O 讀寫。

而圖中的 ThreadPool 就是的 Dispatcher 中的線程池,它來解耦開來耗時的業務邏輯和 I/O 操作,這樣就可以更 scalabe,只需要少數的線程就可以處理成千上萬的連接,這種思想是標準的分治策略,offload 非 I/O 操作到另外的線程池。

Dispatcher

Dispatcher 的主要作用是保存註冊的RpcEndpoint、分發相應的Message到RpcEndPoint中進行處理。Dispatcher 即是上圖中 ThreadPool的角色。它同時也維繫一個 threadpool,用來處理每次接受到的 InboxMessage 。而這裏處理 InboxMessage 是通過 inbox 實現的。

Inbox

Inbox 其實屬於 Actor 模型,是 Actor 中的信箱,不過它和 Dispatcher 聯繫緊密所以放這邊。

InboxMessage 有多個實現它的類,比如 OneWayMessage,RpcMessage,等等。Dispatcher會將接收到的 InboxMessage 分發到對應 RpcEndpoint 的 Inbox 中,然後 Inbox 便會處理這個 InboxMessage 。

OK,這次就先介紹到這裏,下次我們從代碼的角度來看 Spark RPC 的運行機制

如果覺得對你有幫助,不妨關注一波吧~~

參考資料:https://zhuanlan.zhihu.com/p/28893155

 

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