¿Mejores prácticas para implementar aplicaciones web Java con un tiempo de inactividad mínimo?

StackOverflow https://stackoverflow.com/questions/1640333

Pregunta

Al implementar una aplicación web Java grande (> 100 MB .war), actualmente utilizo el siguiente proceso de implementación:

  • El archivo .war de la aplicación se expande localmente en la máquina de desarrollo.
  • La aplicación ampliada se rsync: ed desde la máquina de desarrollo al entorno en vivo.
  • El servidor de aplicaciones en el entorno en vivo se reinicia después de rsync. Este paso no es estrictamente necesario, pero he descubierto que reiniciar el servidor de aplicaciones en la implementación evita "java.lang.OutOfMemoryError: PermGen space". debido a la carga frecuente de clases.

Cosas buenas sobre este enfoque:

  • El rsync minimiza la cantidad de datos enviados desde la máquina de desarrollo al entorno en vivo. Cargar todo el archivo .war lleva más de diez minutos, mientras que un rsync tarda un par de segundos.

Cosas malas sobre este enfoque:

  • Mientras se ejecuta rsync, el contexto de la aplicación se reinicia desde que se actualizan los archivos. Idealmente, el reinicio debería ocurrir después de que se complete el rsync, no cuando todavía se está ejecutando.
  • El reinicio del servidor de aplicaciones provoca aproximadamente dos minutos de tiempo de inactividad.

Me gustaría encontrar un proceso de implementación con las siguientes propiedades:

  • Tiempo de inactividad mínimo durante el proceso de implementación.
  • Tiempo mínimo dedicado a cargar los datos.
  • Si el proceso de implementación es específico del servidor de aplicaciones, entonces el servidor de aplicaciones debe ser de código abierto.

Pregunta:

  • Dados los requisitos establecidos, ¿cuál es el proceso de implementación óptimo?
¿Fue útil?

Solución

Se ha observado que rsync no funciona bien cuando se envían cambios a un archivo WAR. La razón de esto es que los archivos WAR son esencialmente archivos ZIP y, de forma predeterminada, se crean con archivos miembros comprimidos. Pequeños cambios en los archivos miembro (antes de la compresión) dan como resultado diferencias a gran escala en el archivo ZIP, lo que hace que el algoritmo de transferencia delta de rsync sea ineficaz.

Una posible solución es usar jar -0 ... para crear el archivo WAR original. La opción -0 le dice al comando jar que no comprima los archivos miembros al crear el archivo WAR. Luego, cuando rsync compara las versiones antigua y nueva del archivo WAR, el algoritmo de transferencia delta debería poder crear pequeñas diferencias. Luego arregle que rsync envíe los diffs (o archivos originales) en forma comprimida; p.ej. use rsync -z ... o un flujo de datos comprimido / transporte debajo.

EDITAR: Dependiendo de cómo esté estructurado el archivo WAR, también puede ser necesario usar jar -0 ... para crear archivos JAR de componentes. Esto se aplicaría a los archivos JAR que con frecuencia están sujetos a cambios (o que simplemente se reconstruyen), en lugar de a archivos JAR de terceros estables.

En teoría, este procedimiento debería proporcionar una mejora significativa sobre el envío de archivos WAR regulares. En la práctica, no he intentado esto, así que no puedo prometer que funcionará.

La desventaja es que el archivo WAR desplegado será significativamente más grande. Esto puede resultar en tiempos de inicio de aplicaciones web más largos, aunque sospecho que el efecto sería marginal.


Un enfoque completamente diferente sería mirar su archivo WAR para ver si puede identificar los JAR de la biblioteca que probablemente (casi) nunca cambien. Saque estos JAR del archivo WAR e impleméntelos por separado en el directorio common / lib del servidor Tomcat; p.ej. usando rsync .

Otros consejos

Actualización:

Desde que esta respuesta se escribió por primera vez, ha surgido una mejor manera de implementar archivos de guerra en Tomcat con cero tiempo de inactividad. En versiones recientes de tomcat puedes incluir números de versión en tus nombres de archivos war. Entonces, por ejemplo, puede implementar los archivos ROOT ## 001.war y ROOT ## 002.war en el mismo contexto simultáneamente. Tomcat interpreta todo como ## como un número de versión y no forma parte de la ruta de contexto. Tomcat mantendrá todas las versiones de su aplicación ejecutándose y atenderá nuevas solicitudes y sesiones a la versión más reciente que esté completamente activa mientras completa con gracia las solicitudes y sesiones antiguas en la versión con la que comenzaron. La especificación de los números de versión también se puede hacer a través del administrador de tomcat e incluso las tareas de catalina ant. Más información aquí .

Respuesta original:

Rsync tiende a ser ineficaz en archivos comprimidos ya que su algoritmo de transferencia delta busca cambios en los archivos y un pequeño cambio en un archivo sin comprimir puede alterar drásticamente la versión comprimida resultante. Por esta razón, podría tener sentido sincronizar un archivo war sin comprimir en lugar de una versión comprimida, si el ancho de banda de la red demuestra ser un cuello de botella.

¿Qué tiene de malo utilizar la aplicación de administrador Tomcat para realizar sus implementaciones? Si no desea cargar el archivo de guerra completo directamente en la aplicación del administrador de Tomcat desde una ubicación remota, puede sincronizarlo (sin comprimir por las razones mencionadas anteriormente) a una ubicación de marcador de posición en la caja de producción, reempaquetarlo en una guerra y luego entrégalo al gerente localmente. Existe una buena tarea de hormigas que se incluye con Tomcat que le permite realizar implementaciones de script utilizando la aplicación de administrador de Tomcat.

Hay un defecto adicional en su enfoque que no ha mencionado: Si bien su aplicación se implementa parcialmente (durante una operación rsync), su aplicación podría estar en un estado inconsistente donde las interfaces cambiadas pueden estar fuera de sincronización, nuevo / las dependencias actualizadas pueden no estar disponibles, etc. Además, dependiendo de cuánto demore su trabajo rsync, su aplicación puede reiniciarse varias veces. ¿Sabe que puede y debe desactivar el comportamiento de escuchar archivos modificados y reiniciar en Tomcat? En realidad no se recomienda para sistemas de producción. Siempre puede hacer un reinicio manual o con secuencia de comandos de su aplicación utilizando la aplicación de administrador Tomcat.

Su aplicación no estará disponible para los usuarios durante un reinicio, por supuesto. Pero si le preocupa tanto la disponibilidad, seguramente tiene servidores web redundantes detrás de un equilibrador de carga. Al implementar un archivo war actualizado, puede hacer que el equilibrador de carga envíe temporalmente todas las solicitudes a otros servidores web hasta que finalice la implementación. Enjuague y repita para sus otros servidores web.

En cualquier entorno donde el tiempo de inactividad sea una consideración, seguramente está ejecutando algún tipo de clúster de servidores para aumentar la confiabilidad mediante redundancia. Sacaría un host del clúster, lo actualizaría y luego lo devolvería al clúster. Si tiene una actualización que no puede ejecutarse en un entorno mixto (por ejemplo, se requiere un cambio de esquema incompatible en la base de datos), tendrá que cerrar todo el sitio, al menos por un momento. El truco es abrir procesos de reemplazo antes de soltar los originales.

Usando tomcat como ejemplo: puede usar CATALINA_BASE para definir un directorio donde se encontrarán todos los directorios de trabajo de tomcat, separados del código ejecutable. Cada vez que implemento el software, lo implemento en un nuevo directorio base para poder tener un nuevo código residente en el disco junto al código anterior. Luego puedo iniciar otra instancia de tomcat que apunta al nuevo directorio base, hacer que todo comience a funcionar y luego cambiar el proceso anterior (número de puerto) con el nuevo en el equilibrador de carga.

Si me preocupa preservar los datos de la sesión a través del conmutador, puedo configurar mi sistema de modo que cada host tenga un socio al que replica los datos de la sesión. Puedo soltar uno de esos hosts, actualizarlo, volver a activarlo para que recoja los datos de la sesión y luego cambiar los dos hosts. Si tengo varios pares en el clúster, puedo soltar la mitad de todos los pares, luego hacer un cambio masivo, o puedo hacerlos un par a la vez, dependiendo de los requisitos de la versión, los requisitos de la empresa, etc. Personalmente, sin embargo, prefiero permitir que los usuarios finales sufran la pérdida ocasional de una sesión activa en lugar de tratar de actualizar con las sesiones intactas.

Todo es una compensación entre la infraestructura de TI, la complejidad del proceso de lanzamiento y el esfuerzo del desarrollador. Si su clúster es lo suficientemente grande y su deseo lo suficientemente fuerte, es bastante fácil diseñar un sistema que pueda intercambiarse sin tiempo de inactividad para la mayoría de las actualizaciones. Los grandes cambios de esquema a menudo fuerzan el tiempo de inactividad real, ya que el software actualizado generalmente no puede acomodar el esquema anterior, y probablemente no pueda copiar los datos en una nueva instancia de db, realizar la actualización del esquema y luego cambiar los servidores al nuevo db, ya que habrás perdido cualquier dato escrito en el viejo después de que el nuevo db fue clonado de él. Por supuesto, si tiene recursos, puede encargar a los desarrolladores que modifiquen la nueva aplicación para que usen nuevos nombres de tabla para todas las tablas que se actualizan, y pueden colocar activadores en la base de datos en vivo que actualizará correctamente las nuevas tablas con datos como está escrito en las tablas antiguas por la versión anterior (o tal vez use vistas para emular un esquema de otro). Abra sus nuevos servidores de aplicaciones y cámbielos al clúster. Hay un montón de juegos que puedes jugar para minimizar el tiempo de inactividad si tienes los recursos de desarrollo para construirlos.

Quizás el mecanismo más útil para reducir el tiempo de inactividad durante las actualizaciones de software es asegurarse de que su aplicación pueda funcionar en modo de solo lectura. Eso proporcionará algunas funcionalidades necesarias a sus usuarios, pero le permitirá realizar cambios en todo el sistema que requieren modificaciones en la base de datos y demás. Coloque su aplicación en modo de solo lectura, luego clone los datos, actualice el esquema, muestre nuevos servidores de aplicaciones contra nuevos db, luego cambie el equilibrador de carga para usar los nuevos servidores de aplicaciones. Su único tiempo de inactividad es el tiempo requerido para cambiar al modo de solo lectura y el tiempo requerido para modificar la configuración de su equilibrador de carga (la mayoría de los cuales puede manejarlo sin ningún tiempo de inactividad).

Mi consejo es usar rsync con versiones explosionadas pero implementar un archivo war.

  1. Cree una carpeta temporal en el entorno en vivo donde habrá explotado la versión de la aplicación web.
  2. Versiones explosionadas de Rsync.
  3. Después de rsync exitoso, cree un archivo war en una carpeta temporal en la máquina del entorno en vivo.
  4. Reemplace la antigua guerra en el directorio de implementación del servidor por una nueva de la carpeta temporal.

Se recomienda reemplazar la guerra antigua por una nueva en el contenedor JBoss (que se basa en Tomcat) porque es una operación atómica y rápida y es seguro que cuando el implementador inicie, toda la aplicación estará en estado desplegado.

¿No puede hacer una copia local de la aplicación web actual en el servidor web, sincronizar con ese directorio y luego quizás incluso usar enlaces simbólicos, de una vez, apunte Tomcat a una nueva implementación sin mucho tiempo de inactividad?

Su enfoque para rsync la guerra extraída es bastante bueno, también el reinicio ya que creo que un servidor de producción no debería tener habilitada la implementación en caliente. Entonces, el único inconveniente es el tiempo de inactividad cuando necesita reiniciar el servidor, ¿verdad?

Supongo que todo el estado de su aplicación se mantiene en la base de datos, por lo que no tiene problemas con algunos usuarios que trabajan en una instancia del servidor de aplicaciones mientras que otros usuarios están en otra instancia del servidor de aplicaciones. Si es así,

Ejecute dos servidores de aplicaciones : inicie el segundo servidor de aplicaciones (que escucha en otros puertos TCP) e implemente su aplicación allí. Después de la implementación, actualice la configuración de Apache httpd (mod_jk o mod_proxy) para que apunte al segundo servidor de aplicaciones. Reiniciando con gracia el proceso httpd de Apache. De esta forma, no tendrá tiempo de inactividad y los nuevos usuarios y solicitudes serán redirigidos automáticamente al nuevo servidor de aplicaciones.

Si puede utilizar el clúster del servidor de aplicaciones y la compatibilidad con la replicación de sesiones, será incluso más fácil para los usuarios que actualmente están conectados, ya que el segundo servidor de aplicaciones se sincronizará tan pronto como se inicie. Luego, cuando no haya accesos al primer servidor, apáguelo.

Esto depende de la arquitectura de su aplicación.

Una de mis aplicaciones se encuentra detrás de un proxy de equilibrio de carga, donde realizo una implementación escalonada, erradicando efectivamente el tiempo de inactividad.

Si los archivos estáticos son una gran parte de su gran WAR (100Mo es bastante grande), colocarlos fuera de WAR y desplegarlos en un servidor web (por ejemplo, Apache) frente a su servidor de aplicaciones podría acelerar las cosas. Además de eso, Apache generalmente hace un mejor trabajo al servir archivos estáticos que un motor de servlet (incluso si la mayoría de ellos hicieron un progreso significativo en esa área).

Entonces, en lugar de producir una WAR grande y gorda, póngala a dieta y produzca:

  • un ZIP grande y gordo con archivos estáticos para Apache
  • una GUERRA menos gorda para el motor servlet.

Opcionalmente, vaya más allá en el proceso de hacer que WAR sea más delgado: si es posible, implemente Grails y otros JAR que no cambien con frecuencia (que probablemente sea el caso de la mayoría de ellos) en el nivel del servidor de aplicaciones.

Si logras producir un WAR más ligero, no me molestaría en sincronizar directorios en lugar de archivos.

Fortalezas de este enfoque:

  1. Los archivos estáticos pueden estar calientes "desplegados" en Apache (por ejemplo, use un enlace simbólico que apunta al directorio actual, descomprima los nuevos archivos, actualice el enlace simbólico y listo & # 224;).
  2. La WAR será más delgada y llevará menos tiempo desplegarla.

Debilidad de este enfoque:

  1. Hay un servidor más (el servidor web) así que esto agrega (un poco) más complejidad.
  2. Tendrá que cambiar los scripts de compilación (no es un gran problema IMO).
  3. Tendrá que cambiar la lógica de rsync.

No estoy seguro de si esto responde a su pregunta, pero compartiré el proceso de implementación que uso o encuentro en los pocos proyectos que hice.

De manera similar a ti, no recuerdo haber hecho una redistribución o actualización de guerra completa. La mayoría de las veces, mis actualizaciones están restringidas a unos pocos archivos jsp, tal vez una biblioteca, algunos archivos de clase. Puedo administrar y determinar cuáles son los artefactos afectados y, por lo general, empaquetamos esas actualizaciones en un archivo zip, junto con un script de actualización. Ejecutaré el script de actualización. El script hace lo siguiente:

  • Haga una copia de seguridad de los archivos que se sobrescribirán, tal vez en una carpeta con la fecha y hora de hoy.
  • Desempaquetar mis archivos
  • Detener el servidor de aplicaciones
  • Mover los archivos
  • Inicie el servidor de aplicaciones

Si el tiempo de inactividad es una preocupación, y generalmente lo son, mis proyectos suelen ser HA, incluso si no comparten el estado sino que usan un enrutador que proporciona un enrutamiento de sesión fijo.

Otra cosa que tengo curiosidad sería, ¿por qué la necesidad de rsync? Debe poder saber cuáles son los cambios necesarios, determinándolos en su entorno de preparación / desarrollo, no realizando comprobaciones delta con live. En la mayoría de los casos, tendría que ajustar su rsync para ignorar los archivos de todos modos, como ciertos archivos de propiedades que definen los recursos que utiliza un servidor de producción, como conexión de base de datos, servidor smtp, etc.

Espero que esto sea útil.

¿En qué está configurado PermSpace? Esperaría ver que esto también crezca, pero debería bajar después de la recopilación de las clases anteriores. (¿o el ClassLoader todavía se sienta?)

Pensando en voz alta, puede rsync a un directorio separado con nombre de versión o fecha. Si el contenedor admite enlaces simbólicos, ¿podría SIGSTOP el proceso raíz, cambiar la raíz del sistema de archivos del contexto a través de un enlace simbólico y luego SIGCONT?

En cuanto al contexto temprano se reinicia. Todos los contenedores tienen opciones de configuración para deshabilitar la nueva implementación automática en el archivo de clase o cambios de recursos estáticos. Probablemente no pueda deshabilitar las reasignaciones automáticas en los cambios de web.xml, por lo que este archivo es el último en actualizarse. Por lo tanto, si deshabilita para volver a implementar automáticamente y actualizar el web.xml como el último, verá que el contexto se reinicia después de toda la actualización.

Cargamos la nueva versión de la aplicación web en un directorio separado, luego nos movemos para cambiarla por la que está en ejecución o usamos enlaces simbólicos. Por ejemplo, tenemos un enlace simbólico en el directorio de tomba webapps llamado "myapp", que apunta a la aplicación web actual llamada "myapp-1.23". Subimos la nueva aplicación web a "myapp-1.24". Cuando todo esté listo, detenga el servidor, elimine el enlace simbólico y cree uno nuevo que apunte a la nueva versión, luego inicie el servidor nuevamente.

Desactivamos la recarga automática en los servidores de producción para el rendimiento, pero aun así, el hecho de que los archivos dentro de la aplicación web cambien de manera no atómica puede causar problemas, ya que los archivos estáticos o incluso las páginas JSP podrían cambiar de manera que causen enlaces rotos o peor.

En la práctica, las aplicaciones web están ubicadas en un dispositivo de almacenamiento compartido, por lo que los servidores agrupados, con equilibrio de carga y de conmutación por error tienen el mismo código disponible.

El principal inconveniente de su situación es que la carga llevará más tiempo, ya que su método permite que rsync solo transfiera archivos modificados o agregados. Puede copiar la carpeta de la aplicación web anterior a la nueva primero, y rsync a eso, si hace una diferencia significativa, y si realmente es un problema.

Tomcat 7 tiene una característica agradable llamada " implementación paralela " que está diseñado para este caso de uso.

La esencia es que expandes el .war en un directorio, ya sea directamente bajo webapps / o enlace simbólico. Las versiones sucesivas de la aplicación se encuentran en directorios llamados app ## version , por ejemplo myapp ## 001 y myapp ## 002 . Tomcat manejará las sesiones existentes que van a la versión anterior y las nuevas sesiones que van a la nueva versión.

El problema es que debes tener mucho cuidado con las filtraciones de PermGen. Esto es especialmente cierto con Grails que usa mucho PermGen. VisualVM es tu amigo.

Simplemente use 2 o más servidores tomcat con un proxy sobre él. Ese proxy puede ser de apache / nignix / haproxy.

Ahora en cada uno de los servidores proxy hay " en " y "fuera" url con puertos configurados.

Primero copia tu guerra en el gato sin detener el servicio. Una vez que se despliega la guerra, el motor Tomcat la abre automáticamente.

Nota verificación cruzada unpackWARs = " verdadero " y autoDeploy = " verdadero " en el nodo " Host " dentro de server.xml

Parece que le gusta esto

  <Host name="localhost"  appBase="webapps"
        unpackWARs="true" autoDeploy="true"
        xmlValidation="false" xmlNamespaceAware="false">

Ahora vea los registros de tomcat. Si no hay ningún error, significa que está funcionando correctamente.

Ahora acceda a todas las API para probar

Ahora ve a tu servidor proxy.

Simplemente cambia el mapeo de URL de fondo con el nombre de la nueva guerra. Dado que registrarse en los servidores proxy como apache / nignix / haProxy tomó mucho menos tiempo, sentirá un tiempo de inactividad mínimo

Referir - https://developers.google.com/speed/pagespeed/module / dominios para mapear URL

Estás utilizando Resin, Resin ha incorporado soporte para el control de versiones de aplicaciones web.

http://www.caucho.com/resin-4.0/ admin / deploy.xtp # VersioningandGracefulUpgrades

Actualización: su proceso de vigilancia también puede ayudar con los problemas de permgenspace.

No es una "mejor práctica" pero algo que acabo de pensar.

¿Qué hay de implementar la aplicación web a través de un DVCS como git?

De esta manera puedes dejar que git descubra qué archivos transferir al servidor. También tiene una buena manera de retroceder si resulta que está roto, ¡solo haga una reversión!

Escribí un script de bash que toma algunos parámetros y sincroniza el archivo entre servidores. Acelera mucho la transferencia de rsync para archivos más grandes:

https://gist.github.com/3985742

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top