instalar configurar como cluster balancing balanceador java tomcat web-applications architecture cluster-computing

java - configurar - Problema arquitectónico con el entorno de clúster Tomcat



tomcat cluster (10)

Estoy trabajando en un proyecto en el que tenemos un mecanismo de autenticación. Estamos siguiendo los pasos a continuación en el mecanismo de autenticación.

  1. El usuario abre un navegador e ingresa su correo electrónico en un cuadro de texto y hace clic en el botón de inicio de sesión.
  2. La solicitud va a un servidor. Generamos una cadena aleatoria (por ejemplo, 123456) y enviamos una notificación al Android / iPhone del usuario y hacemos que el actual hilo espere con la ayuda del método wait() .
  3. El usuario ingresa una contraseña en su teléfono y hace clic en el botón Enviar en su teléfono.
  4. Una vez que el usuario hace clic en el botón Enviar, estamos haciendo que un servicio web llegue al servidor y pase la cadena generada previamente (por ejemplo, 123456) y la contraseña.
  5. Si la contraseña es correcta contra el correo electrónico previamente ingresado, llamamos al método notify() al hilo que estaba esperando y enviamos éxito como respuesta y el usuario ingresa a nuestro sistema.
  6. Si la contraseña es incorrecta contra el correo electrónico previamente ingresado, llamamos al método notify() al hilo que estaba esperando y el envío falló como respuesta y mostramos un mensaje de credencial no válido al usuario.

Todo está funcionando bien, pero recientemente nos mudamos a un entorno agrupado. Encontramos que algunos subprocesos no se notifican incluso después de que el usuario los haya respondido y durante un tiempo de espera ilimitado.

Para el servidor, estamos utilizando Tomcat 5.5, y estamos siguiendo el Servlet / JSP Container de Apache Tomcat 5.5 para hacer que el entorno de clúster tomcat.

Respuesta :: Posible problema y solución

El posible problema son las múltiples JVM en un entorno agrupado. Ahora también estamos enviando la URL de Tomcat agrupada a la aplicación de Android del usuario junto con la cadena generada.

Y cuando el usuario hace clic en el botón de respuesta, estamos enviando la cadena generada junto con la URL de Tomcat agrupada, por lo que en este caso ambas solicitudes van a la misma JVM, y funciona bien.

Pero me pregunto si hay una sola solución para el problema anterior.

Hay un problema en esta solución. ¿Qué sucede si el Tomcat agrupado se cuelga? El equilibrador de carga enviará una solicitud al segundo Tomcat agrupado y nuevamente surgirá el mismo problema.


Usar wait / notify puede ser complicado. Recuerde que cualquier hilo puede ser suspendido en cualquier momento. Por lo tanto, es posible llamar a notify antes de esperar, en cuyo caso la espera se bloqueará para siempre.

No esperaría esto en tu caso, ya que tienes la interacción del usuario involucrada. Pero para el tipo de sincronización que estás haciendo, intenta usar un semáforo. Crea un semáforo con 0 (cero) cantidad. El hilo de espera llama a acquire () y se bloqueará hasta que otro hilo llame a release ().

Usar Semaphore de esta manera es mucho más robusto que esperar / notificar para la tarea que describió.


Considere el uso de una cuadrícula en memoria para que las instancias en el clúster puedan compartir el estado. Usamos Hazelcast para compartir datos entre instancias, por lo que en caso de que una respuesta llegue a una instancia diferente, aún puede manejarla.

Por ejemplo, puede usar el bloqueo de cuenta regresiva distribuido con valor de 1 para configurar el hilo en espera después de enviar el mensaje, y cuando la respuesta llega desde el cliente a una instancia separada puede disminuir, esa instancia puede disminuir el bloqueo a 0 permitiendo ejecutar el primer hilo.


Su despliegue en clúster significa que cualquier nodo en el clúster podría recibir cualquier respuesta.

El uso de wait / notify mediante el uso de subprocesos para una aplicación web conlleva el riesgo de acumular una gran cantidad de subprocesos que pueden no ser notificados, lo que podría perder memoria o crear una gran cantidad de subprocesos bloqueados. Esto eventualmente podría afectar la confiabilidad de su servidor.

Una solución más robusta sería enviar la solicitud a la aplicación de Android y almacenar el estado actual de la solicitud de los usuarios para su posterior procesamiento y completar la solicitud HTTP. Para almacenar el estado que podría considerar:

  • Una base de datos a la que todos los nodos de tomcat se conectan
  • Una solución de caché de java que funcionará en todos los nodos de tomcat como hazelcast

Este estado sería visible para todos los nodos en su clúster de tomcat.

Cuando la respuesta de la aplicación Android llega a un nodo diferente, restaure el estado de lo que estaba haciendo su secuencia y continúe el procesamiento en ese nodo.

Si la interfaz de usuario de la aplicación está esperando una respuesta del servidor, puede considerar el uso de una solicitud ajax para sondear el estado de respuesta del servidor. El nodo que procesa la respuesta de la aplicación Android no necesita ser el mismo que maneja las solicitudes de UI.


Usar Thread.wait en un entorno de servicio web es un error colosal. En su lugar, mantenga una base de datos de pares de usuario / token y expínelos a intervalos.

Si desea un clúster, utilice una base de datos que sea clúster. Recomendaría algo como memcached ya que está en la memoria (y rápido) y bajo en la sobrecarga (los pares clave / valor son muy simples, por lo que no necesitas RDBMS, etc.). memcached maneja la expiración de tokens para ti, por lo que parece un ajuste perfecto.

Creo que la estrategia de nombre de usuario -> token -> contraseña es innecesaria, especialmente porque tiene dos componentes diferentes que comparten la misma responsabilidad de autenticación de 2 factores. Creo que puede reducir aún más su complejidad, reducir la confusión de sus usuarios y ahorrarse algo de dinero en las tarifas de envío de SMS.

La interacción con su servicio web es simple:

  1. El usuario inicia sesión en su sitio web usando nombre de usuario + contraseña
  2. Si la autenticación primaria (nombre de usuario / contraseña) es exitosa, genere un token e inserte el userid = token en memcached
  3. Enviar el token al teléfono del usuario
  4. Presentar la página "ingresar token" para el usuario
  5. El usuario recibe el token por teléfono y lo ingresa en el formulario
  6. Obtenga el valor del token de memcached en función de la identificación del usuario. Si coincide, expire el token en memcached y considere el segundo factor exitoso
  7. Los tokens caducarán automáticamente después de la cantidad de tiempo que quieras establecer en memcached

No hay problemas de subprocesamiento con la solución anterior y escalará tantas JVM como necesite para respaldar su propio software.


Después de analizar su pregunta, llegué a la conclusión de que el problema exacto es el de varias JVM en un entorno agrupado.


El problema exacto es debido al entorno de clúster. Ambas solicitudes no van a la misma JVM. Pero sabemos que una notificación normal / simple funciona en la misma JVM cuando el hilo anterior está esperando.

Debería intentar ejecutar ambas solicitudes (primera solicitud, segunda solicitud cuando el usuario responda desde una aplicación de Android).


Solución:

Crea un único punto de contacto para todos los hilos en espera. Por lo tanto, en un entorno agrupado, todos los subprocesos esperarán en una tercera JVM (único punto de contacto), de modo que todas las solicitudes (cualquier Tomcat agrupado) se pondrán en contacto con la misma JVM para esperar y notificar la lógica y, por lo tanto, por un tiempo ilimitado Si hay una respuesta, el hilo será notificado si el mismo objeto ha esperado y se le notifica por segunda vez.


Supongo que el problema es que su primer hilo envía una notificación a la aplicación de Android del usuario en JVM 1 y cuando el usuario responde de nuevo, el control pasa a JVM 2. Y ese es el problema principal.

De alguna manera, ambos subprocesos pueden acceder a la misma JVM para aplicar lógica de espera y notificación.


La razón subyacente de sus problemas es que Java EE fue diseñado para funcionar de una manera diferente: intentar bloquear / esperar en un hilo de servicio es uno de los no-no más importantes. Daré la razón de esto primero, y cómo resolver el problema después de eso.

Java EE (tanto el nivel web como el nivel EJB) está diseñado para poder escalar a un tamaño muy grande (cientos de computadoras en un clúster). Sin embargo, para hacer eso, los diseñadores tuvieron que hacer las siguientes suposiciones, que son limitaciones específicas sobre cómo codificar:

  • Las transacciones son:

    1. De corta duración (por ejemplo, no bloquear o esperar por períodos superiores a un segundo más o menos)
    2. Independiente el uno del otro (por ejemplo, no hay comunicación entre hilos)
    3. Para EJB, administrado por el contenedor
  • Todo el estado del usuario se mantiene en contenedores de almacenamiento de datos específicos, que incluyen:

    1. Un almacén de datos al que se accede, p. Ej., JDBC. Puede usar una base de datos SQL tradicional o un backend NoSQL
    2. Granos de sesión con estado, si usa EJB. Piense en estos como Java Bean que persisten sus campos en una base de datos. Los beans de sesión con estado son administrados por el contenedor
    3. Sesión web Este es un almacén de clave-valor (algo así como una base de datos NoSQL pero sin la escala o capacidades de búsqueda) que persiste datos para un usuario específico durante su sesión. Es administrado por el contenedor Java EE y tiene las siguientes propiedades:

      1. Se reubicará automáticamente si el nodo falla en un clúster
      2. Los usuarios pueden tener más de una sesión web actual (es decir, en dos navegadores diferentes)
      3. Las sesiones web finalizan cuando el usuario finaliza su sesión al cerrar la sesión, o cuando la sesión está inactiva durante más tiempo que el tiempo de espera configurable.
      4. Todos los valores que se almacenan deben ser serializables para que persistan o se transfieran entre nodos en un clúster.

Si seguimos esas reglas, el contenedor Java EE puede administrar con éxito un clúster, lo que incluye cerrar nodos, iniciar nuevos y migrar sesiones de usuario, sin ningún código de desarrollador específico . Los desarrolladores escriben la interfaz gráfica y la lógica de negocios: toda la ''fontanería'' se gestiona mediante características configurables del contenedor.

Además, en el tiempo de ejecución, el contenedor Java EE puede ser monitoreado y administrado por algún software bastante sofisticado que puede rastrear el desempeño de la aplicación y los problemas de comportamiento en un sistema en vivo.

<snark> Bueno, esa era la teoría. La práctica sugiere que hay limitaciones bastante importantes que se perdieron, que conducen a AOSP y técnicas de inyección de código, pero esa es otra historia </ snark>

[Hay muchas discusiones en torno a la ''red sobre esto. Uno que se centra en los EJB está aquí: ¿Por qué se desalientan los hilos de desove en el contenedor Java EE? Exactamente lo mismo es cierto para contenedores web como Tomcat]

Perdón por el ensayo, pero esto es importante para su problema. Debido a las limitaciones en los hilos, no debe bloquear en la solicitud web esperando otra solicitud posterior.

¿Otro problema con el diseño actual es qué debería suceder si el usuario se desconecta de la red, se queda sin energía o simplemente decide abandonar? Presumiblemente, se desconectará, pero ¿cuánto tiempo? Demasiado pronto para algunos clientes, tal vez, lo que causará problemas de satisfacción. Si el tiempo de espera es demasiado largo, puede terminar bloqueando todos los hilos de trabajo en Tomcat y el servidor se congelará. Esto abre su organización para un ataque de denegación de servicio.

EDITAR: sugerencias mejoradas después de que se publicó una descripción más detallada del algoritmo.

A pesar de la discusión anterior sobre la mala práctica de bloquear un hilo de trabajo web y también la posible denegación de servicio, está claro que al usuario se le presenta una pequeña ventana de tiempo para reaccionar a la notificación en el teléfono Android, y esto puede mantenerse razonablemente pequeño para mejorar la seguridad. Esta ventana de tiempo también se puede mantener por debajo del tiempo de espera de Tomcat para las respuestas también. Entonces el enfoque de bloqueo de hilos podría ser usado.

Hay dos formas de resolver este problema:

  1. Cambia el enfoque de la solución al extremo del cliente : sondear el servidor usando Javascript en el navegador
  2. La comunicación entre los nodos en el clúster permite que el nodo que recibe la respuesta de autorización de la aplicación de Android desbloquee el nodo que bloquea la respuesta del servlet.

Para el enfoque 1, el navegador sondea el servidor a través de Javascript con una llamada AJAX a un servicio web en Tomcat; la llamada AJAX devuelve True si la aplicación de Android se autenticó. Ventaja: del lado del cliente, implementación mínima en el servidor, sin bloqueo de subprocesos en el servidor. Desventajas: durante el período de espera, debe realizar llamadas frecuentes (tal vez una por segundo; el usuario no notará esta latencia), lo que equivale a una gran cantidad de llamadas y cierta carga adicional en el servidor.

Para el enfoque 2, hay otra opción:

  1. Bloquee el hilo con Object.wait() almacenando opcionalmente el ID del nodo, IP u otro identificador en un almacén de datos compartido: Si es así, el nodo que recibe la autorización de la aplicación Android debe:

    1. Busque el nodo que actualmente está bloqueando o transmitiendo a todos los nodos del clúster
    2. Para cada nodo en 1. anterior, envíe un mensaje que identifique la sesión del usuario para desbloquear. El mensaje podría enviarse a través de:

      1. Tener un servlet interno solo en cada nodo: el servlet lo llama cuando realiza la autorización de la aplicación Android. El servlet interno llamará a Object.notify en el hilo correcto
      2. Use una cola de mensajes JMS pub-sub para transmitir a todos los miembros del clúster. Cada nodo es un suscriptor que, al recibir una notificación, llamará a Object.notify() en el hilo correcto.
  2. Encuestar un almacén de datos hasta que el hilo esté autorizado para continuar: en este caso, lo único que debe hacer la aplicación de Android es guardar el estado en un DB SQL.


Me temo, pero los hilos no pueden migrar sobre los clústeres clásicos de Java EE .

Debe replantear su arquitectura para implementar la espera / notificación de manera diferente (sin conexión).

O bien, puedes intentarlo con terracotta.org . Parece que esto permite agrupar todo un proceso de JVM en varias máquinas. Tal vez es tu única solución.

Lea una introducción rápida en Introducción a OpenTerracotta .