akka流阅读时无休止的http流反压

时间:2017-10-21 21:56:13

标签: scala http curl akka

基本上这是我使用的代码。

当我与curl建立连接时,我在curl命令中看到所有实体都非常快。当我尝试用akka模仿相同的行为时,在打印出我得到的元素之间会有很大的停顿。

在前4条消息之后,流以某种方式受到压力 其余的1条消息在显着的时间到达打印行之后。

前4条消息大约是2k JSON,最后一条是no。 5是80k JSON。

最后一个实体(编号5)也是最大的块,我得到的印象是在流完成之前打印的。而且我非常肯定它只在运行2-3秒后才可用。

知道为什么这个流在读完前4个元素之后就会挂起

val awesomeHttpReq = Http().singleRequest(
  HttpRequest(
    method = GET,
    uri = Uri("http://some-service-providing-endless-http.stream")
  )
)

val a = Source.fromFuture(awesomeHttpReq).flatMapConcat {
  case HttpResponse(status, _, entity, _) =>
    // I saw some comments the back pressure might kick in
    // because I might not be consuming the bytes here properly
    // but this is totally in line with all the examples etc.

    entity.withoutSizeLimit.getDataBytes.via(Framing delimiter (ByteString("\n"), Int.MaxValue))
} map { bytes =>
  parse(bytes decodeString StandardCharsets.UTF_8).fold(pf => throw new IllegalStateException(s"unable to parse: $pf"), identity[Json])
} mapConcat { items =>
  // every line that comes in from previous stage contains
  // key elements - this I'm interested in, it's an array
  items.asObject flatMap (_.toMap get "events") flatMap (_ asArray) getOrElse Nil
}

val b: Future[Vector[Json]] = a
  .takeWithin(50 second)
  .runWith(Sink.fold(Vector.empty[Json])((a, b) => {

    // I'm using this to see what's going on in the stream
    // there are significant pauses between the entities
    // in reality the elements are available in the stream (all 5)
    // within 2-3 seconds
    // and this printing just has very big pause after first 4 elements

    println(s"adding\n\n\n ${b.noSpaces}")
    a :+ b
  }))

Await.result(b, 1 minute)

我看过这个问题它似乎与我所拥有的https://github.com/akka/akka-http/issues/57非常接近,但不知何故找不到对我的情况有用的东西。

我也尝试更改akka http的块大小,但没有真正帮助。

这是传入消息的时间: 来自流初始化:

1.  881 ms
2.  889 ms
3.  894 ms
4.  898 ms
// I don't understand why this wait time of 30 seconds in betweeen
5. 30871 ms

最后一条消息显然在某处停留了30秒

任何想法都会受到赞赏。

更新

因为前4个元素一直在4点出来并且第5个元素等待30秒真的很奇怪,所以我决定将initial-input-buffer-size = 4从默认值4增加到16,现在它可以用作预期。我只是无法理解背压在上面的代码中的位置。

更新2:

缓冲区大小有助于我的简单示例。但在我真正的问题中,我有一些非常奇怪的事情:

entity.withoutSizeLimit.dataBytes
    .alsoTo(Sink.foreach(a => println("stage 1 " + a.decodeString(StandardCharsets.UTF_8))))
    .via(Framing delimiter (ByteString("\n"), Int.MaxValue))
    .buffer(1000, OverflowStrategy.backpressure)
    .alsoTo(Sink.foreach(a => println("stage 2 " + a.decodeString(StandardCharsets.UTF_8))))

我可以在框架(阶段1)之前看到我需要的消息,但在日志(阶段2)之后不能看到它。我确保有足够的空间可以通过放置缓冲区来推动它。

现在我发现新的线条角色并没有真正进入前面的舞台(第1阶段),这就是每条线通常结束的方式:

"7da".sliding(2, 2).toArray.map(Integer.parseInt(_, 16).toChar).mkString
res12: String =
"}
"

在我的最后一项上我错过了最后一个字节a,基本上新行没有进入框架。所以整件事都没有发出。

1 个答案:

答案 0 :(得分:1)

经过一番调查后,我决定解决这个问题,因为看起来有多种因素的组合。整个问题的输入源实际上是我公司使用的具有kafka背景的专有企业服务总线:https://github.com/zalando/nakadi

根据上面的症状,我想也许系统没有按照文档播放,并且他们可能没有通过附加发送\n但是他们准备好了每一行,但事实并非如此因为我签了代码:https://github.com/zalando/nakadi/blob/0859645b032d19f7baa919877f72cb076f1da867/src/main/java/org/zalando/nakadi/service/EventStreamWriterString.java#L36

看到这个后,我尝试使用这个例子来模拟整个事情:

<强> build.sbt

name := "test-framing"

version := "0.1"

scalaVersion := "2.12.4"    

lazy val akkaVersion = "2.5.6"
lazy val akkaHttpVersion = "10.0.10"

libraryDependencies ++= Seq(
  "com.typesafe.akka" %% "akka-stream" % akkaVersion,
  "com.typesafe.akka" %% "akka-http" % akkaHttpVersion,
  "com.typesafe.akka" %% "akka-http-spray-json" % akkaHttpVersion
)

scalacOptions in Compile ++= (scalacOptions in Compile).value :+ "-Yrangepos"

* TestApp.scala - 我的代码中遇到问题*

import java.nio.charset.StandardCharsets

import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.model._
import akka.stream.ActorMaterializer
import akka.stream.scaladsl.{Framing, Sink, Source}
import akka.util.ByteString

import scala.concurrent.duration._
import scala.concurrent.{Await, Future}

object TestApp extends App {

  implicit val system = ActorSystem("MyAkkaSystem")
  implicit val materializer = ActorMaterializer()

  val awesomeHttpReq = Http().singleRequest(
    HttpRequest(
      method = HttpMethods.GET,
      uri = Uri("http://localhost:9000/streaming-json")
    )
  )

  val a = Source.fromFuture(awesomeHttpReq).flatMapConcat {
    case HttpResponse(status, _, entity, _) =>
      entity.withoutSizeLimit.getDataBytes
        .via(Framing delimiter (ByteString("\n"), Int.MaxValue))
  } map { bytes =>
    bytes decodeString StandardCharsets.UTF_8
  }

  val b: Future[Vector[String]] = a
    .takeWithin(50 second)
    .runWith(Sink.fold(Vector.empty[String])((a, b) => {
      println(s"adding $b")
      a :+ b
    }))

  Await.result(b, 1 minute)

}

*模拟终点*

import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.common.EntityStreamingSupport
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
import akka.http.scaladsl.server.Directives
import akka.stream.scaladsl.{Flow, Source}
import akka.stream.{ActorMaterializer, ThrottleMode}
import akka.util.ByteString
import spray.json._

import scala.concurrent.duration._
import scala.io.StdIn

object TestApp2 extends App {

  implicit val system = ActorSystem("MyAkkaSystem")
  implicit val materializer = ActorMaterializer()

  implicit val executionContext = system.dispatcher

  case class SomeData(name: String)

  trait JsonSupport extends SprayJsonSupport with DefaultJsonProtocol {
    implicit val someFormat = jsonFormat1(SomeData)
  }

  val start = ByteString.empty
  val sep = ByteString("\n")
  val end = ByteString.empty

  implicit val jsonStreamingSupport = EntityStreamingSupport
    .json()
    .withFramingRenderer(Flow[ByteString].intersperse(sep))

  object MyJsonService extends Directives with JsonSupport {

    def streamingJsonRoute =
      path("streaming-json") {
        get {
          val sourceOfNumbers = Source(1 to 1000000)

          val sourceOfDetailedMessages =
            sourceOfNumbers
              .map(num => SomeData(s"Hello $num"))
              .throttle(elements = 5,
                        per = 30 second,
                        maximumBurst = 6,
                        mode = ThrottleMode.Shaping)

          complete(sourceOfDetailedMessages)
        }
      }
  }

  val bindingFuture =
    Http().bindAndHandle(MyJsonService.streamingJsonRoute, "localhost", 9000)

  println(s"Server online at http://localhost:9000/\nPress RETURN to stop...")
  StdIn.readLine() // let it run until user presses return
  bindingFuture
    .flatMap(_.unbind()) // trigger unbinding from the port
    .onComplete(_ => system.terminate()) // and shutdown when done

}

在模拟终点中,我得到了预期的行为,所以akka没有什么问题。

当多个图书馆+ nakadi聚集在一起时,仍然可能存在一些问题,但这只是鹅狩猎。最后如果我将batch_flush_timeout降低到某个低值,服务器实际上会将下一个事件发送到管道中,因此管道中最后一条消息将被推送到我的应用层,以便我可以对它进行处理。

基本上所有这些文字都是因为一个字节在某种程度上没有形成框架,但是在过去的几天里我又学到了很多关于akka流的信息。