terracotaImaginemos poder unir la memoria de distintas computadoras en la red, creando una única gran área de memoria compartida por cualquier cantidad de máquinas virtuales Java.

Si esto fuera posible, cambiaría totalmente la forma de compartir datos entre procesos. Por ejemplo, dejaría de ser necesario persistir información (en bases de datos) o usar mensajería para compatir datos que necesitan ser procesados. Simplemente, algún proceso Java crearía objetos en la memoria, esperando que alguna otra aplicación los tome y procese. Suena interesante, ¿no?

Bienvenidos a Terracotta.

Entonces, ¿qué es Terracotta?

Terracotta es un software de código abierto que permite crear aplicaciones Java que pueden escalar a cualquier cantidad de computadoras, sin tener que crear código adicional o usar bases de datos para compartir datos dentro del cluster.

Terracotta utiliza el concepto de Memoria Adjunta a la Red (NAM - Network Attached Memory). El uso de NAM le permite a Terracotta distribuir en cluster a Máquinas Virtuales Java (JVM) directamente debajo de las aplicaciones, brindando alta disponibilidad y escalabilidad de forma transparente.

La ventaja de Terracotta es que la aplicación preparada para funcionar en cluster es exactamente igual a una aplicación Java común. Todos los conceptos y librerías que se usan (POJOs, Spring, Hibernate, threads, sincronización, etc.) funcionan de la misma manera con Terracotta en un entorno distribuido, de la misma manera que funcionan en una única máquina virtual con muchos hilos de ejecución.

Terracotta funciona a nivel de memoria, por lo que no es necesario heredar de ninguna clase ni implementar ninguna interfaz para compatir objetos entre todas las máquinas virtuales del cluster (ni siquiera es necesario implementar java.io.Serializable).

En definitiva, se puede programar la aplicación de manera natural, y se deja a Terracotta que administre todo el trabajo para crear un entorno escalable y de alta disponibilidad.

Características

Terracotta es una alternativa más rápida, simple y barata que las bases de datos tradicionales para compartir datos entre distintos procesos. Terracotta persiste la información a disco y funciona en memoria, por lo que resulta más rápido y escalable que las bases de datos o la replicación por mensajería (siempre hablando dentro del contexto de compartir información entre procesos)

Algunas de las características de Terracotta:

  • Heap compartido entre todas las JVM (replicación de objetos, identidad de objetos entre todas las JVM)
  • Bloqueos distribuidos (sincronización, wait/notify y soporte para java.util.concurrent)
  • Coherencia de objetos garantizada
  • Persistencia a disco para una alta disponibilidad
  • Memoria virtual (el heap puede estar en Terracotta o en disco, de manera transparente)
  • Estructura de datos distribuida, como Maps, Lists, Arrays, Queues, y cualquier POJO
  • Integración con varios frameworks populares (Spring, Lucene, Compass, EHCache, Struts, y muchos más)
  • Herramientas de monitoreo y administración
  • Alta disponibilidad y posiblidad de escalar horizontalmente los servidores de Terracotta

¿Cómo funciona Terracotta?

La Memoria Adjunta a la Red (NAM) tiene similitudes con el Almacenamiento Adjunto a la Red (NAS - Network Attached Storage). Ambos tienen un componente de servidor. Ambos tienen una capa cliente que funciona de forma transparente bajo el sistema de Entrada/Salida. Y ambos tienen un protocolo de red para mover datos entre el cliente y el servidor, y usar un caché de datos locales para lecturas y escrituras más rápidas.

NFS es una versión muy popular de NAS. El servidor de NFS puede manejar cientos de clientes conectados. La interfaz del cliente es el sistema de archvios. Al igual que NAS, Terracotta tiene componentes de cliente y de servidor, y la interfaz es el mismo Java. Así como NAS es transparente bajo el sistema de archivos, NAM es transparente bajo el lenguaje Java. En las aplicaciones con múltiples JVM se crean objetos, se manipulan y mantienen consistententes en NAM. Sin embargo, a diferencia de NAS, NAM puede escalar a varios servidores para lograr alta disponibilidad.

Con esto se logra un cluster a nivel de máquina virtual que brinda:

  • Objetos con ID únicos en todo el cluster
  • Bloqueos y coordinación de trabajos en el cluster
  • Replicación rápida de cambios en objetos
  • Alta disponibilidad y escalabilidad
  • Arquitectura sin puntos de falla únicos

La arquitectura de despliegue en Terracotta

La arquitectura de Terracotta está pensada para minimizar la interacción en la red, garantizar la coherencia de datos y brindar alta disponibilidad sin puntos de fallo únicos.

La arquitectura de despliegue tiene dos componentes principales: los nodos cliente y la matriz de servidores de Terracotta.

Los nodos cliente

Cada nodo cliente se corresponde a un proceso Java en el cluster (por ejemplo, un Servidor de Aplicaciones como GlassFish, o una aplicación aislada). El nodo cliente se ejecuta en una Máquina Virtual Java (JVM) estándar. Durante el inicio de la JVM se cargan los JAR de Terracotta en la JVM.

La matriz de servidores de Terracotta

La matriz de servidores de Terracotta brinda un cluster inteligente tolerante a fallas de alto rendimiento, con bloqueos centralizados y persistencia a disco.

Cada servidor de Terracotta dentro de la matriz es un proceso 100% Java. La matriz puede funcionar en una configuración de pares activo/pasivo para lograr alta disponibilidad, que puede brindar decenas de miles de transacciones por segundo.

arquitectura de Terracotta

Los cambios en una JVM son visibles a otras JVM

Con Terracotta, los cambios que ocurren en una JVM se reflejan instantáneamente a traves del cluster hacia las demás JVM que necesitan saber de este cambio. Sólo se envian por la red los cambios a nivel de atributo, y sólo se envian a aquellas JVM cuyo contexto indica que "necesitan saber" de este cambio. La identidad del objeto se preserva en todo el cluster, por lo que no se necesita comparar por versiones del objeto al hacer el cambio.

Terracotta se interpone en la creación de objetos a nivel de la Máquina Virtual, y así se logra garantizar la identidad del objeto en todo el cluster.

Cluster a nivel de la JVM

Para las aplicaciones, el NAM de Terracottaes igual que el heap local de Java. Ciertas partes del heap se comparten con Terracotta de forma transparente, lo que permite a NAM funcionar como un cluster a nivel de máquina virtual. Desde el punto de vista de la aplicación Java es como si fuera una única gran JVM, con una única área de memoria local, en donde se ejecutan muchos hilos.

Con NAM, las aplicaciones ques que se ejecutan con Terracotta pueden expandirse para funcionar en cualquier cantidad de computadoras distribuidas en la red, sin reescribir la aplicación.

Los desarrolladores definen qué partes de la memoria se debe compartir (es decir, qué atributos de qué objetos). Además, Terracotta identifica y propaga sólo los cambios que ocurren en los objetos ubicados en la NAM.

Cuando una de las JVM que está ejecutando una aplicación cluster falla, Terracotta se asegura que alguna otra JVM disponible reciba los datos de la memoria de la JVM que falló. A medida que nuevas JVMs se unen al cluster, Terracotta las integra al cluster de manera transparente, e inmediatamente se unen para completar los trabajos.

Configuración de rendimiento, no de cluster

Terracotta siempre mantiene la coherencia, es decir, crea una vista consistente de los datos en todo el cluster. Además brinda un rendimiento en-memoria, con la seguridad de la persistencia a disco.

La mayoría de los cambios que se hacen al desplegar Terracotta son para ajustar el rendimiento, y no para configurar el cluster en sí mismo. Terracotta tiene una consola de administración para visualizar el rendimiento de los servidores y clientes.

Ejemplo: "Hola Mundo" en cluster

Como siempre, lo mejor para entender algo es ver un ejemplo concreto. Hagamos entonces un Hola Mundo en cluster con Terracotta, muy sencillo.

Para ejecutar este ejemplo en sus máquinas necesitarán:

  • Un JDK instalado, y la variable de entorno JAVA_HOME apuntando a la raíz de este JDK
  • Terracotta instalado en algún directorio, que de ahora en más llamaremos TERRACOTTA_HOME (por ejemplo, en mis caso es /Users/leito/Proyectos/Librerias/terracotta-2.7.0 )

Vamos a escribir tan solo dos archivos:

  • HolaMundoCluster.java, que será la clase Java que subiremos al cluster y ejecutaremos en distintas JVM
  • tc-config.xml, que será el archivo de configuración de Terracotta para nuestro nodo cliente. Este archivo dirá dónde está funcionando el servidor de Terracotta, e indicaremos qué clases y atributos serán compartidos en el cluster.

¡Manos a la obra!

La clase HolaMundoCluster

Crearemos la clase para este ejemplo. Esta clase contiene el método main() para poder ejecutarla. Tiene un bucle infinito, dentro del cual va recorriendo un mensaje y agregando una letra del mensaje a un buffer (una letra por iteración del bucle). Así, va "copiando" los caracteres del mensaje al buffer, el cual imprime. Al terminar de copiar todas las letras al buffer, lo vacia y vuelva a empezar.

package com.dosideas.terracotta;
 
import java.util.Arrays;
 
public class HolaMundoCluster {
 
  private static final String mensaje = "Hola Mundo en cluster!";
  private static final int length = mensaje.length();
 
  private static char[] buffer = new char [length];
  private static int contador;
 
 
  public static void main(String args[]) throws Exception {
    while (true) {
      synchronized (buffer) {
        int indiceEnMensaje = contador % length;
        contador++;
 
        //Limpiar el buffer al comenzar de nuevo
        if(indiceEnMensaje == 0) {
            Arrays.fill(buffer, ' ');
        }
 
        //agregar una letra del mensaje al buffer
        buffer[indiceEnMensaje] = mensaje.charAt(indiceEnMensaje);
 
        System.out.println( buffer );
 
        //esperamos un ratito para que no sea tan rapido...
        Thread.sleep(100);
      }
    }
  }
}

Esta clase la compilamos dentro de un jar terracotta-demo.jar para facilitar la ejecución.

Podemos ejecutar la clase en forma aislada (sin Terracotta):

java -jar terracotta-demo.jar

La ejecución sin Terracotta nos muestra lo siguiente:

terracotta screenshot ejecucion aislada

Cuando ejecutemos esta misma clase en el cluster de Terracotta, cada proceso irá completando el buffer distribuido, por lo que la salida por consola de cada uno de los HolaMundoCluster en ejecución no será completa.

La clase HolaMundoCluster ya está completa y no sufrirá ningún cambio adicional. A continuación entonces configuraremos Terracotta (tc-config.xml) y luego ejecutaremos la clase HolaMundoCluster contra nuestro servidor de Terracotta.

El archivo de configuración tc-config.xml

Junto a nuestra clase HolaMundoCluster crearemos el archivo de configuración de nuestro nodo cliente de Terracotta. Este archivo indicará:

  • La ubicación de nuestro servidor de Terracotta ("localhost" para nuestro ejemplo)
  • La clase que deberá instrumentarse, es decir, que será compartida en el cluster (HolaMundoCluster)
  • Los atributos de la clase que serán compatirdos en el cluster.

El archivo tc-config.xml queda:

<?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.HolaMundoCluster.buffer
                    </field-name>
                </root>
                <root>
                    <field-name>
                        com.dosideas.terracotta.HolaMundoCluster.contador
                    </field-name>
                </root>
            </roots>
            <instrumented-classes>
                <include>
                    <class-expression>
                        com.dosideas.terracotta.HolaMundoCluster
                    </class-expression>
                </include>
            </instrumented-classes>
            <locks>
                <autolock>
                    <lock-level>write</lock-level>
                    <method-expression>
                        void com.dosideas.terracotta.HolaMundoCluster.main(..)
                    </method-expression>
                </autolock>
            </locks>
        </dso>
    </application>
</tc:tc-config>
 

El script dso-java (¡y ejecutamos el "Hola Mundo" en cluster!)

Ya estamos casi listos. Lo único que nos queda es ejecutar la clase HolaMundoCluster, pero configurando antes a nuestra JVM para que, al crear las clases, se conecte contra al servidor de Terracotta. Esto se logra a través de una bootloader que se carga al inicio de la máquina virtual.

El script dso-java.sh (dso-java.bat para Windows) es una utilidad que se encuentra en el directorio TERRACOTTA_HOME/bin y se encarga de ejecutar la máquina virtual ubicada en el JAVA_HOME, pasándole los parámetros necesarios para cargar Terracotta durante su inicio. dso-java no es una JVM especial. Simplemente nos facilita la ejecución de nuestra JVM de elección. Podemos usar dso-java como si fuera una JVM común, pasándole todos los parámetros habituales (que serán delegados a la JVM nuestra).

Cuando se realice un "new" de nuestra clase HolaMundoCluster, Terracotta interceptará esta creación y se contactará con el servidor local para mantener actualizada la copia del cluster.

Tenemos entonces estos dos archivos ubicados en el mismo directorio:

  • terracotta-demo.jar
  • tc-config.xml

Ejecutando HolaMundoCluster con Terracotta

Ya está todo preparado para la ejecución. Lo primero será iniciar el servidor de Terracotta:

TERRACOTTA_HOME/bin/start-tc-server.sh

Este comando iniciará el servidor de Terracotta, el cual quedará funcionando.

A continuación, en consolas de comando distintas, podemos ir ejecutando nuestros clientes:

TERRACOTTA_HOME/bin/dso-java.sh -jar terracotta-demo.jar

Si tenemos un único cliente funcionando veremos la misma salida que con la ejecución sin Terracotta. Sin embargo, al ir agregando clientes, veremos que cada una de las consolas tarda más en responder (porque se van quedando bloqueadas en la línea Thread.sleep de la clase HolaMundoCluster). Presten atención cómo se va completando el cluster entre los distintos clientes que están ejecutando.

En la siguiente captura de pantalla hay 2 clientes funcionando en la parte superior, y el servidor de Terracotta corriendo en la ventana de más abajo.

terracotta screenshot ejecucion holamundocluster 2

Y a continuación, lo mismo pero con 3 clientes. Noten cómo se va armando el buffer entre los clientes.

terracotta screenshot ejecucion holamundocluster


Inspiración.

"Si tú tienes una manzana y yo tengo una manzana e intercambiamos las manzanas, entonces tanto tú como yo seguiremos teniendo una manzana cada uno. Pero si tú tienes una idea y yo tengo una idea, e intercambiamos las ideas, entonces ambos tendremos dos ideas"

Bernard Shaw