2012-04-09 11 views
42

He estado tratando de entender los conceptos de Akka y los sistemas basados ​​en actores recientemente. Si bien tengo una buena comprensión de los fundamentos de Akka, todavía estoy luchando con algunas cosas cuando se trata de agrupar y actores remotos.Descubrimiento de actores Akka en el clúster

Trataré de ilustrar el problema utilizando el WebSocket chat example that comes with Play Framework 2.0: Hay un actor que tiene los WebSockets y que mantiene una lista de los usuarios conectados actualmente. Los actores básicamente representan la sala de chat tanto técnica como lógicamente. Esto funciona perfectamente bien siempre y cuando haya una sola sala de chat ejecutándose en un único servidor.

Ahora trato de entender cómo debería extenderse este ejemplo cuando hablamos de muchas salas de chat dinámicas (las nuevas salas se pueden abrir/cerrar en cualquier momento) que se ejecutan en un clúster de servidores (con nodos individuales ser agregado o eliminado según la demanda actual). En tal caso, el usuario A podría conectarse al servidor 1 mientras que el usuario B se conecta al servidor 2. Ambos podrían estar hablando en la misma sala de chat. En cada servidor, todavía habría un actor (¿para cada sala de chat?) Que contiene las instancias de WebSocket para recibir y publicar eventos (mensajes) a los usuarios correctos. Pero, lógicamente, solo debe haber un actor de la sala de chat en el servidor 1 o el servidor 2 que contiene la lista de usuarios actualmente conectados (o tareas similares).

¿Cómo harías para lograr esto, preferiblemente en "akka puro" y sin agregar un sistema de mensajería adicional como ZeroMQ o RabbitMQ?

Esto es lo que he encontrado hasta el momento, por favor, hágamelo saber si esto tiene sentido:

  1. Un usuario se conecta al servidor 1 y un actor que se asigna sostiene su WebSocket.
  2. El actor comprueba (utilizando Router? EventBus? Something else?) Si un "actor de sala de chat" para la sala de chat activa existe en cualquiera de los nodos de clúster conectados. Como no lo hará, solicitará la creación de un nuevo actor de la sala de chat de alguna manera y enviará y recibirá futuros mensajes de chat a/de este actor.
  3. El usuario B se conecta en el servidor 2 y también se asigna un actor para su WebSocket.
  4. También comprueba si un actor para la sala de chat solicitada existe en alguna parte y lo encuentra en el servidor 1.
  5. El actor de la sala de chat en el servidor 1 ahora actúa como el centro de la sala de chat, enviando mensajes a todos los "conectados" "Actores miembros de chat y distribución de los entrantes.

Si el servidor 2 se cae, el actor de la sala de chat tendría que volver a crearse/moverse al servidor 2 de alguna manera, aunque esta no es mi principal preocupación en este momento. Me pregunto más acerca de cómo este descubrimiento dinámico de actores se extendió sobre varias máquinas, básicamente independientes, que podrían hacerse usando el conjunto de herramientas de Akka.

He estado buscando en la documentación de Akka desde hace bastante tiempo, así que tal vez me falta lo obvio aquí. Si es así, por favor aclararme :-)

Respuesta

11

Estoy trabajando en un proyecto privado que es básicamente una versión muy extendida del ejemplo de sala de chat y también tuve problemas de inicio con akka y todo el pensamiento "descentralizado". Así puedo decirle cómo "resolví" mi sala de chat extendida:

Quería un servidor que se pudiera implementar fácilmente varias veces sin demasiada configuración adicional. Estoy usando redis como almacenamiento para todas las sesiones de usuario abiertas (serialización simple de sus ActorRefs) y para todas las salas de chat.

El servidor tiene los siguientes actores:

  • WebsocketSession: que mantiene la conexión con un usuario y gestiona las peticiones del usuario y envía mensajes del sistema.
  • ChatroomManager: esta es la emisora ​​central, que se implementa en cada instancia del servidor. Si un usuario desea enviar un mensaje a una sala de chat, WebSocketSession-Actor envía toda la información al ChatroomManager-Actor, que luego transmite el mensaje a todos los miembros de la sala de chat.

Así que aquí es mi procedimiento:

  1. Un usuario se conecta al servidor 1, que asigna un nuevo WebsocketSession. Este actor inserta el camino absoluto a este actor en redis.
  2. Usuario A se une a una sala de chat X que también inserta su ruta absoluta (uso esto como la ID única de una sesión de usuario) en redis (cada sala tiene un conjunto de "conexiones")
  3. El usuario B se conecta al servidor 2 - > Redis
  4. usuario B se une sala de chat X -> Redis
  5. usuario B envía un mensaje al chat que X de la siguiente manera: el usuario B envía su mensaje a través del WebSocket a su sesión de actor, que (después de algunos controles) envía un actor de -mensaje al ChatroomManager. Este actor en realidad recupera la lista de usuarios de la sala de chat de redis (rutas absolutas utilizadas con el método actorFor de akka) y luego envía el mensaje a cada actor de sesión. Estos actores de la sesión luego escriben en sus websockets.

En cada ChatroomManager-actor hago un poco de caché ActorRef que le dio velocidad adicional. Creo que esto difiere de su enfoque, especialmente que estos ChatroomManagers manejan las solicitudes de todos los chats. Pero tener un actor para una sala de chat es un punto único de falla que quería evitar. Además sería esto causar muchos más mensajes, por ejemplo:

  • usuario A y el usuario B están en el servidor 1.
  • Chatroom X está en el servidor 2.

Si el usuario A quiere hablar usuario B, ambos tendrían que comunicarse por la sala de chat-actor en el servidor 1.

Además, utilicé las funcionalidades de Akka como (round-robin) -rutadores para crear múltiples instancias de un ChatroomManager-actor en cada sistema para manejar muchas solicitudes

Dediqué algunos días a configurar toda la infraestructura remota de Akka en combinación con serialización y redis. Pero ahora puedo crear cualquier cantidad de instancias de la aplicación de servidor que use redis para compartir allí ActorRef s (serializadas como rutas absolutas con puerto ip +).

Esto puede ayudarlo un poco más y estoy abierto para nuevas preguntas (por favor no sobre mi inglés;).

+1

Estoy de acuerdo con la respuesta de Freed y que puede haber simplemente desplazados el problema de un único punto de fallo hacia el almacenamiento. Por supuesto, un caché distribuido es una mejora, pero como dijo Freed no es perfecto. Actualmente estoy accediendo a redis a través de un 'Storage'-Actor implementado que actualmente maneja solicitudes de datos (como obtener la lista de miembros actuales). Si lo hice bien, para mi implementación este sería el lugar para evitar un cuello de botella con una futura implementación de clúster de akka. – th3hamm0r

+0

no necesita esperar las extensiones de agrupamiento Akka, consulte las publicaciones de blog vinculadas en mi respuesta. Akka está muy lejos de soportar el agrupamiento completo. – SoftMemes

+0

La lista de membresías de salas de chat se puede mantener en dos actores en dos servidores para tolerancia a errores con actualizaciones enviadas a ambos. Consulte este documento que describe cómo hacer esto con paxos y hash mod de la ID de sala de chat para asignar host primario/secundario para la sala de chat. Luego, cada cliente websocket calcula los dos servidores para saber cuál es la principal para consultar, y luego volver a la secundaria si ese host baja https://www.dropbox.com/s/iihpq9bjcfver07/VLDB-Paper.pdf (claramente es mejor comprarlo). ese servidor de caché de sesión ahora se llama aerospike que lo escribes tú mismo, pero su algoritmo y diseño es muy informativo). – simbo1905

9

La clave para escalar en varias máquinas es mantener el estado mutable lo más aislado posible. Aunque puede usar un caché distribuido para coordinar el estado en todos los nodos, esto le daría problemas de sincronización y cuello de botella al escalar a una gran cantidad de nodos.Idealmente, entonces, debería haber un solo actor que conozca los mensajes y los participantes en una sala de chat.

El núcleo de su problema es, si una sala de chat está representada por un único actor que se ejecuta en una sola máquina, o incluso si tal sala existe. El truco consiste en enrutar las solicitudes relacionadas con una determinada sala de chat utilizando un identificador, como el nombre de la sala de chat. Calcule el hash del nombre y, según el número, elija uno de los n cuadros. El nodo sabrá sobre sus salas de chat actuales y podrá encontrar o crear de forma segura el actor correcto de la sala de chat para usted.

Usted puede echar un vistazo a las siguientes entradas del blog discusión de la agrupación y la ampliación en Akka:

http://blog.softmemes.com/2012/06/16/clustered-akka-building-akka-2-2-today-part-1/

http://blog.softmemes.com/2012/06/16/clustered-akka-building-akka-2-2-today-part-2/

6

me gustaría utilizar Zookeeper + Norbert a saber sobre qué hosts están subiendo y abajo:

http://www.ibm.com/developerworks/library/j-zookeeper/

Ahora cada nodo en mi granja de servidores de chat puede conocer todos los hosts en el cluster lógico. Recibirán una devolución de llamada cuando un nodo se desconecta (o entra en línea). Ahora cualquier nodo puede mantener una lista ordenada de miembros de clúster actuales, hash la ID de sala de chat y mod por tamaño de lista para obtener el índice dentro de la lista, que es el nodo que debe alojar cualquier sala de chat determinada. Podemos agregar 1 y volver a crear un segundo índice (requiere un bucle hasta que obtenga un índice nuevo) para calcular el segundo host para que contenga una segunda copia de la sala de chat para redundancia. En cada uno de los dos hosts de salas de chat hay un actor de sala de chat que reenvía todos los mensajes de chat a cada actor de Websocket que es miembro de la sala de chat.

Ahora podemos enviar mensajes de chat a través de ambos actores de sala de chat activos con un enrutador Akka personalizado. Un cliente simplemente envía el mensaje una vez y el enrutador hará los mods de hash y los enviará a los dos actores remotos de la sala de chat. Usaría el algoritmo de Twitter snowflake para generar identificadores únicos de 64 bits para los mensajes que se envían. Vea el algoritmo en el método nextId() del código en el siguiente enlace. El datacenterId y workerId se pueden ajustar mediante las propiedades Norbert para asegurar que ninguno de chocar identificaciones que se genera en los diferentes servidores:

https://github.com/twitter/snowflake/blob/master/src/main/scala/com/twitter/service/snowflake/IdWorker.scala

Ahora dos copias de cada mensaje se destinará a cada extremo de cliente a través de cada uno de los dos activos actores de sala de chat. En cada actor del cliente de Websocket me gustaría quitar la máscara de bits de las identificaciones de copo de nieve para aprender el número del centro de datos Id + trabajador enviando el mensaje y hacer un seguimiento del número de mensajes de chat más alto visto desde cada host en el clúster. Entonces ignoraría cualquier mensaje que no sea más alto que lo que ya se ha visto en el cliente dado para un host remitente dado. Esto desduplicaría el par de mensajes que llegan a través de los dos actores activos de la sala de chat.

Hasta ahora todo bien; Tendríamos mensajes flexibles en el sentido de que si un nodo muere, no perderemos la única copia superviviente de las salas de chat. Los mensajes fluirán ininterrumpidamente a través de la segunda sala de chat automáticamente.

A continuación tenemos que ocuparnos de los nodos que abandonan el clúster o que se vuelven a agregar al clúster. Obtendremos una devolución de llamada de Norbert dentro de cada nodo para notificarnos sobre los cambios en la membresía del clúster. En esta devolución de llamada podemos enviar un mensaje akka a través del enrutador personalizado que indica la nueva lista de miembros y el nombre de host actual. El enrutador personalizado en el host actual verá ese mensaje y actualizará su estado para conocer la nueva membresía del clúster y calcular el nuevo par de nodos para enviar cualquier tráfico de chat determinado.Este reconocimiento de la nueva membresía del clúster será enviado por el enrutador a todos los nodos para que cada servidor pueda realizar un seguimiento cuando todos los servidores hayan alcanzado el cambio de membresía y ahora estén enviando mensajes correctamente.

La sala de chat superviviente puede seguir activa después del cambio de membresía. En este caso, todos los enrutadores en todos los nodos seguirán enviándolo como normal, pero también enviarán un mensaje especulativo al nuevo segundo host de sala de chat. Es posible que la segunda sala de chat aún no se haya levantado, pero eso no es un problema ya que los mensajes fluirán a través del sobreviviente. Si la sala de chat superviviente ya no está activa después de que la membresía cambie, todos los enrutadores en todos los hosts al principio enviarán a tres hosts; el sobreviviente y los dos nuevos nodos. El mecanismo de vigilancia de akka death se puede usar para que todos los nodos puedan ver eventualmente el apagado de la sala de chat superviviente para volver al tráfico del chat de enrutamiento a través de dos hosts.

A continuación tenemos que migrar la sala de chat del servidor superviviente a uno o dos nuevos hosts, según las circunstancias. El actor de la sala de chat en algún momento recibirá un mensaje contándole sobre la nueva membresía del grupo. Comenzará enviando una copia de la membresía de la sala de chat a los nuevos nodos. Este mensaje creará la nueva copia del actor de sala de chat con la membresía correcta en los nuevos nodos. Si el sobreviviente ya no es uno de los dos nodos que deberían albergar la sala de chat, pasará al modo de desmantelamiento. En el modo de desmantelamiento, solo reenviará los mensajes a los nuevos nodos primarios y secundarios, no a los miembros de la sala de chat. El reenvío de mensajes Akka es perfecto para esto.

Una sala de desactivación escuchará los mensajes de acuse de recibo de membresía de clúster de norbert de cada nodo. Eventualmente verá que todos los nodos dentro del clúster han reconocido la nueva membresía del clúster. Luego sabe que ya no recibirá más mensajes para reenviar. Entonces puede matarse a sí mismo. El hotswapping de Akka es perfecto para implementar el comportamiento de desmantelamiento.

Hasta ahora todo bien; tenemos una configuración de mensajería flexible que no perderá mensajes para un bloqueo de nodo. En el punto en que cambia la membresía del clúster, obtendremos un aumento del tráfico intranodo para copiar las salas de chat a nuevos nodos. También tenemos una ráfaga residual de reenvío intranodo de mensajes a los nodos hasta que todos los servidores se hayan puesto al corriente con qué salas de chat han movido dos servidores. Si queremos ampliar el sistema, podemos esperar hasta un punto bajo en el tráfico del usuario y simplemente activar un nuevo nodo. Las salas de chat se redistribuirían a través de los nuevos nodos automáticamente.

La descripción anterior se basa en la lectura de la siguiente papel y traducirla en conceptos akka:

https://www.dropbox.com/s/iihpq9bjcfver07/VLDB-Paper.pdf

+0

Si se agrega un nuevo nodo al clúster, la mayoría de los actores se reequilibrarían a nuevos nodos (¿no?). ¿Sabes aproximadamente aproximadamente cuánto tardaría el clúster en reequilibrar? (Si solo se trata de 1 centro de datos y, por ejemplo, 10 nodos) (la razón por la que los actores se moverían sería: * "hash el ID de sala de chat y mod por el tamaño de lista * [miembros del clúster] * para obtener el índice dentro la lista que es el nodo que debe alojar cualquier sala de chat determinada "*) – KajMagnus

Cuestiones relacionadas