performance - latest - ¿Cuál es el costo de crear actores en Akka?
akka latest (3)
Considere un escenario en el que estoy implementando un sistema que procesa las tareas entrantes utilizando Akka. Tengo un actor principal que recibe tareas y las envía a algunos trabajadores que procesan las tareas.
Mi primer instinto es implementar esto haciendo que el despachador cree un actor para cada tarea entrante. Después de que el actor trabajador procesa la tarea se detiene.
Esta parece ser la solución más limpia para mí, ya que se adhiere al principio de "una tarea, un actor". La otra solución sería reutilizar los actores, pero esto implica la complejidad adicional de la limpieza y la administración de algunos grupos.
Sé que los actores en Akka son baratos. Pero me pregunto si hay un costo inherente asociado con la creación y eliminación repetidas de actores. ¿Hay algún costo oculto asociado con las estructuras de datos que Akka utiliza para la contabilidad de los actores?
La carga debe ser del orden de decenas o cientos de tareas por segundo. Piense en ello como un servidor web de producción que crea un actor por solicitud.
Por supuesto, la respuesta correcta se encuentra en el perfilado y el ajuste fino del sistema según el tipo de carga entrante. Pero me preguntaba si alguien podría decirme algo de su propia experiencia.
DESPUÉS DE EDITAR:
Debería dar más detalles sobre la tarea en cuestión:
- Solo N tareas activas pueden ejecutarse en algún punto. Como señaló @drexin, esto podría solucionarse fácilmente utilizando enrutadores. Sin embargo, la ejecución de tareas no es una simple ejecución y se realiza de forma sencilla.
- Las tareas pueden requerir información de otros actores o servicios y, por lo tanto, pueden tener que esperar y quedarse dormidos. Al hacerlo, liberan una ranura de ejecución. La ranura puede ser tomada por otro actor que espera, que ahora tiene la oportunidad de correr. Podría hacer una analogía con la forma en que se programan los procesos en una CPU.
- Cada actor trabajador debe mantener algún estado con respecto a la ejecución de la tarea.
Nota: Aprecio soluciones alternativas a mi problema, y ciertamente las tomaré en consideración. Sin embargo, también me gustaría una respuesta a la pregunta principal sobre la creación y eliminación intensiva de actores en Akka.
He probado con 10000 actores remotos creados a partir de un contexto main
por un actor root
, el mismo esquema que en el módulo prod fue creado un solo actor. MBP 2.5GHz x2:
- en principal: principal? root // main pide root para crear un actor
- en main: actorOf (niño) // crea un niño
- in root: watch (child) // mira los mensajes del ciclo de vida
- en la raíz: la raíz? niño // espera respuesta (verificación de conexión)
- en niño: niño! raíz // respuesta (conexión ok)
- en la raíz: la raíz! main // notificar creado
Código:
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")
}
El resultado:
Starting of a single actor of 10000: 0.3642917 ms
Hubo un mensaje que indicaba que "Slf4jEventHandler comenzó" entre "HELOOOOOOOO" y "A partir de una sola", por lo que el experimento parece aún más realista (?)
Dispatchers era un valor predeterminado (un PinnedDispatcher que iniciaba un nuevo hilo cada vez), y parecía que todas esas cosas son iguales a las de Thread.start()
, desde hace mucho tiempo desde que Java 1 - 500K-1M ciclos más o menos ^)
Es por eso que he cambiado todo el código dentro del bucle, a un new java.lang.Thread().start()
El resultado:
Starting of a single actor of 10000: 0.1355219 ms
No debe crear un actor para cada solicitud, debe usar un enrutador para enviar los mensajes a una cantidad dinámica de actores. Para eso son los enrutadores. Lea esta parte de los documentos para obtener más información: http://doc.akka.io/docs/akka/2.0.4/scala/routing.html
editar:
Crear actores de nivel superior ( system.actorOf
) es costoso, ya que cada actor de nivel superior también inicializará un kernel de error y esos son costosos. Crear actores infantiles (dentro de un actor context.actorOf
) es mucho más barato.
Pero aún así le sugiero que reconsidere esto, ya que dependiendo de la frecuencia de creación y eliminación de actores, también pondrá una presión adicional sobre el GC.
edit2:
Y lo más importante, los actores no son hilos! Así que incluso si creas actores 1M, solo se ejecutarán en tantos subprocesos como el grupo. Entonces, dependiendo de la configuración de rendimiento en la configuración, cada actor procesará n mensajes antes de que el hilo se lance nuevamente al grupo.
¡Tenga en cuenta que bloquear un hilo (incluye dormir) NO lo devolverá a la piscina!
Un actor que recibirá un mensaje justo después de su creación y fallecerá justo después de enviar el resultado puede ser reemplazado por un futuro. Los futuros son más ligeros que los actores.
Puede usar pipeTo
para recibir el resultado futuro cuando se haga. Por ejemplo en tu actor lanzando los cálculos:
def receive = {
case t: Task => future { executeTask( t ) }.pipeTo(self)
case r: Result => processTheResult(r)
}
donde executeTask
es su función que toma una Task
para devolver un Result
.
Sin embargo, reutilizaría actores de una agrupación a través de un enrutador como se explica en la respuesta de @drexin.