LCDS 2.6 et le RTMP Clusterisé

LCDS (LiveCycle Data Services) utilise sa propre méthode de clustering, ainsi pour intégrer au mieux le RTMP dans une architecture clusterisée, nous devons nous y prendre comme expliqué dans la Livedocs Adobe LCDS 2.6 :

- Il faut utiliser jGroups
- Un fichier jgroups-tcp.xml différent par nœud du cluster.

Bien souvent, pour mettre en Production, on utilise une archive war unique et non pas une par noeud. Voici donc une manière de contourner ce problème : générer le fichier de configuration lors du lancement de l'application Web juste avant que le messageBroker (Servlet utilisé par LCDS) d'Lcds ne se configure.

Voici un exemple de fichier jgroup-tcp.xml :

<?xml version="1.0" encoding="UTF-8"?>
<config>
	<TCP bind_addr="{hostname_du_noeud}" start_port="{port}" loopback="false"/>
	<TCPPING timeout="3000"
		initial_hosts="{neoud1}[{port}],{neoud2}[{port}],...,{neoudN}[{port}]"
		port_range="1"
		num_initial_members="3"/>
	<FD_SOCK/>
	<FD timeout="10000" max_tries="5" shun="true"/>
	<VERIFY_SUSPECT timeout="1500" down_thread="false" up_thread="false"/>
	<MERGE2 max_interval="10000" min_interval="2000"/>
	<pbcast.NAKACK gc_lag="100" retransmit_timeout="600,1200,2400,4800"/>
	<pbcast.STABLE stability_
		delay="1000" desired_avg_gossip="20000" down_thread="false"
		max_bytes="0" up_thread="false"/>
	<pbcast.GMS
		print_local_addr="true" join_timeout="5000" join_retry_timeout="2000" shun="true"/>
</config>

Les variables ici sont :

  • hostname_du_noeud : le hostname du noeud courant
  • port : Le port à utiliser pour la communication entre les neouds
  • noeud1, noeud2, ..., noeudN : les hostname des autres noeuds composant le cluster.

Imaginons un fichier template composé de la sorte :

<?xml version="1.0" encoding="UTF-8"?>

<config>
	<TCP bind_addr="{0}" start_port="{1}" loopback="false"/>
	<TCPPING timeout="3000"
		initial_hosts="{2}"
		port_range="1"
		num_initial_members="3"/>
	<FD_SOCK/>
	<FD timeout="10000" max_tries="5" shun="true"/>
	<VERIFY_SUSPECT timeout="1500" down_thread="false" up_thread="false"/>
	<MERGE2 max_interval="10000" min_interval="2000"/>
	<pbcast.NAKACK gc_lag="100" retransmit_timeout="600,1200,2400,4800"/>
	<pbcast.STABLE stability_
		delay="1000" desired_avg_gossip="20000" down_thread="false"
		max_bytes="0" up_thread="false"/>
	<pbcast.GMS
		print_local_addr="true" join_timeout="5000" join_retry_timeout="2000" shun="true"/>
</config>

En configurant notre servlet de génération via le web.xml on pourrait transmettre :

- le numéro de port

- une variable de context (JNDI) contenant les valeurs de hostname et des autres noeuds pour chaque noeud.

voici donc le code possible :

package com.eurodisney.clustering.servlet;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.util.logging.Logger;

import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;

public class ConfigureJGroupServlet extends HttpServlet
{
	private Logger log = Logger.getLogger(ConfigureJGroupServlet.class
			.getName());

	private String hostname;

	private String clusters;

	private String port;

	private String jGroupFilenameDestination = "WEB-INF/flex/jgroups-tcp.xml";

	private String jGroupFilenameTemplate = "WEB-INF/flex/jgroups-tcp.xml";

	private String jndiCluster = "jndi/app/clusters";

	private String serviceConfigFileName = "WEB-INF/flex/services-config.xml";

	private void initParams() throws Exception
	{
		if (getServletConfig().getInitParameter("clusterPort") == null)
			throw new Exception(
					"[INIT CLUSTER] Le paramètre 'clusterPort' n'est pas renseigné");
		else
		{
			port = getServletConfig().getInitParameter("clusterPort");
			log.info("[INIT CLUSTER] ClusterPort utilisé : " + port);
		}

		if (getServletConfig().getInitParameter("jGroupFilenameDestination") == null)
			log
					.warning("[INIT CLUSTER] jGroupFilenameDestination non renseigné, la valeur par défaut a été prise : "
							+ jGroupFilenameDestination);
		else
		{
			jGroupFilenameDestination = getServletConfig().getInitParameter(
					"jGroupFilenameDestination");
			log.info("[INIT CLUSTER] destination jGroup utilisé : "
					+ jGroupFilenameDestination);
		}

		if (getServletConfig().getInitParameter("jGroupFilenameTemplate") == null)
			log
			.warning("[INIT CLUSTER] jGroupFilenameTemplate non renseigné, la valeur par défaut a été prise : "
					+ jGroupFilenameDestination);
		else
		{
			jGroupFilenameTemplate = getServletConfig().getInitParameter(
			"jGroupFilenameTemplate");
			log.info("[INIT CLUSTER] fichier jGroup utilisé : "
					+ jGroupFilenameTemplate);
		}

		if (getServletConfig().getInitParameter("jndiCluster") == null)
			log
					.warning("[INIT CLUSTER] jndiCluster non renseigné, la valeur par défaut a été prise : "
							+ jndiCluster);
		else
		{
			jndiCluster = getServletConfig().getInitParameter("jndiCluster");
			log.info("[INIT CLUSTER] jndi utilisé : " + jndiCluster);
		}

		if (getServletConfig().getInitParameter("services.configuration.file") == null)
			log
					.warning("[INIT CLUSTER] services.configuration.file non renseigné, la valeur par défaut a été prise : "
							+ serviceConfigFileName);
		else
		{
			serviceConfigFileName = getServletConfig().getInitParameter(
					"services.configuration.file");
			log.info("[INIT CLUSTER] services-config flex utilisé : "
					+ serviceConfigFileName);
		}

		InitialContext ctx = new InitialContext();

		Factory jndi = (Factory) ctx.lookup(jndiCluster);
		if (jndi == null)
			throw new Exception(
					"[INIT CLUSTER] Impossible de charger le jndi : "
							+ jndiCluster);

		if (jndi.getAttribute("clusters") == null)
			log.warning("[INIT CLUSTER] Pas de cluster défini");
		else
		{
			clusters = (String) jndi.getAttribute("clusters");
			log.info("[INIT CLUSTER] Clusters définis : " + clusters);
		}

		if (jndi.getAttribute("hostname") == null)
		{
			log
					.warning("[INIT CLUSTER] Le hostname n'est pas défini, Réccupération du hostname via InetAddress");
			hostname = InetAddress.getLocalHost().getHostName();
		} else
			hostname = (String) jndi.getAttribute("hostname");
		log.info("[INIT CLUSTER] Valeur de hostname utilisé : " + hostname);
	}

	@Override
	public void init() throws ServletException
	{
		super.init();
		try
		{
			initParams();

			// jGroup-tcp.xml
			log.info("Lecture de " + jGroupFilenameTemplate);
			String config = readJGroupFile();
			config = replaceJGroupVars(config);

			log.info("Ecriture de " + jGroupFilenameDestination);
			writeFile(jGroupFilenameDestination, config);

			// services-config.xml
			log.finest("Lecture de " + serviceConfigFileName);
			String configStr = readServiceAndReplaceServerName();
			log.finest("Ecriture de " + serviceConfigFileName);
			writeFile(serviceConfigFileName, configStr);

		} catch (Exception e)
		{
			log
					.severe("[INIT CLUSTER FAILED] L'initialisation de la servlet a échoué. Impossible de générer les fichier des configuration pour les clusters");
			e.printStackTrace();
		}
	}

	private String readJGroupFile() throws IOException
	{
		BufferedReader configInput = null;
		try
		{
			File configFile = new File(getServletContext().getRealPath(
					jGroupFilenameTemplate));

			String config = "";

			configInput = new BufferedReader(new InputStreamReader(
					new FileInputStream(configFile)));

			String tmp = configInput.readLine();
			while (tmp != null)
			{
				config += tmp + "n";
				tmp = configInput.readLine();
			}

			return config;
		}
		catch (IOException e)
		{
			throw e;
		}
		finally
		{
			if (configInput != null)
				configInput.close();
		}
	}

	private String replaceJGroupVars(String config) throws NamingException
	{
		config = config.replace("{0}", hostname);
		config = config.replace("{1}", port);

		String replaceStr = "";
		int nbClusters = 0;
		if (clusters != null && !clusters.equals(""))
		{
			replaceStr = "";
			String[] clu = clusters.split(";");
			nbClusters = clu.length;
			for (int i = 0; i < clu.length; ++i)
			{
				replaceStr += clu[i] + "[" + port + "]";
			}
		}
		config = config.replace("{2}", replaceStr);
		config = config.replace("{3}", Integer.toString(nbClusters));
		return config;
	}

	private void writeFile(String path, String body)
			throws FileNotFoundException
	{
		// File jgroupFile = new File(flexConfigDirectory + File.separator
		// + jGroupFilename);
		PrintWriter out = null;
		try
		{
			File file = new File(getServletContext().getRealPath(path));
			out = new PrintWriter(new FileOutputStream(file));
			out.println(body);
			out.close();
		} catch (FileNotFoundException e)
		{
			throw e;
		} finally
		{
			if (out != null)
				out.close();
		}
	}

	private String readServiceAndReplaceServerName() throws IOException
	{
		BufferedReader input = null;
		try
		{
			File serviceConfig = new File(getServletContext().getRealPath(
					serviceConfigFileName));
			input = new BufferedReader(new InputStreamReader(
					new FileInputStream(serviceConfig)));
			String tmp = input.readLine();
			String configStr = "";
			while (tmp != null)
			{
				configStr = configStr
						+ tmp.replaceAll("\{server.name\}", hostname) + "n";
				tmp = input.readLine();
			}
			return configStr;
		} catch (IOException e)
		{
			throw e;
		} finally
		{
			if (input != null)
				input.close();
		}
	}
}
Dans le web.xml on aura :
	<context-param>
		<param-name>flex.class.path</param-name>
		<param-value>/WEB-INF/flex/hotfixes,/WEB-INF/flex/jars</param-value>
	</context-param>

	<!-- Http Flex Session attribute and binding listener support -->
	<listener>
		<listener-class>flex.messaging.HttpFlexSession</listener-class>
	</listener>

	<!-- MessageBroker Servlet -->
	<servlet>
		<servlet-name>MessageBrokerServlet</servlet-name>
		<servlet-class>flex.messaging.MessageBrokerServlet</servlet-class>
		<init-param>
			<param-name>services.configuration.file</param-name>
			<param-value>/WEB-INF/flex/services-config.xml</param-value>
		</init-param>
		<init-param>
			<param-name>flex.write.path</param-name>
			<param-value>/WEB-INF/flex</param-value>
		</init-param>
		<load-on-startup>2</load-on-startup>
	</servlet>

	<servlet>
		<servlet-name>ConfigureJGroupServlet</servlet-name>
		<servlet-class>
			com.eurodisney.clustering.servlet.ConfigureJGroupServlet</servlet-class>
		<init-param>
			<param-name>clusterPort</param-name>
			<param-value>2054</param-value>
		</init-param>
		<init-param>
			<param-name>jGroupFilenameTemplate</param-name>
			<param-value>/WEB-INF/flex/jgroups-tcp.tmp.xml</param-value>
		</init-param>
		<init-param>
			<param-name>jGroupFilenameDestination</param-name>
			<param-value>/WEB-INF/flex/jgroups-tcp.xml</param-value>
		</init-param>
		<init-param>
			<param-name>reeeCluster</param-name>
			<param-value>jndi/app/clusters</param-value>
		</init-param>
		<init-param>
			<param-name>services.configuration.file</param-name>
			<param-value>/WEB-INF/flex/services-config.xml</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>

Il est important de constater les load-on-startup, en effet le messageBroker doit s'initialiser après le ConfigureJGroupServlet.

Le tour est joué.

  • <TCP bind_addr="64.13.226.4start_port="7800" loopback="false"/>
<TCPPING timeout="3000"
initial_hosts="data1.com[7800],data2.com[7800],data3.com[7800]"
port_range="1"
num_initial_members="3"/>
<FD_SOCK/>
<FD timeout="10000" max_tries="5" shun="true"/>
<VERIFY_SUSPECT timeout="1500" down_thread="false" up_thread="false"/>
<MERGE2 max_interval="10000" min_interval="2000"/>
<pbcast.NAKACK gc_lag="100" retransmit_timeout="600,1200,2400,4800"/>
<pbcast.STABLE stability_
delay="1000" desired_avg_gossip="20000" down_thread="false"
max_bytes="0" up_thread="false"/>
<pbcast.GMS

print_local_addr="true" join_timeout="5000" join_retry_timeout="2000"cxwdsq shun="true"/>

dqs

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Captcha *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.