Как spray.routing.HttpService отправляет запросы?

Отказ от ответственности: у меня пока нет опыта работы со Scala, поэтому мой вопрос связан с самыми основами.

Рассмотрим следующий пример (он может быть неполным):

import akka.actor.{ActorSystem, Props}
import akka.io.IO
import spray.can.Http
import akka.pattern.ask
import akka.util.Timeout
import scala.concurrent.duration._
import akka.actor.Actor
import spray.routing._
import spray.http._

object Boot extends App {
  implicit val system = ActorSystem("my-actor-system")
  val service = system.actorOf(Props[MyActor], "my")
  implicit val timeout = Timeout(5.seconds)
  IO(Http) ? Http.Bind(service, interface = "localhost", port = 8080)
}

class MyActor extends Actor with MyService {
  def actorRefFactory = context

  def receive = runRoute(myRoute)
}

trait MyService extends HttpService {
  val myRoute =
    path("my") {
      post {
        complete {
          "PONG"
        }
      }
    }
}

Мой вопрос: что на самом деле происходит, когда управление достигает блока complete? Вопрос кажется слишком общим, поэтому позвольте мне разделить его.

  1. В примере я вижу создание одного актора. Означает ли это, что приложение является однопоточным и использует только одно ядро ​​​​процессора?
  2. Что произойдет, если я заблокирую вызов внутри complete?
  3. Если с. 1 верно и п. 2 будет блокироваться, как мне отправить запросы на использование всего процессора? Я вижу два пути: актор на запрос и актор на соединение. Второй вариант кажется разумным, но я не могу найти способ сделать это с помощью библиотеки спреев.
  4. Если предыдущий вопрос не имеет значения, подойдет ли директива detach? А как насчет передачи функции, возвращающей Future, в директиву complete? В чем разница между функцией detach и функцией передачи, возвращающей Future?
  5. Как правильно настроить количество рабочих потоков и сбалансировать запросы/соединения?

Было бы здорово, если бы вы указали мне пояснения в официальной документации. Это очень обширно, и я считаю, что что-то упускаю.

Спасибо.


person Unforgiven    schedule 16.08.2015    source источник


Ответы (1)


На этот вопрос ответил здесь Матиас — один из авторов Spray. . Копирую его ответ для справки:

В конце концов, единственное, что действительно завершает запрос, — это вызов requestContext.complete. Таким образом, не имеет значения, из какого потока или контекста Актера сделан этот вызов. Все, что имеет значение, это то, что это происходит в течение настроенного периода «тайм-аута запроса». Вы, конечно, можете инициировать этот вызов самостоятельно тем или иным образом, но спрей дает вам ряд предопределенных конструкций, которые, возможно, лучше подходят для вашей архитектуры, чем передача фактического RequestContext. В основном это:

  1. Директива complete, которая просто добавляет немного сахара поверх «сырого» литерала функции ctx => ctx.complete(…).
  2. Future Marshaller, который вызывает ctx.complete из обработчика future.onComplete.
  3. Директива produce, которая извлекает функцию T => Unit, которую впоследствии можно использовать для завершения запроса с экземпляром пользовательского типа.

С архитектурной точки зрения в большинстве случаев рекомендуется не допускать «просачивания» уровня API в ядро ​​вашего приложения. т.е. приложение не должно ничего знать об уровне API или HTTP. Он должен иметь дело только с объектами своей собственной модели предметной области. Поэтому передача RequestContext напрямую ядру приложения — не лучшее решение.

Прибегнуть к «спросу» и положиться на Future Marshaller — очевидная, хорошо понятная и довольно простая альтернатива. Это имеет (небольшой) недостаток, заключающийся в том, что запрос поставляется с обязательной проверкой тайм-аута, которая логически не требуется (поскольку слой аэрозольного баллончика уже заботится о тайм-аутах запроса). Тайм-аут запроса требуется по техническим причинам (поэтому базовый PromiseActorRef можно очистить, если ожидаемый ответ так и не придет).

Другой альтернативой передаче RequestContext является директива produce (например, produce(instanceOf[Foo]) { completer => …). Он извлекает функцию, которую вы можете передать ядру приложения. Когда ваша базовая логика вызывает complete(foo), запускается логика завершения, и запрос завершается. Таким образом, ядро ​​приложения остается отделенным от уровня API, а накладные расходы минимальны. Недостатки этого подхода двоякие: во-первых, функция завершения не сериализуема, поэтому вы не можете использовать этот подход за пределами JVM. И, во-вторых, логика завершения теперь работает непосредственно в контексте актора ядра приложения, что может нежелательным образом изменить поведение во время выполнения, если Marshaller[Foo] должен выполнять нетривиальные задачи.

Третий вариант заключается в том, чтобы создать актора для каждого запроса на уровне API и заставить его обрабатывать ответ, возвращаемый ядром приложения. Тогда вам не придется использовать аск. Тем не менее, вы сталкиваетесь с той же проблемой, что и PromiseActorRef, лежащий в основе запроса: как очиститься, если ядро ​​приложения никогда не отвечает? С актером повторного запроса у вас есть полная свобода реализовать решение этого вопроса. Однако, если вы решите положиться на тайм-аут (например, через context.setReceiveTimeout), преимущества перед «аском» могут отсутствовать.

Какое из описанных решений лучше всего подходит для вашей архитектуры, решать вам самостоятельно. Однако, как я, надеюсь, смог показать, у вас есть несколько альтернатив на выбор.

Чтобы ответить на некоторые из ваших конкретных вопросов: существует только один актер/обработчик, который обслуживает маршрут, поэтому, если вы заблокируете его, Spray заблокирует. Это означает, что вы хотите либо завершить маршрут немедленно, либо отправить работу, используя один из трех вариантов выше.

В Интернете есть много примеров для этих 3 вариантов. Самый простой способ — поместить ваш код в файл Future. Отметьте также вариант/пример «актер по запросу». В конце концов, ваша архитектура определит наиболее подходящий путь.

Наконец, Spray работает поверх Akka, поэтому все настройки Akka остаются в силе. См. HOCON reference.conf и application.conf для настройки многопоточности Актера.

person yǝsʞǝla    schedule 17.08.2015