我是akka-http的新手,并且在同一路径上并行运行查询时遇到麻烦。
我有一个可以非常快速地返回结果的路由(如果缓存)或者没有(繁重的CPU多线程计算)。我想并行运行这些查询,如果一个较短的查询在经过长时间的计算之后到达,我不希望第二次调用等待第一次完成。
但是,如果这些查询位于同一路径上并且并行运行(如果在不同的路由上并行运行),则这些查询似乎不会并行运行。
我可以在一个基本项目中重现它:
并行调用服务器3次(http://localhost:8080/test上有3个Chrome标签页)会导致响应分别达到3.0秒,6.0秒和9.0秒。我认为查询不会并行运行。
使用jdk 8在Windows 10上运行6核(带HT)计算机。
build.sbt
name := "akka-http-test"
version := "1.0"
scalaVersion := "2.11.8"
libraryDependencies += "com.typesafe.akka" %% "akka-http-experimental" % "2.4.11"
* AkkaHttpTest.scala **
import java.util.concurrent.Executors
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.server.Directives._
import akka.stream.ActorMaterializer
import scala.concurrent.{ExecutionContext, Future}
object AkkaHttpTest extends App {
implicit val actorSystem = ActorSystem("system") // no application.conf here
implicit val executionContext =
ExecutionContext.fromExecutor(Executors.newFixedThreadPool(6))
implicit val actorMaterializer = ActorMaterializer()
val route = path("test") {
onComplete(slowFunc()) { slowFuncResult =>
complete(slowFuncResult)
}
}
def slowFunc()(implicit ec: ExecutionContext): Future[String] = Future {
Thread.sleep(3000)
"Waited 3s"
}
Http().bindAndHandle(route, "localhost", 8080)
println("server started")
}
我在这里做错了什么?
感谢您的帮助
编辑:感谢@Ramon J Romero y Vigil,我添加了Future Wrapping,但问题仍然存在
def slowFunc()(implicit ec : ExecutionContext) : Future[String] = Future {
Thread.sleep(3000)
"Waited 3.0s"
}
val route = path("test") {
onComplete(slowFunc()) { slowFuncResult =>
complete(slowFuncResult)
}
}
尝试使用默认的线程池,配置文件中定义的线程池和固定线程池(6个线程)。
似乎onComplete指令仍然等待将来完成然后阻塞Route(使用相同的连接)。
Flow技巧的相同问题
import akka.stream.scaladsl.Flow
val parallelism = 10
val reqFlow =
Flow[HttpRequest].filter(_.getUri().path().equalsIgnoreCase("/test"))
.mapAsync(parallelism)(_ => slowFunc())
.map(str => HttpResponse(status=StatusCodes.Ok, entity=str))
Http().bindAndHandle(reqFlow, ...)
感谢您的帮助
答案 0 :(得分:5)
每个IncomingConnection由同一个路由处理,因此当您"并行呼叫服务器3次时#34;您可能使用相同的连接,因此使用相同的路由。
Route以akka-stream方式处理所有3个传入的HttpRequest
值,即Route由多个阶段组成,但每个阶段在任何给定时间只能处理1个元素。在你的例子中,"完成"流的阶段将为每个传入的请求调用Thread.sleep
并一次处理每个请求。
要同时处理多个并发请求,您应为每个请求建立唯一的连接。
可以创建客户端连接池的示例similar to the documentation examples:
import akka.http.scaladsl.Http
val connPoolFlow = Http().newHostConnectionPool("localhost", 8080)
然后可以将其集成到发出请求的流中:
import akka.http.scaladsl.model.Uri._
import akka.http.scaladsl.model.HttpRequest
val request = HttpRequest(uri="/test")
import akka.stream.scaladsl.Source
val reqStream =
Source.fromIterator(() => Iterator.continually(request).take(3))
.via(connPoolFlow)
.via(Flow.mapAsync(3)(identity))
.to(Sink foreach { resp => println(resp)})
.run()
路线修改
如果您希望每个HttpRequest
并行处理,那么您可以使用相同的Route来执行此操作,但您必须在Route内部生成Futures并使用onComplete
指令:
def slowFunc()(implicit ec : ExecutionContext) : Future[String] = Future {
Thread.sleep(1500)
"Waited 1.5s"
}
val route = path("test") {
onComplete(slowFunc()) { slowFuncResult =>
complete(slowFuncResult)
}
}
要注意的一件事是:如果您没有为睡眠功能指定不同的ExecutionContext,那么路由的相同线程池将用于您的睡眠。您可以通过这种方式耗尽可用的线程。你可能应该使用单独的ec来睡觉...
基于流量
处理HttpRequests的另一种方法是使用a stream Flow:
import akka.stream.scaladsl.Flow
val parallelism = 10
val reqFlow =
Flow[HttpRequest].filter(_.getUri().path().equalsIgnoreCase("/test"))
.mapAsync(parallelism)(_ => slowFunc())
.map(str => HttpResponse(status=StatusCodes.Ok, entity=str))
Http().bindAndHandle(reqFlow, ...)
答案 1 :(得分:1)
如果这仍然有意义,或者对于将来的读者,答案在Http().bindAndHandle
文档中:
/**
* Convenience method which starts a new HTTP server...
* ...
* The number of concurrently accepted connections can be configured by overriding
* the `akka.http.server.max-connections` setting....
* ...
*/
def bindAndHandle(...
使用akka.http.server.max-connections
设置并发连接数。