基本上这是我使用的代码。
当我与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
,基本上新行没有进入框架。所以整件事都没有发出。
答案 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流的信息。