Подтвердить что ты не робот

Какова стоимость создания актеров в Акке?

Рассмотрим сценарий, в котором я реализую систему, которая обрабатывает входящие задачи с помощью Akka. У меня есть основной актер, который получает задания и отправляет их некоторым работникам, которые обрабатывают задачи.

Мой первый инстинкт - реализовать это, если диспетчер создаст актера для каждой входящей задачи. После того, как рабочий-актер обработает задачу, он будет остановлен.

Это кажется самым чистым решением для меня, поскольку оно придерживается принципа "одна задача, один актер". Другим решением будет повторное использование актеров, но это связано с дополнительной сложностью очистки и некоторого управления пулами.

Я знаю, что актеры в Акке дешевы. Но мне интересно, есть ли неотъемлемая стоимость, связанная с повторным созданием и удалением участников. Есть ли скрытая стоимость, связанная с структурами данных, которые Akka использует для учета актеров?

Нагрузка должна составлять порядка десятков или сотен задач в секунду - подумайте об этом как о производственном веб-сервере, который создает один актер для каждого запроса.

Конечно, правильный ответ заключается в профилировании и точной настройке системы на основе типа входящей нагрузки. Но я подумал, может ли кто-нибудь рассказать мне что-то по собственному опыту?

LATER EDIT:

Мне нужно дать более подробную информацию о задаче:

  • В какой-то момент можно запустить только N активных задач. Как отметил @drexin - это было бы легко разрешимо с помощью маршрутизаторов. Тем не менее, выполнение задач не является простым прогоном и должно быть выполнено.
  • Задачи могут потребовать информацию от других участников или служб и, следовательно, возможно, придется ждать и уснуть. Поступая таким образом, они освобождают слот выполнения. Слот может быть взят другим ожидающим игроком, который теперь имеет возможность запускать. Вы можете сделать аналогию с тем, как процессы запланированы на одном CPU.
  • Каждому действующему актеру необходимо сохранить какое-то состояние относительно выполнения задачи.

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

4b9b3361

Ответ 1

Вам не следует создавать актера для каждого запроса, лучше использовать маршрутизатор для отправки сообщений динамическому количеству участников. Для чего нужны маршрутизаторы. Прочтите эту часть документов для получения дополнительной информации: http://doc.akka.io/docs/akka/2.0.4/scala/routing.html

изменить:

Создание субъектов верхнего уровня (system.actorOf) дорого, потому что каждый актер верхнего уровня также инициализирует ядро ​​ошибок, и это дорого. Создание дочерних актеров (внутри актера context.actorOf) намного дешевле.

Но все же я предлагаю вам переосмыслить это, потому что в зависимости от частоты создания и удаления участников вы также будете оказывать дополнительное давление на GC.

edit2:

И самое главное, актеры - это не потоки! Таким образом, даже если вы создадите 1M актеров, они будут работать только на столько потоков, сколько у пула. Поэтому в зависимости от настройки пропускной способности в конфигурации каждый актер будет обрабатывать n сообщений до того, как поток снова будет выпущен в пул.

Обратите внимание, что блокирование потока (включая спящий) НЕ вернет его в пул!

Ответ 2

Актер, который получит одно сообщение сразу после его создания и умереть сразу после отправки результата, может быть заменен будущим. Фьючерсы более легкие, чем актеры.

Вы можете использовать pipeTo для получения будущего результата при его завершении. Например, у вашего актера, запускающего вычисления:

def receive = {
  case t: Task => future { executeTask( t ) }.pipeTo(self)
  case r: Result => processTheResult(r)
}

где executeTask - ваша функция, берущая Task для возврата Result.

Однако я бы повторно использовал актеров из пула через маршрутизатор, как описано в ответе @drexin.

Ответ 3

Я тестировал 10000 удаленных участников, созданных с помощью некоторого main контекста актером root, той же схемой, что и в модуле prod, был создан один актер. MBP 2,5 ГГц x2:

  • в основном: main? root//main запрашивает root для создания актера
  • в главном: actorOf (child)//создаем дочерний
  • в корне: watch (child)//просмотр сообщений жизненного цикла
  • в корне: root? child//ждать ответа (проверка соединения)
  • у ребенка: ребенок! root//response (connection ok)
  • в корне: root! main//уведомлять созданные

код:

def start(userName: String) = {
  logger.error("HELLOOOOOOOO ")
  val n: Int = 10000
  var t0, t1: Long = 0
  t0 = System.nanoTime
  for (i <- 0 to n) {
    val msg = StartClient(userName + i)
    Await.result(rootActor ? msg, timeout.duration).asInstanceOf[ClientStarted] match {
    case succ @ ClientStarted(userName) => 
      // logger.info("[C][SUCC] Client started: " + succ)
    case _ => 
      logger.error("Terminated on waiting for response from " + i + "-th actor")
      throw new RuntimeException("[C][FAIL] Could not start client: " + msg)
    }
  }
  t1 = System.nanoTime
  logger.error("Starting of a single actor of " + n + ": " + ((t1 - t0) / 1000000.0 / n.toDouble) + " ms")
}

Результат:

Starting of a single actor of 10000: 0.3642917 ms

Появилось сообщение о том, что "Slf4jEventHandler начал" между "HELOOOOOOOO" и "Starting of single", поэтому эксперимент кажется еще более реалистичным (?)

Диспетчеры были по умолчанию (PinnedDispatcher запускает новый поток каждый раз), и казалось, что все эти вещи такие же, как Thread.start(), долгое время с тех пор, как Java 1 - 500K-1M циклов или так что ^)

Вот почему я изменил весь код внутри цикла, на new java.lang.Thread().start()

Результат:

Starting of a single actor of 10000: 0.1355219 ms