RPC의 불꽃 - 심층 분석

[이 문서에서 근무, 현재 알리바바 광고 시스템 연구 개발 업무에 종사하고, 대학원생의 내용이 베이징 게시물의 대학 및 통신 졸업, 칭화 대학을 졸업 다시 인쇄됩니다 훌루 주요 빅 데이터 기술에서 근무 바이 작업과 상무부의 검색 광고의 절차 6 년, 주목 서비스 백엔드, 컴퓨팅 엔진, 빅 데이터 , 광고 기술, 고품질로 작성된 코드에, 오픈 소스 기술을 사랑, 오픈 소스 자신의 클래스 라이브러리 어셈블리의 일부 (참조 내 작품을 )뿐만 아니라 아파치 스파크, FLINK, 봄 클라우드 유레카, 소스 코드를 통해 Protostuff 기여. ]

[원본에 포함 된 노하우 거의 , 저자의 허락 덕분에 다시 인쇄합니다. ]

머리말

스파크는 노드 간 통신은 다른 구성 요소 사이에 존재해야 빠르고 범용 분산 컴퓨팅 시스템, 분산 자연이 뜻이 논문이 점에 대한 RPC (원격 프로 시저 호출)에 의해 통신을 가리 키도록 방법을 설명 스파크입니다 가. 그것은 세 장으로 나누어 져 있습니다,

  1. 스파크 RPC 간단한 예제와 실제 적용
  2. 스파크 RPC 모듈 설계 원칙
  3. 스파크 RPC 핵심 기술 요약

 

예 1 스파크 RPC의 간단하고 실용적인

두 가지 주요 모듈 RPC의 스파크

1) 스파크 코어는, 패키지의 주요 역할은 모듈 org.apache.spark.spark - 네트워크 - 일반적인에 따라 더 나은 서버와 클라이언트뿐만 아니라 융합과 스칼라 언어를 전달한다.

2)에서 org.apache.spark.spark - 네트워크 - 공통 모듈이 자바로 작성, 최신 버전은 전이중 (full-duplex)을 제공하기 위해 개발 netty4, 멀티 플렉스를 기반으로 I / O 모델 소켓 I / O 용량은 점화 전송 프로토콜 구조 (와이어 프로토콜)을 정의한다.

더 나은 내가 RPC 통신의 일부를 꺼내 2.1 버전은 단독 프로젝트 시작 스파크에 따라 내부 스파크 RPC 구현 세부 사항을 이해하기 위해 하고 github에로 발표를 메이븐 중앙 저장소가 사용하는 학습하기 시작하는 좋은 문서, 파라미터 설정 및 성능 평가를 제공합니다. 여기에서 우리는이 모듈을 통해 지각 인식 스파크 RPC을한다.

다음 코드는있을 수 kraps-RPC 발견했다.

간단한 예 1.1

우리는 클라이언트가 인사 또는 안녕 문자열, 서버 응답을 전송할 수있는 안녕하세요 서비스를 개발하고자, 입력 문자열로 반향 가정하자.

ThreadSafeRpcEndpoint로부터 상속 된 경우 첫 번째 단계는하는 HelloEndpoint 동시에 서비스를 호출 할 수 있습니다 RpcEndpoint 쇼에서 상속 정의, 엔드 포인트가 동시에 허용하지 나타냅니다.

class HelloEndpoint(override val rpcEnv: RpcEnv) extends RpcEndpoint { override def onStart(): Unit = { println("start hello endpoint") } override def receiveAndReply(context: RpcCallContext): PartialFunction[Any, Unit] = { case SayHi(msg) => { println(s"receive $msg") context.reply(s"hi, $msg") } case SayBye(msg) => { println(s"receive $msg") context.reply(s"bye, $msg") } } override def onStop(): Unit = { println("stop hello endpoint") } } case class SayHi(msg: String) case class SayBye(msg: String)

그리고,이 (예를 들면, 일반 명 또는 ID 같은) 인터페이스 또는 표시하는 방법을 정의하지 않고 볼 수있는 전통적인 자바 RPC 대비 용액, 패턴 매칭 방법을 라우팅 스칼라를 사용했다. 피어 통신 교환 계약 대상 언어 있지만, 이것은 SayHi SayBye이 경우 클래스,하지만 내부에 위치 스파크 RPC 통신 구성 요소, 그래서 무해하다.

 

두 번째 단계, 좋은 개발은 RPC 엔드 포인트가 외부 요청에 대한 응답으로, 자신의 수명주기를 관리 스파크합니다. RpcEnvServerConfig 매개 변수, 서버 이름 (하나는 플래그이다), 바인드 주소와 포트를 정의 할 수 있습니다. NettyRpcEnvFactory으로이 공장 방법은 RpcEnv를 생성하기 위해, RpcEnv 전체 스파크 RPC의 중심에, 나중에 setupEndpoint하여 "안녕하세요 서비스"및 바인딩 엔드 포인트의 이름을 정의하는 첫 번째 단계를 확대 상세하게 설명 클라이언트 후속 호출은로 연결됩니다 엔드 포인트 필요 "안녕하세요 서비스"이름입니다. 서버가 요청 및 프로세스를 수신 차단 전화 AwaitTermination.

val config = RpcEnvServerConfig(new RpcConf(), "hello-server", "localhost", 52345) val rpcEnv: RpcEnv = NettyRpcEnvFactory.create(config) val helloEndpoint: RpcEndpoint = new HelloEndpoint(rpcEnv) rpcEnv.setupEndpoint("hello-service", helloEndpoint) rpcEnv.awaitTermination()

 

세 번째 단계는 클라이언트가 아니라 서버 제 RpcEnvClientConfig 시작된 통화 및 RpcEnv이 필요하고 스터브로 간주 할 수 방금 언급 "헬로 서비스"이름하여 원격 엔드 포인트 (참조)에 대한 레퍼런스를 생성 개발하는, 호출하는 데 사용, 첫 공연은 비동기 요청에 의해 여기에해야 할 일.

val rpcConf = new RpcConf() val config = RpcEnvClientConfig(rpcConf, "hello-client") val rpcEnv: RpcEnv = NettyRpcEnvFactory.create(config) val endPointRef: RpcEndpointRef = rpcEnv.setupEndpointRef(RpcAddress("localhost", 52345), "hell-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("30s"))

또한 실제로 askSync 이름 스파크 askWithRetry의 최신에서, 방법을 동기화 할 수 있습니다.

val result = endPointRef.askWithRetry[String](SayBye("neo"))

 

이것은 매우 간단하고, 사용 편의성이 상상 될 수 있고, 통신 프로세스 점화 RPC 인, 패킷 식별 그물코를 사용하여 소켓 I / O 모델 차폐 모델, 직렬화 / 역 직렬화 프로세스를 스레딩 RPC 프레임 수행 긴 연결, 네트워크 재 연결 메커니즘을 다시 시도하십시오.

 

1.2 실용화

스파크 내부 엔드 포인트 EndpointRef 통신 많이는 드라이버 사이의 상호 작용, 예를 들어, 이러한 형태 통해 상기 실행 프로그램이 사용하는 하트 비트 메커니즘을 사용 HeartbeatReceiver을 또한 종점의 등록에서, 이는 달성 다음 코드를 할 SparkContext 초기화 시간 :

_heartbeatReceiver = env.rpcEnv.setupEndpoint(HeartbeatReceiver.ENDPOINT_NAME, new HeartbeatReceiver(this))

그리고 그것은라고 실행자 다음과 같이 내부 방법 :

val message = Heartbeat(executorId, accumUpdates.toArray, env.blockManager.blockManagerId) val response = heartbeatReceiverRef.askWithRetry[HeartbeatResponse](message, RpcTimeout(conf, "spark.executor.heartbeatInterval", "10s")) 

 

설계 원칙 2. 스파크 RPC 모듈

(자세한 내용은 참조를 위해 첫째, 이후 스파크 2.0 걸었다 Akka의 RPC 프레임 워크 밖으로 제거 SPARK-5293을 ) 많은 사용자가 Akka를 사용하는 메시징을 수행하고 버전 포함 된 불꽃과 충돌 할 것이기 때문에, 그 이유는 매우 간단하다 , 스파크는 기본 org.apache.spark.spark - 네트워크 - 일반적인 모듈을 기반으로, 2.0 이후 있도록 RPC Akka을 수행하는 데 사용하는 동안은 코어 내부에 캡슐화, 액터 모델 스칼라 메시징 모듈 Akka 유사한 구현 kraps -rpc 프로젝트의 코어로부터 독립적 뒤집어 박리의 일부이다.

하지만이 Akka 박탈, 여전히 개념 배우 모드에서 현재 스파크 RPC에서 다음 매핑을 따랐다.

RpcEndpoint => Actor
RpcEndpointRef => ActorRef RpcEnv => ActorSystem

모든 기본 통신 그물코가 org.apache.spark.spark - 네트워크 - 일반적인 내부 lib 디렉토리를 사용하여 대체 사용합니다.

 

2.1 클래스 다이어그램 분석

처음도 스파크 RPC 모듈 내의 UML 클래스의 관계를 나타내고, 화이트 옐로우 org.apache.spark.spark 네트워크 공통 자바 클래스이다 스칼라 스파크 코어 클래스이다.

이 사진을 두려워하지 마십시오, 다음과 같은 분석을 통해 설명, 나는 독자가 자신의 합리적인 디자인의주의 깊은 연구없이, 그 의미를 이해할 수 있다고 생각, 스파크는 빠르게 발전, 발전 프로젝트는 지속적으로 변화하고, 코드가 고정되지이다 특정.

 

RpcEndpoint 및 RpcCallContext

가장 왼쪽 RpcEndpoint 봐 RpcEndpoint 액터 Akka 볼 수있는 시그니처 (아래 참조)를 제공하는 그 방법에서, 유사 서비스 요청에 응답 할 수, 수신 모드는이 UDP와 비교 될 수 있고, 단방향 방법이며 receiveAndReply는 응답 모드이며, TCP에 비교 될 수있다. 그것의 서브 클래스는이 두 가지 기능을 선택적으로 적용 범위를 달성 할 수있다, 우리는 HeartbeatReceiver 모든 서브 클래스에서 처음 HelloEndpoint의 장과 불꽃을 달성했다.

def receive: PartialFunction[Any, Unit] = { case _ => throw new RpcException(self + " does not implement 'receive'") } def receiveAndReply(context: RpcCallContext): PartialFunction[Any, Unit] = { case _ => context.sendFailure(new RpcException(self + " won't reply anything")) }

상기 비즈니스 로직은 박리 될 수 있도록 상기 코어를 분리하는 방법을 브리징 RpcCallContext 멀티 스파크 RPC의 조합으로 볼 수있는 송신의 기본적인 비즈니스 로직 및 콜백 중합 콜백 OO 디자인 패턴을 추상화 할 -> RPC 패키지 (스파크 코어 모듈) -> 로우 - 레벨 통신 (스파크 공통 네트워크) 세. RpcCallContext 예를 들어, 통상의 응답과 예외 오류를 반환하는데 이용 될 수있다 :

reply(response: Any) // 回复一个message,可以是一个case class。 sendFailure(e: Throwable) // 回复一个异常可以是Exception的子类由于Spark RPC默认采用Java序列化方式所以异常可以完整的在客户端还原并且作为cause re-throw出去

RpcCallContext 또한 개의 서브 카테고리, 즉 LocalNettyRpcCallContext 및 RemoteNettyRpcCallContext로 나누어, 이것은 로컬 왼쪽 RPC 원격 상호 작용에 의해 필요한 끝점 달리 왼쪽 RemoteNettyRpcCallContext 직접 호출 LocalNettyRpcCallContext 경우에 내부 틀, 주로, 이는 또한 RPC 반영 핵심 개념은 동일한 로컬 호출에 마치 다른 주소 공간 함수 방법에 실행하는 방법이다.

또한, RpcEndpoint 또한 콜백 범위의 시리즈를 제공합니다.

- onError
- onConnected
- onDisconnected - onNetworkError - onStart - onStop - stop

또한 그 서브 클래스 중 하나가 ThreadSafeRpcEndpoint입니다 아래에, 엔드 포인트의 많은 불꽃이 클래스를 상속주의, 스파크 RPC 프레임 워크 즉, 같은 시간에 하나의 스레드 만 전화를 걸 수 있도록 같은 엔드 포인트의 동시 처리되지 않습니다.

클라이언트가 호출 할 때마다 당신은 하나의 엔드 포인트가있는 경우 서버를 요청해야하기 때문에 RpcEndpoint가 RpcEndpointVerifier라는 기본, 각 RpcEnv 초기화 시간이 엔드 포인트에 등록됩니다도있다.

 

RpcEndpointRef

ActorRef에 RpcEndpointRef 유사한 Akka는 이름에서 알 수 있듯이, 이것은 등가 제공 RpcEndpoint 방법에 대한 참조는, (그 수신에 응답 RpcEndpoint)를 단방향 전송을위한 요청을 전송?,하기 방법이 동등 ASK !, 보내 불 앤을 제공하는 것이다 의미를 잊지 (receiveAndReply에 RpcEndpoint는 응답) 요청 응답 의미를 제공하도록 요청, 기본, 당신은 블록 대기를 동기화 할 수 있습니다 시간 초과 메커니즘, 응답을 반환 할 필요가, 당신은 또한 미래의 핸들을 반환 할 수있는 요청 스레드를 시작하는 작업을 차단하지 않습니다 .

RpcEndpointRef 클라이언트는 RpcEnv에서 얻은, 로컬 전화 또는 RPC를 할 스마트 될 수 요청 항목을 시작했다.

 

RpcEnv 및 NettyRpcEnv

핵심 클래스 라이브러리는 RpcEnv, 그냥이 ActorSystem은 서비스와 클라이언트가 통신에 사용할 수 있습니다 언급이다.

서버 측의 경우,, RpcEnv는 수명주기 관리 전반에 걸쳐 RpcEndpoint 운영 환경은 책임을 파괴하거나 RpcMessage로 포장 된 엔드 포인트 등록, TCP 층과 직렬화하여 데이터 패킷 수 RpcEndpoint, 라우팅 지정된 엔드 포인트에 대한 요청입니다 응답이 필요한 경우, 비즈니스 로직 코드를 호출 엔드 포인트, 원격 피어 층 리턴 오브젝트 TCP 재전송 시퀀스 끝점 이상, 그 이상 호출 RpcCallContext.sendFailure 다시 보낼 경우.

클라이언트 측에서 당신의 RpcEndpointRef입니다 RpcEnv에 의해 참조 RpcEndpoint를 얻을 수 있습니다.

RpcEnv은 충전 및 특정 상호 작용을 기본 통신 모듈에있는 사람, 그 동반자 개체는 다음과 같이 RpcEnv, 서명을 생성하는 방법을 포함 :

def create(
      name: String, bindAddress: String, advertiseAddress: String, port: Int, conf: SparkConf, securityManager: SecurityManager, numUsableCores: Int, clientMode: Boolean): RpcEnv = { val config = RpcEnvConfig(conf, name, bindAddress, advertiseAddress, port, securityManager, numUsableCores, clientMode) new NettyRpcEnvFactory().create(config) }

RpcEnv는 하나의 하위 범주가 RpcEnvFactory 책임 RpcEnvFactory을 만들 NettyRpcEnvFactory는이 원래 AkkaRpcEnvFactory이다. 전화 한 번 NettyRpcEnvFactory.create 방법은 바로 바인드 주소와 포트에 서버를 시작합니다.

또한 종속 RpcEnvConfig SparkConf하고 파라미터 (kraps-RPC는 RpcConf 개명 됨)의 수를 포함한다. RpcEnv 매개 변수, RpcEnvConfig에서 등 가장 기본적인 호스트 이름과 포트뿐만 아니라 일부 높은 수준의 연결 시간 제한, 재시도, 원자로 스레드 풀 크기 및을해야합니다.

이제 RpcEnv이 가장 일반적으로 사용되는 방법을 살펴 보자 :

// 注册endpoint,必须指定名称,客户端路由就靠这个名称来找endpoint
def setupEndpoint(name: String, endpoint: RpcEndpoint): RpcEndpointRef // 拿到一个endpoint的引用 def setupEndpointRef(address: RpcAddress, endpointName: String): RpcEndpointRef

인 NettyRpcEnvFactory.create 의해 생성 NettyRpcEnv 전체 다리 점화 org.apache.spark.spark 코어 네트워크 공통 시맨틱 클래스 액터를 포장하는 동안 상기 저면에 의해 제공되는 지렛대 내의 통신 용량. 상기 두 방법의 핵심은 setupEndpoint 끝점 디스패처에 등록되어 setupEndpointRef 호출 엔드 포인트가 있는지 확인하는 로컬 또는 원격 RpcEndpointVerifier 시도로 이동하고 RpcEndpointRef를 생성한다. 서버에 대한 자세한 내용은 클라이언트는 세부 사항이 여기에 착수하지, 타이밍 다이어그램에 명시된됩니다 호출합니다.

 

디스패처 和받은 편지함

NettyRpcEnv 주로 올바른 RpcEndpoint에 라우팅 도움과 비즈니스 로직을 호출하는 서버, Dispatcher를 포함하고 있습니다.

요구는, 본 명세서에 반응기 모델을 설정 대표적인 원자로의 RPC의 소켓 I / O 모델 스파크하지만 사서함 액터 패턴과 함께, 혼합의 일 실시 예로서 설명 될 수있다.

반응기 모델 사용은 EventLoop 기본 그물코 의해 생성 할 I / O 멀티플렉서, 뷰 그물코의 관점에서, 다음과 같이, 본원이 형태의 다중 반응기, 사용 된 바와 같이, 주 및 BossGroup 및 WorkerGroup 서브 반응기 반응기 개념적 맵 전자는 실제 I을 담당하는 TCP 연결 설정과 해제를 모니터링 할 책임이 / O 읽기 및 쓰기 및 그림 수영장에서 ThreadPool이 발송자 스레드, 그것은 비즈니스 로직을 분리하는 벗고 있다는 및 I / O 작업은 더 scalabe 수 있도록 스레드의 작은 숫자가 수천 개의 연결을 처리 할 수있는,이 아이디어는 표준 분할하고 다른 스레드 풀에 비의 I / O 작업을 오프로드 전략을 정복.

중간 스레드 핸들러 반응기 디코드 RpcMessage로 처리 한 다음, 상기 연산 공정에 있도록 함을 제공하여 한 설명 실시간 ThreadPool이있는 RpcEndpoint 서비스 로직 처리 디스패처 스레드 풀을한다.

(출처 여기를 클릭 )

단지 또한 사서함 모드에서 언급 된 배우 패턴은 스파크 RPC 지금, 여전히이 모드를 사용하도록 진화, Akka에서 유래. 다음은받은 편지함을 소개하고, 각 엔드 포인트는 RpcMessage 통해 원격으로 호출 할 수 있습니다, InboxMessage 많은 하위 범주가있다 InboxMessage의 목록이받은 편지함,받은 편지함을 가지고, 단방향 메시지는 통해 원격으로 호출 할 수있는 파이어 앤 포겟 OneWayMessage, 또한 시작하는 다양한 서비스를 할 수있다, 링크 해제 메시지를 설정, 메시지는 적절한 RpcEndpoint 기능 (한 모든 일을),받은 편지함의 내부에 패턴 매칭 방법을 호출합니다.

Dispatcher中包含一个MessageLoop,它读取LinkedBlockingQueue中的投递RpcMessage,根据客户端指定的Endpoint标识,找到Endpoint的Inbox,然后投递进去,由于是阻塞队列,当没有消息的时候自然阻塞,一旦有消息,就开始工作。Dispatcher的ThreadPool负责消费这些Message。

Dispatcher的ThreadPool它使用参数spark.rpc.netty.dispatcher.numThreads来控制数量,如果kill -3 <PID>每个Spark driver或者executor进程,都会看到N个dispatcher线程:

"dispatcher-event-loop-0" #26 daemon prio=5 os_prio=31 tid=0x00007f8877153800 nid=0x7103 waiting on condition [0x000000011f78b000]

那么另外的问题是谁会调用Dispatcher分发Message的方法呢?答案是RpcHandler的子类NettyRpcHandler,这就是Reactor中的线程做的事情。RpcHandler是底层org.apache.spark.spark-network-common提供的handler,当远程的数据包解析成功后,会调用这个handler做处理。

这样就完成了一个完全异步的流程,Network IO通信由底层负责,然后由Dispatcher分发,只要Dispatcher中的InboxMessage的链表足够大,那么就可以让Dispatcher中的ThreadPool慢慢消化消息,和底层的IO解耦开来,完全在独立的线程中完成,一旦完成Endpoint内部业务逻辑,利用RpcCallContext回调来做消息的返回。

 

Outbox

NettyRpcEnv中包含一个ConcurrentHashMap[RpcAddress, Outbox],每个远程Endpoint都对应一个Outbox,这和上面Inbox遥相呼应,是一个mailbox似的实现方式。

和Inbox类似,Outbox内部包含一个OutboxMessage的链表,OutboxMessage有两个子类,OneWayOutboxMessage和RpcOutboxMessage,分别对应调用RpcEndpoint的receive和receiveAndReply方法。

NettyRpcEnv中的send和ask方法会调用指定地址Outbox中的send方法,当远程连接未建立时,会先建立连接,然后去消化OutboxMessage。

同样,一个问题是Outbox中的send方法如何将消息通过Network IO发送出去,如果是ask方法又是如何读取远程响应的呢?答案是send方法通过org.apache.spark.spark-network-common创建的TransportClient发送出去消息,由Reactor线程负责序列化并且发送出去,每个Message都会返回一个UUID,由底层来维护一个发送出去消息与其Callback的HashMap,当Netty收到完整的远程RpcResponse时候,回调响应的Callback,做反序列化,进而回调Spark core中的业务逻辑,做Promise/Future的done,上层退出阻塞。

这也是一个异步的过程,发送消息到Outbox后,直接返回,Network IO通信由底层负责,一旦RPC调用成功或者失败,都会回调上层的函数,做相应的处理。

 

spark-network-common中的类

这里暂不做过多的展开,都是基于Netty的封装,有兴趣的读者可以自行阅读源码,当然还可以参考我之前开源的Navi-pbrpc框架的代码,其原理是基本相同的。

 

2.2 时序图分析

服务启动

话不多述,直接上图。

 

服务端响应

第一阶段,IO接收。TransportRequestHandler是netty的回调handler,它会根据wire format(下文会介绍)解析好一个完整的数据包,交给NettyRpcEnv做反序列化,如果是RPC调用会构造RpcMessage,然后回调RpcHandler的方法处理RpcMessage,内部会调用Dispatcher做RpcMessage的投递,放到Inbox中,到此结束。

第二阶段,IO响应。MessageLoop获取带处理的RpcMessage,交给Dispatcher中的ThreadPool做处理,实际就是调用RpcEndpoint的业务逻辑,通过RpcCallContext将消息序列化,通过回调函数,告诉TransportRequestHandler这有一个消息处理完毕,响应回去。

这里请重点体会异步处理带来的便利,使用Reactor和Actor mailbox的结合的模式,解耦了消息的获取以及处理逻辑。

 

客户端请求

客户端一般需要先建立RpcEnv,然后获取RpcEndpointRef。

第一阶段,IO发送。利用RpcEndpointRef做send或者ask动作,这里以send为例,send会先进行消息的序列化,然后投递到指定地址的Outbox中,Outbox如果发现连接未建立则先尝试建立连接,然后调用底层的TransportClient发送数据,直接通过该netty的API完成,完成后即可返回,这里返回了UUID作为消息的标识,用于下一个阶段的回调,使用的角度来说可以返回一个Future,客户端可以阻塞或者继续做其他操作。

第二,IO接收。TransportResponseHandler接收到远程的响应后,会先做反序列号,然后回调第一阶段的Future,完成调用,这个过程全部在Reactor线程中完成的,通过Future做线程间的通知。

 

3. Spark RPC核心技术总结

Spark RPC作为RPC传输层选择TCP协议,做可靠的、全双工的binary stream通道。

做一个高性能/scalable的RPC,需要能够满足第一,服务端尽可能多的处理并发请求,第二,同时尽可能短的处理完毕。CPU和I/O之前天然存在着差异,网络传输的延时不可控,CPU资源宝贵,系统进程/线程资源宝贵,为了尽可能避免Socket I/O阻塞服务端和客户端调用,有一些模式(pattern)是可以应用的。Spark RPC的I/O Model由于采用了Netty,因此使用的底层的I/O多路复用(I/O Multiplexing)机制,这里可以通过spark.rpc.io.mode参数设置,不同的平台使用的技术不同,例如linux使用epoll。

线程模型采用Multi-Reactors + mailbox的异步方式来处理,在上文中已经介绍过。

Schema Declaration和序列化方面,Spark RPC默认采用Java native serialization方案,主要从兼容性和JVM平台内部组件通信,以及scala语言的融合考虑,所以不具备跨语言通信的能力,性能上也不是追求极致,目前还没有使用Kyro等更好序列化性能和数据大小的方案。

协议结构,Spark RPC采用私有的wire format如下,采用headr+payload的组织方式,header中包括整个frame的长度,message的类型,请求UUID。为解决TCP粘包和半包问题,以及组织成完整的Message的逻辑都在org.apache.spark.network.protocol.MessageEncoder中。

 

使用wireshake具体分析一下。

首先看一个RPC请求,就是调用第一章说的HelloEndpoint,客户端调用分两个TCP Segment传输,这是因为Spark使用netty的时候header和body分别writeAndFlush出去。

下图是第一个TCP segment:

例子中蓝色的部分是header,头中的字节解析如下:

00 00 00 00 00 00 05 d2 // 十进制1490是整个frame的长度

03一个字节表示的是RpcRequest,枚举定义如下,

RpcRequest(3)
RpcResponse(4) RpcFailure(5) StreamRequest(6) StreamResponse(7) StreamFailure(8), OneWayMessage(9) User(-1)

每个字节的意义如下

4b ac a6 9f 83 5d 17 a9 // 8个字节是UUID 05 bd // 十进制1469payload长度

具体的Payload就长下面这个样子,可以看出使用Java native serialization,一个简单的Echo请求就有1469个字节,还是很大的,序列化的效率不高。但是Spark RPC定位内部通信,不是一个通用的RPC框架,并且使用的量非常小,所以这点消耗也就可以忽略了,还有Spark Structured Streaming使用该序列化方式,其性能还是可以满足要求的。

另外,作者在kraps-rpc中还给Spark-rpc做了一次性能测试,具体可以参考github

 

总结

作者从好奇的角度来深度挖掘了下Spark RPC的内幕,并且从2.1版本的Spark core中独立出了一个专门的项目Kraps-rpc,放到了github以及发布到Maven中央仓库做学习使用,提供了比较好的上手文档、参数设置和性能评估,在整合kraps-rpc还发现了一个小的改进点,给Spark提了一个PR——[SPARK-21701],已经被merge到了主干,算是contribute社区了(10086个开心)。

接着深入剖析了Spark RPC模块内的类组织关系,使用UML类图和时序图帮助读者更好的理解一些核心的概念,包括RpcEnv,RpcEndpoint,RpcEndpointRef等,以及I/O的设计模式,包括I/O多路复用,Reactor和Actor mailbox等,这里还是重点提下Spark RPC的设计哲学,利用netty强大的Socket I/O能力,构建一个异步的通信框架。最后,从TCP层的segment二进制角度分析了wire protocol。

 

【欢迎访问作者的博客neoremind.com,欢迎技术交流。】

추천

출처www.cnblogs.com/oush/p/11486498.html