Какова стоимость создания актеров в Акке?
Рассмотрим сценарий, в котором я реализую систему, которая обрабатывает входящие задачи с помощью Akka. У меня есть основной актер, который получает задания и отправляет их некоторым работникам, которые обрабатывают задачи.
Мой первый инстинкт - реализовать это, если диспетчер создаст актера для каждой входящей задачи. После того, как рабочий-актер обработает задачу, он будет остановлен.
Это кажется самым чистым решением для меня, поскольку оно придерживается принципа "одна задача, один актер". Другим решением будет повторное использование актеров, но это связано с дополнительной сложностью очистки и некоторого управления пулами.
Я знаю, что актеры в Акке дешевы. Но мне интересно, есть ли неотъемлемая стоимость, связанная с повторным созданием и удалением участников. Есть ли скрытая стоимость, связанная с структурами данных, которые Akka использует для учета актеров?
Нагрузка должна составлять порядка десятков или сотен задач в секунду - подумайте об этом как о производственном веб-сервере, который создает один актер для каждого запроса.
Конечно, правильный ответ заключается в профилировании и точной настройке системы на основе типа входящей нагрузки.
Но я подумал, может ли кто-нибудь рассказать мне что-то по собственному опыту?
LATER EDIT:
Мне нужно дать более подробную информацию о задаче:
- В какой-то момент можно запустить только N активных задач. Как отметил @drexin - это было бы легко разрешимо с помощью маршрутизаторов. Тем не менее, выполнение задач не является простым прогоном и должно быть выполнено.
- Задачи могут потребовать информацию от других участников или служб и, следовательно, возможно, придется ждать и уснуть. Поступая таким образом, они освобождают слот выполнения. Слот может быть взят другим ожидающим игроком, который теперь имеет возможность запускать. Вы можете сделать аналогию с тем, как процессы запланированы на одном CPU.
- Каждому действующему актеру необходимо сохранить какое-то состояние относительно выполнения задачи.
Примечание.. Я ценю альтернативные решения моей проблемы, и я обязательно их рассмотрю. Однако мне также хотелось бы ответить на главный вопрос относительно интенсивного создания и удаления актеров в Акке.
Ответы
Ответ 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