我们开始实施文档中提到的Source.queue[HttpRequest]
模式:http://doc.akka.io/docs/akka-http/current/scala/http/client-side/host-level.html#examples
这是文档中的(简化)示例
val poolClientFlow = Http()
.cachedHostConnectionPool[Promise[HttpResponse]]("akka.io")
val queue =
Source.queue[(HttpRequest, Promise[HttpResponse])](
QueueSize, OverflowStrategy.dropNew
)
.via(poolClientFlow)
.toMat(Sink.foreach({
case ((Success(resp), p)) => p.success(resp)
case ((Failure(e), p)) => p.failure(e)
}))(Keep.left)
.run()
def queueRequest(request: HttpRequest): Future[HttpResponse] = {
val responsePromise = Promise[HttpResponse]()
queue.offer(request -> responsePromise).flatMap {
case QueueOfferResult.Enqueued => responsePromise.future
case QueueOfferResult.Dropped => Future.failed(new RuntimeException("Queue overflowed. Try again later."))
case QueueOfferResult.Failure(ex) => Future.failed(ex)
case QueueOfferResult.QueueClosed => Future.failed(new RuntimeException("Queue was closed (pool shut down) while running the request. Try again later."))
}
}
val responseFuture: Future[HttpResponse] = queueRequest(HttpRequest(uri = "/"))
文档声明使用Source.single(request)
是反模式,应该避免。但是,使用Source.queue
无法澄清原因和含义。
在这个地方,我们之前展示了一个使用
Source.single(request).via(pool).runWith(Sink.head)
的示例。 事实上,这是一种表现不佳的反模式。请使用队列或流式方式提供请求,如下所示。
OverflowStrategy
进行明确的背压处理并匹配QueueOfferResult
当我们开始在我们的应用程序中实现此模式时,会出现这些问题。
The queue implementation is not thread safe。当我们在不同的路线/演员中使用队列时,我们有这样的场景:
排队的请求可以覆盖最新的排队请求,从而导致未解决的Future。
更新
akka/akka/issues/23081中已解决此问题。该队列实际上是线程安全的。
过滤请求时会发生什么?例如。当有人改变实施时
Source.queue[(HttpRequest, Promise[HttpResponse])](
QueueSize, OverflowStrategy.dropNew)
.via(poolClientFlow)
// only successful responses
.filter(_._1.isSuccess)
// failed won't arrive here
.to(Sink.foreach({
case ((Success(resp), p)) => p.success(resp)
case ((Failure(e), p)) => p.failure(e)
}))
未来会不会解决?使用单个请求流程,这很简单:
Source.single(request).via(poolClientFlow).runWith(Sink.headOption)
QueueSize
和max-open-requests
之间的差异尚不清楚。最后,两者都是缓冲区。我们的实施最终使用QueueSize == max-open-requests
到目前为止,我发现使用Source.queue
超过Source.single
答案 0 :(得分:1)
我会直接回答您的每一个问题,然后对整体问题给出一般的间接答案。
可能是性能提升?
您认为每个Flow
具有IncomingConnection
是正确的,但如果Connection有多个请求,则仍然可以获得性能提升。
过滤请求时会发生什么?
通常,流元素和接收元素之间没有1:1映射。可以有1:0,如您的示例中所示,或者如果单个请求以某种方式产生多个响应,则可以是1:多。
QueueSize vs max-open-request?
此比率取决于向队列提供元素的速度以及将http请求处理为响应的速度。没有预先定义的理想解决方案。
GENERAL REDESIGN
在大多数情况下使用Source.queue
是因为某些上游函数正在动态创建输入元素,然后将它们提供给队列,例如
val queue = ??? //as in the example in your question
queue.offer(httpRequest1)
queue.offer(httpRequest2)
queue.offer(httpRequest3)
这是糟糕的设计,因为用于创建每个输入元素的任何实体或函数本身可能是流Source的一部分,例如
val allRequests = Iterable(httpRequest1, httpRequest2, httpRequest3)
//no queue necessary
val allResponses : Future[Seq[HttpResponse]] =
Source(allRequests)
.via(poolClientFlow)
.to(Sink.seq[HttpResponse])
.run()
现在无需担心队列,最大队列大小等。所有内容都捆绑在一个漂亮的紧凑流中。
即使请求源是动态的,您仍然可以使用Source。假设我们从控制台stdin获取请求路径,这仍然是一个完整的流:
import scala.io.{Source => ioSource}
val consoleLines : () => Iterator[String] =
() => ioSource.stdin.getLines()
Source
.fromIterator(consoleLines)
.map(consoleLine => HttpRequest(GET, uri = Uri(consoleLine)))
.via(poolClientFlow)
.to(Sink.foreach[HttpResponse](println))
.run()
现在,即使每行都以随机间隔键入控制台,流仍然可以在没有队列的情况下进行反应。
我必须创建一个可以传递给第三方API的回调函数,但是每个看到队列的唯一实例,或Source.ActorRef
,绝对必要。此回调函数必须将传入元素提供给队列。