Pregunta

Asimismo, ¿hay patrones de diseño que deberían evitarse?

¿Fue útil?

Solución

Supongo que está escribiendo una aplicación de tipo servidor (dejemos las aplicaciones web por un tiempo; hay algunas buenas soluciones disponibles que pueden ayudar allí, así que echemos un vistazo a la sección "Tengo este gran nuevo tipo de el servidor tengo escrito " ;, pero quiero que sea un problema de alta disponibilidad).

En una implementación de servidor, las solicitudes de los clientes generalmente se convierten (de una forma u otra) a algún patrón de tipo de evento o comando, y luego se ejecutan en una o más colas.

Entonces, primer problema: es necesario almacenar eventos / comandos de manera que sobrevivan en el clúster (es decir, cuando un nuevo nodo toma el control como maestro, busca el siguiente comando que debe ejecutarse y comienza).

Permite comenzar con una implementación de servidor de un solo subproceso (el más sencillo, y los conceptos aún se aplican a los subprocesos múltiples, pero tienen su propio conjunto de problemas0. Cuando se está procesando un comando, se necesita algún tipo de procesamiento de transacciones.

Otra preocupación es la gestión de los efectos secundarios y cómo maneja el fallo del comando actual? Donde sea posible, maneje los efectos secundarios de una manera transaccional, de modo que sean todos o nada. es decir. si el comando cambia las variables de estado, pero se bloquea a mitad de la ejecución, se puede volver a " anterior " estado es genial Esto permite que el nuevo nodo maestro reanude el comando bloqueado y simplemente vuelva a ejecutar el comando. Una buena manera nuevamente es dividir los efectos secundarios en tareas más pequeñas que pueden ejecutarse nuevamente en cualquier nodo. es decir. almacene las tareas de inicio y finalización de la solicitud principal, con muchas tareas pequeñas que manejan, por ejemplo, solo un efecto secundario por tarea.

Esto también presenta otros problemas que afectarán su diseño. Esas variables de estado no son necesariamente actualizaciones de bases de datos. Pueden ser estados compartidos (es decir, una máquina de estados finitos para un componente interno) que también deben distribuirse en el clúster. Por lo tanto, el patrón para administrar cambios de tal manera que el código maestro debe ver una versión consistente del estado que necesita y luego cometer ese estado en todo el clúster. Usar alguna forma de almacenamiento de datos inmutable (al menos desde el hilo maestro que realiza la actualización) es útil. es decir. todas las actualizaciones se realizan de manera efectiva en copias nuevas que deben pasar por algún tipo de mediador o fachada que solo actualiza las copias locales en memoria con las actualizaciones después de la actualización en el clúster (o la cantidad mínima de miembros en el clúster para mantener la coherencia de los datos). / p>

Algunos de estos problemas también están presentes para los sistemas de trabajo maestro.

También se necesita una buena gestión de errores, ya que la cantidad de cosas que pueden salir mal en la actualización de estado aumenta (ya que la red está involucrada).

Uso mucho el patrón de estado. En lugar de actualizaciones de una línea, para los efectos secundarios, desea enviar solicitudes / respuestas, y utilizar fsm específicos de conversación para rastrear el progreso.

Otro problema es la representación de los puntos finales. es decir. ¿El cliente conectado al nodo maestro debe poder volver a conectarse al nuevo maestro y luego escuchar los resultados? ¿O simplemente cancela todos los resultados pendientes y deja que los clientes vuelvan a enviar? Si permite que se procesen las solicitudes pendientes, se necesita una buena forma de identificar los puntos finales (clientes) (es decir, algún tipo de ID de cliente en una búsqueda).

También necesita un código de limpieza, etc. (es decir, no quiero que los datos que esperan a que un cliente se vuelva a conectar esperen para siempre).

Se utilizan muchas colas. Mucha gente, por lo tanto, usará un bus de mensajes (jms say for java) para impulsar eventos de manera transaccional.

Terracota (nuevamente para java) resuelve mucho de esto por ti, solo actualiza la memoria - terracota es tu fachada / mediador aquí. Acaban de inyectar los aspectos para tu.

Terracota (no trabajo para ellos): presenta el concepto de "súper estática", por lo que obtiene estos singletons de todo el clúster que son geniales, pero solo necesita saber cómo esto afectará el flujo de trabajo de desarrollo y prueba. - es decir use mucha composición, en lugar de la herencia de implementaciones concretas para una buena reutilización.

Para aplicaciones web: un buen servidor de aplicaciones puede ayudar con la replicación de variables de sesión y un buen equilibrador de carga funciona. De alguna manera, usar esto a través de un REST (o el método de elección del servicio web) es una forma fácil de escribir un servicio de múltiples subprocesos. Pero tendrá implicaciones de rendimiento. De nuevo depende de tu dominio de problema.

Los servicios de mensajes (por ejemplo, jms) se utilizan a menudo para introducir un acoplamiento suelto entre diferentes servicios. Con un servidor de mensajes decente, puede hacer una gran cantidad de mensajes de enrutamiento (de nuevo apache camel o similar hace un gran trabajo), es decir. diga un consumidor pegajoso contra un grupo de productores de jms, etc. que también puede permitir una buena conmutación por error. La cola de Jms, etc. puede proporcionar una forma sencilla de distribuir cmds en el clúster, independientemente del maestro / esclavo. (de nuevo, depende de si está haciendo un LOB o escribiendo un servidor / producto desde cero).

(si me da tiempo más tarde, pondré en orden, tal vez incluya más detalles en la ortografía de la ortografía, etc.)

Otros consejos

Un enfoque para crear software confiable es software de solo bloqueo :

  

El software de solo bloqueo es un software que se bloquea de forma segura y se recupera rápidamente. La única forma de detenerlo es bloquearlo, y la única forma de iniciarlo es recuperarse. Un sistema de solo bloqueo se compone de componentes de solo bloqueo que se comunican con solicitudes reintentables; las fallas se manejan fallando y reiniciando el componente defectuoso y reintentando cualquier solicitud que haya expirado. El sistema resultante suele ser más robusto y confiable porque la recuperación de fallos es un ciudadano de primera clase en el proceso de desarrollo, en lugar de una idea de último momento, y ya no necesita el código adicional (y las interfaces y errores asociados) para el cierre explícito. Todo el software debería poder bloquearse de forma segura y recuperarse rápidamente, pero el software de solo bloqueo debe tener estas cualidades, o su falta se vuelve rápidamente evidente.

Recomiendo leer ¡Lánzalo! por Michael Nygard. Describe una serie de anti-patrones que afectan a los sistemas de producción, y patrones para ayudar a evitar que un componente errante anule todo el sistema. El libro cubre tres áreas principales; Estabilidad, capacidad y diseño general (que abarca redes, seguridad, disponibilidad y administración).

Mi lugar de trabajo anterior fue mordido (en un momento u otro) por casi todos los escenarios de falla que Nygard describe (con pérdida de ingresos por cada corte resultante). La implementación de algunas de las técnicas y patrones que sugiere dio como resultado sistemas significativamente más estables y predecibles (y sí, el libro está un poco centrado en Java, pero los principios son aplicables en muchos contextos).

Incorrecto:

  

... y habrá un servidor de almacenamiento

Bien:

  

... y habrá una comunidad de almacenamiento (múltiple)   servidores con (múltiples) balanceadores de carga en el frente   de ellos

  • Ponga balanceadores de carga delante de todo. Por ahora, puede tener 4 backends, pero en el futuro puede tener 400 de ellos, por lo que es aconsejable administrarlo solo en el LB, no en todas las aplicaciones que usan el backend.

  • Usa múltiples niveles de caché.

  • Busque soluciones populares para acelerar las gestiones (por ejemplo, adjunto).

  • Si va a renovar un sistema, hágalo parte por parte, en varios pequeños pasos. Si lo haces en un gran paso (desactiva el anterior, activa el nuevo y reza para que funcione) lo más probable es que falle.

  • Usa nombres DNS para cosas, por ejemplo, storage-lb.servicename se resuelve en las direcciones de todos los equilibradores de carga de almacenamiento. Si desea agregar uno, solo modifique el dns, todos los servicios comenzarán a usarlo automáticamente.

  • Mantenlo simple. Cuantos más sistemas dependa, más sufrirá Su servicio.

El diseño de sistemas de alta disponibilidad (HA) es un área activa de investigación y desarrollo. Si observa ACM o IEEE, hay un montón de trabajos de investigación sobre las cualidades del servicio (disponibilidad, confiabilidad, escalabilidad, etc.) y cómo lograrlos (acoplamiento suelto, adaptación, etc.). Si está buscando más aplicaciones prácticas, eche un vistazo a los sistemas tolerantes a fallas y al middleware que está diseñado para permitir la agrupación, la red o la funcionalidad similar a la nube.

La replicación y el balanceo de carga (a.k.a. proxy inverso) son algunos de los patrones más comunes para lograr sistemas de alta disponibilidad, y a menudo se pueden hacer sin realizar cambios de código en el software subyacente, asumiendo que no está muy estrechamente acoplado. Incluso muchas de las ofertas recientes en la nube se logran esencialmente a través de la replicación y el balanceo de carga, aunque tienden a generar elasticidad para manejar amplios rangos de demanda del sistema.

Hacer que los componentes de software sean sin estado facilita la carga de la replicación, ya que el estado en sí no necesita ser replicado junto con los componentes del software. La apatridia es una de las razones principales por las que HTTP se adapta tan bien, pero a menudo requiere que las aplicaciones se agreguen a su propio estado (por ejemplo, sesiones), que luego debe ser replicada.

Por lo tanto, es más fácil hacer que los sistemas acoplados sueltos sean altamente disponibles que los sistemas estrechamente acoplados. Dado que la confiabilidad de los componentes del sistema determina la confiabilidad general del sistema, los componentes que no son confiables pueden necesitar ser reemplazados (fallas de hardware, errores de software, etc.). Permitir la adaptación dinámica en tiempo de ejecución permite que estos componentes defectuosos se reemplacen sin afectar la disponibilidad del sistema en general. El acoplamiento suelto es otra razón para el uso de sistemas de mensajería confiables en los que el remitente y el receptor no tienen que estar disponibles al mismo tiempo, pero el sistema sigue estando disponible.

Según tengo entendido, está buscando patrones específicos para usar en aplicaciones Java que forman parte de una arquitectura de alta disponibilidad. Por supuesto, hay una gran cantidad de patrones y mejores prácticas que se pueden usar, pero estos no son realmente "patrones de HA". Más bien, son buenas ideas que se pueden utilizar en contextos de manys.

Supongo que lo que trato de decir es esto: una arquitectura de alta disponibilidad se compone de numerosas piezas pequeñas. Si seleccionamos una de estas partes pequeñas y las examinamos, probablemente encontraremos que no hay atributos de HA mágicos para este componente pequeño. Si examinamos todos los otros componentes, encontraremos lo mismo. Es cuando se combinan de manera inteligente para convertirse en una aplicación de alta disponibilidad.

Una aplicación HA es una aplicación en la que planeas lo peor desde el principio. Si alguna vez piensa en términos de " Este componente es tan estable que no necesitamos redundancia adicional para él " Probablemente no sea una arquitectura HA. Después de todo, es fácil manejar los escenarios de problemas que prevén. Es el que te sorprende que derriba el sistema.

A pesar de todo esto, hay patrones que son especialmente útiles en contextos de alta disponibilidad. Muchos de ellos están documentados en el libro clásico "Patrones de arquitectura de aplicaciones empresariales", de Martin Fowler.

Estoy interpretando " Alta disponibilidad " como " Zero Downtime " `, que se puede implementar según otra pregunta de SE:

Implementación de tiempo de inactividad cero para aplicaciones Java

  1. Interruptor A / B: (actualización progresiva + mecanismo de recuperación)
  2. Implementación paralela & # 8211; Apache Tomcat: (solo para aplicaciones web)
  3. Enlace de puerto retrasado
  4. Enlace de puerto avanzado

Usaré algunos de esos conceptos para crear patrones de diseño para el sistema de Alta Disponibilidad desde la perspectiva del software, que complementa los enfoques anteriores.

Patrones a utilizar:

Proxy / Factory :

Tenga un objeto proxy y el proxy decidirá dónde redirigir las solicitudes. Supongamos que tiene la versión 1 y amp; Versión 2 del software. Si los clientes se conectan con un protocolo antiguo, rediríjalos al software de la Versión 1. Los nuevos clientes pueden conectarse a la versión 2 directamente. El proxy puede tener el método de Fábrica o AbstractFactory para representar la nueva versión del software.

Estrategia

Puede cambiar el algoritmo en tiempo de ejecución seleccionando un algoritmo de una familia de algoritmos. Si toma el ejemplo de las líneas aéreas, puede cambiar entre los algoritmos DiscountFare y NormalFare durante los meses de tráfico no pico y pico.

Decorador :

Puede cambiar el comportamiento del objeto en tiempo de ejecución. Agrega una nueva clase y decora la responsabilidad adicional.

Adaptador :

Es útil cuando cambia la interfaz o el contrato entre la versión 1 y la versión 2. El adaptador responderá tanto al antiguo como al amp; nuevas solicitudes de clientes apropiadamente.

Directrices generales:

  1. acoplamiento suelto entre objetos
  2. Sigue los S.O.L.I.D principios en tu aplicación

Consulte los sourcemaking para obtener información sobre los patrones anteriores para una mejor comprensión.

Qué no utilizar:

Además de los patrones de diseño, debe tomar algunas precauciones para lograr un tiempo de inactividad cero para su aplicación.

  1. No introduzca puntos únicos de fallas en su sistema.
  2. Use cachés distribuidos (por ejemplo, terracota) / bloqueos con moderación.
  3. Eliminar el acoplamiento duro entre servicios. Elimine el acoplamiento estrecho entre los servicios mediante el uso de bus / frameworks de mensajería (JMS, ActiveMQ, etc.)

La alta disponibilidad es más sobre la disponibilidad de hardware y la redundancia que sobre las convenciones de codificación. Hay un par de patrones que usaría en casi todos los casos de HA: elegiría el patrón de singleton para mi objeto de base de datos y utilizaría el patrón de fábrica para crear el singleton. La fábrica puede tener la lógica para manejar los problemas de disponibilidad con la base de datos (que es donde ocurren la mayoría de los problemas de disponibilidad). Por ejemplo, si el maestro está inactivo, entonces conecte un segundo maestro para ambas lecturas y escrituras hasta que el maestro esté de regreso. No sé si estos son los patrones más apalancados, pero son los más apalancados en mi código.

Por supuesto, esta lógica podría manejarse con un método __constructivo, pero un patrón de fábrica le permitirá controlar mejor su código y la lógica de toma de decisiones sobre cómo manejar los problemas de conectividad de la base de datos. Una fábrica también te permitirá manejar mejor el patrón de singleton.

Absolutamente evitaría el patrón decorador y el patrón observador . Ambos crean complejidad en su código que hace que sea difícil de mantener. Son casos en los que son la mejor opción para sus necesidades, pero la mayoría de las veces no lo son.

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