¿Cuándo NO es bueno usar actores en akka / erlang?

58

He estado trabajando con akka durante 7-8 meses a diario. Cuando comencé, estaba trabajando en aplicaciones y noté que los actores se usarían básicamente en cualquier lugar una vez dentro del sistema de actores para comunicarse entre la mayoría de los objetos. Así que hice lo mismo: girar a otro actor para x / y / z.

Me parece que esto puede ser demasiado indiscriminado, agregando complejidad donde no se necesita, pero no puedo encontrar ninguna discusión sobre dónde deberían usarse los actores frente a la lógica síncrona simple o incluso asíncrona a través de futuros. Comencé a reflexionar sobre mi postura después de que mi compañero de trabajo mencionó algo similar. Me di cuenta de varios casos más recientes en los que he reflexionado sobre una tarea y luego evité crear otro actor porque podría lograr el mismo resultado de forma segura en una implementación inmutable, por ejemplo, algo como obtener valores de configuración de un archivo o archivo en algún lugar al que accedes con poca frecuencia y lo harás Espere a que el resultado sea el caso de uso real.

En particular, me parece que en cualquier caso en el que juegas con un estado inmutable, los actores crean complejidad y limitan el rendimiento: una función pura en un objeto, por ejemplo, se puede llamar simultáneamente sin ningún riesgo con ningún nivel de concurrencia, aún un actor solo puede procesar un mensaje a la vez. La consideración alternativa es que estacionará el hilo si necesita esperar el resultado a menos que comience a usar futuros, pero en los casos en que no necesita preocuparse por la mensajería asíncrona o la escala, parece que puede ser excesivo contratar a un actor.

Entonces mi pregunta es: ¿hay un mal momento para usar actores? Tengo curiosidad por cómo se ve Erlang y realmente me gustaría la visión de otras personas. O si hay algunos principios en torno al uso del actor.

JasonG
fuente
¿Qué tipo de trabajo da la oportunidad de trabajar con Akka diariamente durante meses?
Den
3
Buenos. :) O haz el cambio tú mismo.
JasonG
Me gustaría saber específicamente cuáles son las compensaciones entre askun actor y solo usar un plano Future.
Max Heiber
2
Terminé escribiendo un libro sobre Akka un par de años más tarde y creo que puedes resumirlo así: si tienes algún estado, por ejemplo, un contador, varios hilos que leen y escriben desde / hacia ese valor terminarán pisoteándose unos a otros. sin sincronización y bloqueo. Si coloca ese estado dentro de un actor, de repente puede estar seguro de que se accede a él de forma segura y no está dejando caer las escrituras ni obteniendo lecturas obsoletas. Los actores en erlang y akka también suponen que el estado puede ir mal y el actor puede arrojar errores para que tenga algunas características del sistema de autocuración. Los futuros son más simples si no necesitas mutar
JasonG
muchos años después soy un programador de erlang / elixir y sigo volviendo a esto con nuevas ideas :) Elixir es bastante diferente ya que no hay objetos como alternativa a los procesos, por lo que los procesos siempre se crean cuando se necesita un estado . Simplemente resulta ser concurrente. Esta es la mejor heurística aún.
JasonG

Respuestas:

23

Esta es una pregunta que me interesa y en la que he estado investigando. Para otros puntos de vista, vea esta publicación de blog de Noel Walsh o esta pregunta sobre Stack Overflow. Tengo algunas opiniones que me gustaría ofrecer:

  • Creo que Akka, porque funciona con mensajes, fomenta una "mentalidad de empuje". A menudo, por concurrencia, diría que esto no es lo que quieres. Tirar es mucho más seguro. Por ejemplo, un patrón común para sistemas distribuidos es tener un conjunto de trabajadores procesando información en una cola . Obviamente, esto es posible en Akka, pero no necesariamente parece ser el primer enfoque que la gente intenta . Akka también ofrece buzones duraderos, pero de nuevo depende de cómo los use: una sola cola compartida es mucho más flexible que las colas por trabajador para equilibrar / reasignar el trabajo.
  • Es fácil entrar en la mentalidad de reemplazar sus clases con actores. De hecho, algunas personas incluso parecen defenderlo diciendo que los actores solo deberían hacer una cosa . Llevado a su conclusión lógica, esto aumenta la complejidad del código como lo describe Jason, porque si cada clase es un actor, eso es una gran cantidad de mensajes adicionales y bloques de recepción / envío. También hace que sea más difícil entender y probar el código porque pierde la formalidad de las interfaces, y no estoy convencido de que los comentarios sean una solución para esto . Además, a pesar de la eficiencia legendaria de Akka , sospecho que la proliferación de actores no es una buena idea en cuanto al rendimiento: cuando usamos hilos Java sabemos que son preciosos y los conservamos en consecuencia.
  • Está relacionado con el punto anterior, pero otra molestia es la pérdida de información de tipo que Noel y Pino destacan, ya que para muchos de nosotros es por eso que estamos usando Scala en lugar de otros lenguajes como Python. Hay algunas formas de evitar esto, pero no son estándar , no se recomiendan o son experimentales .
  • Finalmente, la concurrencia, incluso si tiene una mentalidad de "dejar que se cuelgue" , es difícil. Los modelos de programación alternativos pueden ayudar, pero no hacen que los problemas desaparezcan, los cambian, por eso es bueno pensar en ellos formalmente . También es la razón por la cual el desarrollador Joe Average busca herramientas listas para usar como bases de datos RabbitMQ, Storm, Hadoop, Spark, Kafka o NoSQL. Akka tiene algunas herramientas y componentes preconstruidos, lo cual es genial, pero también se siente a un nivel bastante bajo, por lo que elementos comunes de sistemas distribuidos más listos ayudarían a los desarrolladores y garantizarían que los sistemas se construyan correctamente.

Al igual que Jason, estoy ansioso por escuchar la opinión de otras personas aquí. ¿Cómo puedo abordar algunos de los problemas anteriores y usar Akka mejor?

Mark Butler
fuente
1
Encontré esta publicación sobre pruebas en Akka spot-on timgilbert.wordpress.com/2013/07/07/…
Mark Butler
"Composición sobre herencia" sigue siendo útil para la inyección de dependencia. Por ejemplo, pase la dependencia como accesorios (usa parámetros de constructor). El problema sería la supervisión: el actor creado sería supervisado en otro lugar que no sea el actor. Se podría usar un enrutador para envolver una estrategia de supervisión alrededor de los actores creados en el nivel superior, pero ¡eso es bastante complejo ahora! Supongo que el pastel sería una mejor solución: tener un rasgo con la creación del actor para un actor de servicio de base de datos, por ejemplo. Luego, solo use una característica de prueba simulada / stub db service en contexto de texto.
JasonG
1
Sí, exactamente: la supervisión no funciona bien con la inyección de dependencia. También he tenido casos en los que era necesario inyectar una fábrica en lugar de la clase, de lo contrario, hubo problemas de cierre extraños, supongo que debido al tratamiento de hilos de Akka.
Mark Butler
Creo que no es una mala idea, gracias, podría tratar de escribir un poco sobre esto y socializarlo. ¿Podría inyectar algo que le dé a los accesorios quizás? Una especie de fábrica de actores donde el consumidor puede instanciar al actor. por ejemplo, método def (arg) = devolver Props (nuevo ActorWithArgs (arg)) de esa fábrica (psuedo?) para que pueda hacer el actor en el contexto correcto. Esto parece una buena idea y un buen objetivo para las soluciones de pastel para di también.
JasonG
Acabo de comenzar a seguir este curso: coursera.org/course/posa Aunque está dirigido principalmente a personas que programan Android, también es una buena descripción general de la concurrencia en Java. Entonces, una cosa que me pregunto es "¿Acaso Akka no es solo una forma elegante de bucles de eventos con algunas campanas y silbatos (porque puedes poner bucles de eventos en diferentes hilos)?"
Mark Butler
21

Vale la pena considerar para qué se utiliza el modelo de actor: el modelo de actor es

  1. un modelo de concurrencia
  2. que evita el acceso concurrente al estado mutable
  3. utilizando mecanismos de comunicaciones asincrónicas para proporcionar concurrencia.

Esto es valioso porque el uso del estado compartido de múltiples subprocesos se vuelve realmente difícil, especialmente cuando hay relaciones entre diferentes componentes del estado compartido que deben mantenerse sincronizados. Sin embargo, si tiene componentes de dominio en los que:

  • No permite concurrencia, O
  • No permite el estado mutable (como en la programación funcional), O
  • Debe confiar en algún mecanismo de comunicaciones síncronas,

entonces el modelo de actor no proporcionará mucho beneficio (si lo hay).

Espero que ayude.

Aidan Cully
fuente
Gracias. Supongo que realmente tienes que evaluar dos cosas: ¿estado mutable y concurrencia? No hay problema para resolver si una clase no tiene estado O no es concurrente. Una consideración más: ¿creo que la tolerancia a fallas es otro punto? por ejemplo, si tiene un cliente redis inmutable pero puede fallar mientras se ejecuta, ¿no sería un buen caso de uso? que reiniciar ese actor puede ser necesario? Como, aunque la clase puede ser puramente inmutable, es muy posible que haya un estado corrupto externo al actor inmutable que puede causar que falle.
JasonG
Sin embargo, supongo que el actor responsable de comunicarse con redis recibirá la excepción y se reiniciará.
JasonG
@JasonG En mi opinión, la "tolerancia a fallas" no es un aspecto del modelo de actor en general, es algo que viene con la implementación del modelo de actor en Erlang (¿y quizás Akka?). No puedo hablar con redis, aunque tengo que admitir que "cliente inmutable" me parece extraño ... Si un componente de software puede fallar, no veo cómo puede considerarse inmutable.
Aidan Cully
La tolerancia a fallas y la supervisión existen en akka y es muy similar a erlang. Entiendo lo que está diciendo sobre el cliente inmutable, pero inmutable significa que el estado del código no cambia en ningún lado. Si inicializo una conexión al inicio, es posible que el código del cliente pueda ser inmutable con una estrategia de reinicio utilizada en cualquier tipo de falla, simplemente reinicializando al actor al encontrar un problema.
JasonG
12

Tu intuición es correcta, en mi humilde opinión. Usar actores en todas partes es como tener el martillo proverbial y ver solo clavos.

La mejor práctica de Erlang es utilizar procesos / actores para todas las actividades que suceden simultáneamente. Es decir, como en la vida real. A veces es difícil encontrar la granularidad correcta, pero la mayoría de las veces solo se sabe mirando el dominio modelado y utilizando un poco de sentido común. Me temo que no tengo un método mejor que ese, pero espero que ayude.

Vlad Dumitrescu
fuente
Genial, gracias. Estoy realmente interesado en ver qué sucede en esta discusión.
JasonG
0

En orden de entrada / salida de mensajes:

Recientemente me encontré con una aplicación basada en akka donde el modelo de actor realmente causó problemas de concurrencia, un modelo más simple hubiera sido mejor bajo carga.

El problema era que los mensajes entrantes se movían en diferentes 'carriles' (a través de diferentes rutas de actores) pero el código asumía que los mensajes llegarían a su destino final en el mismo orden en que llegaron. Mientras los datos llegaran con intervalos suficientemente largos, esto funcionaría porque solo habría un mensaje conflictivo en carrera hacia el destino. Cuando los intervalos disminuyeron, comenzaron a llegar fuera de servicio y causaron un comportamiento extraño.

El problema podría haberse resuelto correctamente con un poco menos de actores, pero es un error fácil de hacer al usarlos en exceso.

DHa
fuente
Esto es fundamental y ampliamente relevante. Solo puede garantizar el pedido entre dos actores. doc.akka.io/docs/akka/current/scala/general/… No puede garantizar la entrega de pedidos en ningún sistema en el que se despliegue / active . andreasohlund.net/2012/01/18/dont-assume-message-ordering
JasonG
-1

En mi opinión, hay dos casos de uso para los actores. Recursos compartidos como puertos y similares, y gran estado. El primero ha sido bien cubierto por la discusión hasta ahora, pero el estado grande también es una razón válida.

Una estructura grande que se pasa con cada llamada a procedimiento puede usar mucha pila. Este estado se puede colocar en un proceso separado, la estructura se reemplaza por una identificación de proceso y ese proceso se consulta según sea necesario.

Las bases de datos como mnesia pueden considerarse como un estado de almacenamiento externo al proceso de consulta.

Tony Wallace
fuente
1
¿Puedes aclarar? ¿Te refieres a un gran estado en toda la red? Dentro de un proceso local, pasar una referencia a una estructura inmutable casi no tiene costo y no puede pasar estructuras mutables. ¿Asumiendo que está hablando de una gran estructura mutable que necesita ser copiada para enviar? Entonces, eso es básicamente lo mismo otra vez: estado compartido. El tamaño realmente no cambia nada. Avísame si estoy malentendido.
JasonG
Entonces, ¿cómo pasas por referencia? Seguramente la forma de hacerlo es poner la estructura en un proceso y pasar el processid. Las llamadas locales pondrán los datos en la pila, y cada llamada recursiva lo volverá a hacer (con la excepción de la recursión de cola). El procesamiento recursivo de la estructura se puede hacer usando listas para transferir el estado de una llamada a la otra, este estado puede hacer referencia a la estructura en el otro proceso.
Tony Wallace