【问题标题】:Correct use of Akka http client connection pools正确使用 Akka http 客户端连接池
【发布时间】:2016-01-19 09:18:46
【问题描述】:

我需要使用 Akka 的 HTTP 客户端 (v2.0.2) 来使用 REST 服务。合乎逻辑的方法是通过主机连接池来执行此操作,因为我们预计会有大量的同时连接。对此的Flow 使用(HttpRequest, T) 并返回(Try[HttpResponse, T)。 documentation 表示需要一些任意类型的T 来管理对请求的潜在乱序响应,但没有指出调用者应该如何处理返回的T。

我的第一次尝试是下面的函数,使用Int 作为T。从许多地方调用它以确保连接使用单个池。

val pool = Http().cachedHostConnectionPool[Int]("127.0.0.1", 8888, ConnectionPoolSettings(system))

def pooledRequest(req: HttpRequest): Future[HttpResponse] = {
  val unique = Random.nextInt
  Source.single(req → unique).via(pool).runWith(Sink.head).flatMap {
    case (Success(r: HttpResponse), `unique`) ⇒ Future.successful(r)
    case (Failure(f), `unique`) ⇒ Future.failed(f)
    case (_, i) ⇒ Future.failed(new Exception("Return does not match the request"))
  }
}

问题是客户应该如何使用这个T?有没有更清洁更有效的解决方案?最后,我对某些事情可能会出现故障的妄想症实际上不是妄想症吗?

【问题讨论】:

    标签: scala akka-http


    【解决方案1】:

    我自己最初对此有点困惑,直到我通读了几次文档。如果您打算在池中使用单个请求,那么无论有多少不同的地方共享同一个池,您提供的T(在您的情况下为Int)都无关紧要。因此,如果您一直在使用Source.single,那么如果您真的想要,该密钥始终可以是1。

    但它确实发挥作用的地方是,如果一段代码要使用池并将多个请求一次提交到池中,并希望得到所有这些请求的响应。原因是响应按照从被调用的服务接收到的顺序返回,而不是按照它们提供给池的顺序返回。每个请求可能需要不同的时间,因此它们按照从池中接收回来的顺序向下游流向 Sink。

    假设我们有一个服务接受 GET 请求,其 url 格式如下:

    /product/123
    

    123 部分是您要查找的产品的 ID。如果我想一次查找产品1-10,并且对每个产品都有单独的请求,这就是标识符变得很重要的地方,这样我就可以将每个HttpResponse 与它所针对的产品ID 关联起来。此场景的简化代码示例如下:

    val requests = for(id <- 1 until 10) yield (HttpRequest(HttpMethods.GET, s"/product/$id"), id)
    val responsesMapFut:Future[Map[Int,HttpResponse]] = 
      Source(requests).
        via(pool).
        runFold(Map.empty[Int,HttpResponse]){
          case (m, (util.Success(resp), id)) => 
            m ++ Map(id -> resp)
    
          case (m, (util.Failure(ex), i)) =>
            //Log a failure here probably
              m
        }
    

    当我在fold 中收到我的回复时,我还方便地获得了每个相关联的 id,因此我可以将它们添加到我的Map 中,由 id 键入。如果没有这个功能,我可能不得不做一些事情,比如解析主体(如果它是 json)来尝试找出哪个响应是哪个响应是不理想的,并且不包括失败的情况。在这个解决方案中,我知道哪些请求失败了,因为我仍然得到了标识符。

    我希望这可以为您澄清一点。

    【讨论】:

    • 单源是一个相当简单的解决方案,但是有一个问题——当你到达max-open-requests时,它会抛出一个异常BufferOverflowException。最好使用official docs 中描述的队列源
    【解决方案2】:

    Akka HTTP 连接池在消耗基于 HTTP 的资源时是强大的盟友。如果您要一次执行单个请求,那么解决方案是:

    def exec(req: HttpRequest): Future[HttpResponse] = {
      Source.single(req → 1)
        .via(pool)
        .runWith(Sink.head).flatMap {
          case (Success(r: HttpResponse), _) ⇒ Future.successful(r)
          case (Failure(f), _) ⇒ Future.failed(f)
        }
    }
    

    因为您正在执行single 请求,所以无需消除响应的歧义。但是,Akka 流很聪明。您可以同时向池提交多个请求。在这种情况下,我们传入一个Iterable[HttpRequest]。返回的Iterable[HttpResponse] 使用SortedMap 重新排序到与原始请求相同的顺序。你可以通过request zip response 来安排:

    def exec(requests: Iterable[HttpRequest]): Future[Iterable[Future[HttpResponse]]] = {
      Source(requests.zipWithIndex.toMap)
        .via(pool)
        .runFold(SortedMap[Int, Future[HttpResponse]]()) {
          case (m, (Success(r), idx)) ⇒ m + (idx → Future.successful(r))
          case (m, (Failure(e), idx)) ⇒ m + (idx → Future.failed(e))
        }.map(r ⇒ r.values)
    }
    

    如果您需要按照自己的方式解包,那么可迭代 Future 的 Future 就很棒。只需将事物展平即可获得更简单的响应。

    def execFlatten(requests: Iterable[HttpRequest]): Future[Iterable[HttpResponse]] = {
      Source(requests.zipWithIndex.toMap)
        .via(pool)
        .runFold(SortedMap[Int, Future[HttpResponse]]()) {
          case (m, (Success(r), idx)) ⇒ m + (idx → Future.successful(r))
          case (m, (Failure(e), idx)) ⇒ m + (idx → Future.failed(e))
        }.flatMap(r ⇒ Future.sequence(r.values))
    }
    

    我已经使用所有导入和包装器创建了this gist,以创建一个使用 HTTP 服务的客户端。

    特别感谢 @cmbaxter 提供的简洁示例。

    【讨论】:

      【解决方案3】:

      有一张公开的票可以改进 akka-http 文档。请 check this example

      val pool = Http().cachedHostConnectionPool[Promise[HttpResponse]](host = "google.com", port = 80)
      val queue = Source.queue[(HttpRequest, Promise[HttpResponse])](10, OverflowStrategy.dropNew)
        .via(pool)
        .toMat(Sink.foreach({
           case ((Success(resp), p)) => p.success(resp)
          case ((Failure(e), p)) => p.failure(e)
        }))(Keep.left)
        .run
      
      
      val promise = Promise[HttpResponse]
      val request = HttpRequest(uri = "/") -> promise
      
      val response = queue.offer(request).flatMap(buffered => {
        if (buffered) promise.future
        else Future.failed(new RuntimeException())
      })
      

      【讨论】:

        猜你喜欢
        • 2017-02-03
        • 1970-01-01
        • 2020-12-28
        • 2016-10-28
        • 2020-04-10
        • 2020-05-16
        • 1970-01-01
        • 2019-04-11
        • 2017-08-04
        相关资源
        最近更新 更多