使用Spark DStream作为Akka流的Source的惯用方法

时间:2015-10-28 04:26:16

标签: scala spark-streaming akka-stream akka-http

我正在构建一个REST API,它在Spark群集中启动一些计算,并使用分块的结果流进行响应。鉴于带有计算结果的Spark流,我可以使用

dstream.foreachRDD()

从Spark发送数据。我用akka-http发送了分块的HTTP响应:

val requestHandler: HttpRequest => HttpResponse = {
  case HttpRequest(HttpMethods.GET, Uri.Path("/data"), _, _, _) =>
    HttpResponse(entity = HttpEntity.Chunked(ContentTypes.`text/plain`, source))
}

为简单起见,我试图让纯文本首先工作,稍后会添加JSON编组。

但是使用Spark DStream作为Akka流源的惯用方法是什么?我认为我应该可以通过套接字来实现它,但由于Spark驱动程序和REST端点位于相同的JVM上,因此开放套接字似乎有点过分。

2 个答案:

答案 0 :(得分:8)

在问题发生时不确定api的版本。但现在,使用akka-stream 2.0.3,我相信你可以这样做:

val source = Source
  .actorRef[T](/* buffer size */ 100, OverflowStrategy.dropHead)
  .mapMaterializedValue[Unit] { actorRef =>
    dstream.foreach(actorRef ! _)
  }

答案 1 :(得分:2)

编辑:此答案仅适用于旧版本的spark和akka。 PH88的答案是最新版本的正确方法。

您可以使用为源提供的中间akka.actor.Actor(类似于this question)。下面的解决方案不是"反应性"因为潜在的Actor需要维护一个RDD消息的缓冲区,如果下游的http客户端没有足够快地消耗块,可能会丢弃这些消息。但是无论实现细节如何都会出现此问题,因为您无法连接"限制" akka流回流到DStream以减慢数据速度。这是因为DStream没有实现org.reactivestreams.Publisher

基本拓扑结构为:

DStream --> Actor with buffer --> Source

要构建此toplogy,您必须创建一个类似于实现here的Actor:

//JobManager definition is provided in the link
val actorRef = actorSystem actorOf JobManager.props 

根据JobManager创建一个ByteStrings(消息)源流。另外,将ByteString转换为HttpEntity.ChunkStreamPart,这是HttpResponse所需要的:

import akka.stream.actor.ActorPublisher
import akka.stream.scaladsl.Source
import akka.http.scaladsl.model.HttpEntity
import akka.util.ByteString

type Message = ByteString

val messageToChunkPart = 
  Flow[Message].map(HttpEntity.ChunkStreamPart(_))

//Actor with buffer --> Source
val source : Source[HttpEntity.ChunkStreamPart, Unit] = 
  Source(ActorPublisher[Message](actorRef)) via messageToChunkPart

将Spark DStream链接到Actor,以便将每个相关的RDD转换为Iterable of ByteString,然后转发给Actor:

import org.apache.spark.streaming.dstream.Dstream
import org.apache.spark.rdd.RDD

val dstream : DStream = ???

//This function converts your RDDs to messages being sent
//via the http response
def rddToMessages[T](rdd : RDD[T]) : Iterable[Message] = ???

def sendMessageToActor(message : Message) = actorRef ! message

//DStream --> Actor with buffer
dstream foreachRDD {rddToMessages(_) foreach sendMessageToActor}

将来源提供给HttpResponse:

val requestHandler: HttpRequest => HttpResponse = {
  case HttpRequest(HttpMethods.GET, Uri.Path("/data"), _, _, _) =>
    HttpResponse(entity = HttpEntity.Chunked(ContentTypes.`text/plain`, source))
}

注意:dstream foreachRDD行和HttpReponse之间的时间/代码应该非常少,因为Actor的内部缓冲区将立即开始填充{{1之后来自DStream的ByteString消息行已执行。