| Alta Disponibilidad con Terracotta |
|
|
|
| Escrito por Leonardo De Seta | ||||||||||||||||||||||||||||||||||||||||||||
| Jueves 16 de Octubre de 2008 12:07 | ||||||||||||||||||||||||||||||||||||||||||||
|
En este artículo veremos un ejemplo un poco más complejo, compartiendo un objeto Cola y varios productores y consumidores sobre la misma, programado como si la Cola fuera un objeto "local" accedido por muchos hilos... pero cada hilo se ejecutará en una máquina virtual distinta. Para finalizar, realizaremos una configuración de Alta Disponibilidad con Terracotta, y ejecutaremos a los productores y consumidores contra el cluster. A no asustarse que no es dificil. ¡Empecemos! El ejemplo de Productor-ConsumidorVamos a realizar una aplicación muy simple que constará de una Cola, un Productor y un Consumidor. La Cola será una única instancia en donde los Productores agregarán elementos (objetos de tipo Invasor) y los Consumidores quitarán elementos para procesar. Entonces, las clases que intervienen en este ejemplo son:
La clase Cola deberá tener la lógica necesaria para poder ser accedida por múltiples hilos de ejecución, por lo que sincronizará internamente a su estructura donde mantiene los datos (un ArrayList). Estas clases las empaquetamos dentro de un jar terracota-demo.jar para facilitar su ejecución. Veamos entonces las clases (también pueden Descargar un proyecto de ejemplo que incluye estas clases y archivos de configuración) Cola.javapackage com.dosideas.terracotta; import java.util.ArrayList; public class Cola { private ArrayList elementos = new ArrayList(); public Object obtener() { Object o = null; synchronized (elementos) { if (elementos.size() > 0) { o = elementos.get(0); elementos.remove(0); } } return o; } public void agregar(Object o) { synchronized (elementos) { elementos.add(o); } } public int size() { synchronized (elementos) { return elementos.size(); } } } Productor.javapackage com.dosideas.terracotta; public class Productor { private Cola elementos; public Productor(Cola elementos) { this.elementos = elementos; } public void producir(int cantidad) { System.out.println("Produciendo elementos..."); for (int i=0; i<cantidad; i++) { Invasor invasor = new Invasor(); invasor.setId("Id: " + i); invasor.setNombre("Nombre " + i); invasor.setCargo("Cargo " + i); elementos.agregar(invasor); System.out.printf("Producidos %s elementos\n", i); } } } Consumidor.javapackage com.dosideas.terracotta; public class Consumidor { private Cola elementos; public Consumidor(Cola elementos) { this.elementos = elementos; } public void consumir() { System.out.println("Consumiendo elementos..."); Invasor invasor = null; do { invasor = (Invasor) elementos.obtener(); if (invasor != null) { procesar(invasor); } else { System.out.println("Terminado"); } } while (invasor != null); } private void procesar(Invasor invasor) { System.out.printf("Procesando %s - quedan %s\n", invasor, elementos.size()); try { Thread.sleep(500); } catch (InterruptedException ex) { ex.printStackTrace(); } } } Invasor.javapackage com.dosideas.terracotta; public class Invasor { private String id; private String nombre; private String cargo; //getters y setters a continuacion ... } Main.javapackage com.dosideas.terracotta; public class Main { private static Cola elementos = new Cola(); public static void main(String[] args) { if (args.length != 1) { throw new IllegalArgumentException("Parametros incorrectos"); } if (args[0].equals("productor")) { Productor productor = new Productor(elementos); productor.producir(200); } else if (args[0].equals("consumidor")) { Consumidor consumidor = new Consumidor(elementos); consumidor.consumir(); } else { throw new IllegalArgumentException("Parametros incorrectos"); } } } Configurando TerracottaComo vemos, las clases son "comunes" y no tienen referencia a ningún elemento de Terracotta. De hecho, están programadas para poder ser ejecutadas en un entorno de multi-hilos, de manera tradicional. Nos queda crear el archivo de configuración para Terracotta, llamado tc-config.xml, en donde:
tc-config.xml<?xml version="1.0" encoding="UTF-8"?> <tc:tc-config xmlns:tc="http://www.terracotta.org/config" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.terracotta.org/schema/terracotta-4.xsd"> <servers> <server host="localhost" name="sample"/> <update-check> <enabled>true</enabled> </update-check> </servers> <system> <configuration-model>development</configuration-model> </system> <application> <dso> <roots> <root> <field-name> com.dosideas.terracotta.Main.elementos </field-name> </root> </roots> <instrumented-classes> <include> <class-expression> com.dosideas.terracotta.Cola </class-expression> </include> <include> <class-expression> com.dosideas.terracotta.Invasor </class-expression> </include> </instrumented-classes> <locks> <autolock> <lock-level>write</lock-level> <method-expression> void com.dosideas.terracotta.Cola.agregar(..) </method-expression> </autolock> <autolock> <lock-level>write</lock-level> <method-expression> Object com.dosideas.terracotta.Cola.obtener() </method-expression> </autolock> </locks> </dso> </application> </tc:tc-config> EjecuciónLa ejecución del ejemplo consiste en:
Recordemos que el objeto Cola estará en un área de "memoria compartida", y cada máquina virtual usará la misma instancia del atributo "elementos" de la clase Main. Así, los productores y consumidores que ejecutemos usarán la misma instancia para compartir datos. Ejecución del server de TerracottaLevantamos el server de Terracotta con el comando: TERRACOTTA_HOME/bin/start-tc-server.sh Ejecución de un ProductorNos ubicamos en el directorio con el jar terracotta-demo.jar y el archivo de configuración tc-config.xml y ejecutamos: TERRACOTTA_HOME/bin/dso-java.sh -jar terracotta-demo.jar productor El productor agregará 200 elementos a la instancia de Cola y finalizará. Podemos ejecutarlo varias veces para ir agregando más elementos. Veremos algo como lo siguiente:
Ejecución de los ConsumidoresA continuación ejecutamos a nuestros consumidores: TERRACOTTA_HOME/bin/ds-java.sh -jar terracotta-demo.jar consumidor El consumidor irá quitando elementos de la Cola, hasta que la misma quede vacía. Al quitar un elemento el Consumidor lo "procesa" (simulando una demora). Podemos ir ejecutando muchos consumidores en distintas terminales de forma simulatánea: todos irán consumiendo elementos de la misma instancia del objeto Cola, y podremos ver por consola cómo va decreciendo el tamaño de la misma (o incrementándose, si también ejecutamos un Productor mientras se están consumiendo elementos). En la imagen a continuación vemos a dos consumidores trabajando concurrentemente. Noten cómo se van alternando los ID entre ambos procesos, y cómo varía la cantidad de elementos restantes.
Alta Disponibilidad con TerracottaHasta aquí tenemos un Productor-Consumidor que usan un único servidor de Terracotta para compartir los datos. Pero, ¿qué pasa si necesitamos de alta disponibilidad? Es posible crear un cluster de servidores Terracotta. Estos servidores funcionan en una configuración Activo-Pasivo. Es decir, de todos los nodos del cluster, sólo uno es activo y realiza procesamiento. El resto de los nodos están pasivos, recibiendo la información y el estado. Cuando el nodo activo falla (por ejemplo, porque se cae detiene el proceso manualmente), el cluster elige a un nuevo nodo activo. Este nodo toma la responsabilidad del nodo saliente, y la ejecución de los clientes sigue de manera transparente.
Es decir, los clientes (las instancias de la aplicación) siguen funcionando normalmente pese a la falla de un nodo del cluster. Para crear un cluster de Terracotta es necesario dos configuraciones:
El cliente se conectará a un nodo activo, y cuando falle saltará automáticamente la ejecución al próximo nodo activo que aparezca. Para el siguiente ejemplo usaremos dos equipos físicos distintos, llamados "Dib" y "Gaz". Estos son los nombres de los equipos en la red (pueden usar también la dirección IP).
Configurando los servidores: server-config.xmlCrearemos el archivo server-config.xml que usaremos en cada uno de los servidores de Terracotta que usaremos. En este caso declaramos a los servidores "Dib" y "Gaz". <?xml version="1.0" encoding="UTF-8" ?> <tc:tc-config xmlns:tc="http://www.terracotta.org/config"> <servers> <server host="Dib" name="Equipo-Dib"> <l2-group-port>9530</l2-group-port> <data>%(user.home)/terracotta/server-data</data> <logs>%(user.home)/terracotta/server-logs</logs> <statistics>%(user.home)/terracotta/server-statistics</statistics> </server> <server host="Gaz" name="Equipo-Gaz"> <l2-group-port>9530</l2-group-port> <data>%(user.home)/terracotta/server-data</data> <logs>%(user.home)/terracotta/server-logs</logs> <statistics>%(user.home)/terracotta/server-statistics</statistics> </server> <ha> <mode>networked-active-passive</mode> <networked-active-passive> <election-time>5</election-time> </networked-active-passive> </ha> </servers> <clients> <logs>%(user.home)/terracotta/client-logs</logs> <statistics>%(user.home)/terracotta/client-statistics</statistics> </clients> </tc:tc-config> Configurando los clientes: tc-config.xmlPara los clientes será necesario editar el archivo tc-config.xml (el mismo usado anteriormente) para agregar más servidores a la lista:
<servers>
<server host="Dib" name="Equipo-Dib"/>
<server host="Gaz" name="Equipo-Gaz"/>
<update-check>
<enabled>true</enabled>
</update-check>
</servers>
Ejecución en clusterLeventamos el cluster de TerracottaLa ejecución del cluster es realmente muy sencilla. En el equipo Dib ejecutamos: TERRACOTTA_HOME/start-tc-server.sh -f server-config.xml -n Equipo-Dib Dib se convierte así en el primer particpante el cluster, y en el nodo Activo del mismo. En el equipo Gaz ejecutamos: TERRACOTTA_HOME/start-tc-server.sh -f server-config.xml -n Equipo-Gaz Gaz se une al cluster, y se convierte en un nodo Pasivo. Ejecutamos a los clientesLa ejecución de los clientes (Productores y Consumidores) es exactamente la misma. Tanto el Productor como el Consumidor se conectarán al nodo activo para trabajar. La magia ocurre cuando un nodo falla... así que, vamos a simular la caida de Dib. Mientras un Consumidor esté funcionando, vayan a la consola de Dib (el nodo activo) y paren el proceso (presionen CTRL + C, o cierren la ventana). Verán que los Consumidores se detienen momentáneamente. El nodo pasivo (Gaz) tomará el control, informará que se convierte en el nodo Activo, y los clientes resumiran su actividad de manera totalmente transparente. Los nodos pasivos tenían la misma información del objeto Cola, por lo que pueden asumir el control del cluster. Descargar el ejemplo de TerracottaPueden descargar un proyecto de ejemplo de Terracotta, con todas las clases y archivos de configuración mencionados en este artículo.
Powered by !JoomlaComment 3.26
3.26 Copyright (C) 2008 Compojoom.com / Copyright (C) 2007 Alain Georgette / Copyright (C) 2006 Frantisek Hliva. All rights reserved." |
||||||||||||||||||||||||||||||||||||||||||||
Pues los integrantes de 2i deben esta...
jaaaaajajajaja estuviste bárbaro Jos...
jejeje esto me hace acordar cuando er...
Esteeeeemmmm..... como que no estén ...
Para variar, excelente nota. Clara, c...
aca hay 12 clases muy interesantes y ...