Blog Zenika

#CodeTheWorld

Architecture

Introduction à Coherence : Part I

Oracle Coherence entre dans la catégorie relativement fermée des In Memory Data Grids. Initialement édité par la société Tangosol, Coherence a ensuite été rachetée par Oracle et se positionne sur le même marché que GigaSpaces (GigaSpaces), GemFire (VMWare/SpringSource) ou Websphere eXtreme Scale (IBM).

Le parti pris par les solutions d’In Memory Data Grid (IMDG) consiste à déporter le stockage primaire des données du disque vers la mémoire dans le but principal de gagner en performances.
Cependant, la mémoire étant volatile et en quantité plus limitée que le disque, les gains de performances s’accompagnent de contraintes nouvelles dès lors que l’on souhaite prévenir toute perte en cas de défaillance du système. La solution consiste généralement à mettre en place un système distribué où un cluster de machines se répartissent l’ensemble des données. Mais cela introduit d’autres contraintes: que se passe-t-il si une partie des machines tombe ou se retrouve isolée suite à un problème réseau ? Comment synchroniser les données au sein du cluster ?
Les IMDG présentent aussi une rupture importante par rapport aux bases de données relationnelles: les données sont manipulées directement sous forme de graphes d’objets; le modèle et la plupart des opérations doivent être pensées différemment. Cependant, la nature distribuée des IMDG offre de nouvelles possibilités comme l’envoi et l’exécution d’un traitement directement sur les machines qui portent les données à manipuler: le traitement est hautement parallélisé et toute la puissance de calcul du cluster peut être utilisée. Ce type d’architecture permet aussi d’améliorer la scalabilité de l’application puisque l’ajout de nouvelles machines augmente les capacités de stockage et de calcul.
Dans une série d’article, nous présenterons Oracle Coherence à travers sa mise en place dans la célèbre application de démonstration de SpringSource PetClinic en remplacement de la base de données relationnelle. Dans ce premier billet, nous allons introduire Coherence comme un cache distribué simpliste devant la base de données.

Time to get your hands dirty

Get the stuff

Nous vous proposons donc de récupérer le jar Coherence ainsi que le projet PetClinic. il y en a pour tous les goûts :

  • github
git clone -n git://github.com/obourgain/petclinic-coherence.git
git checkout article1-start
  • Subversion
svn co https://src.springframework.org/svn/spring-samples/petclinic/trunk ./petclinic

Vous aurez aussi besoin de Maven et d’un serveur d’application, type Tomcat, pour faire fonctionner l’application.

Get ready

Faites un checkout ou dézippez le projet PetClinic et importez-le dans votre IDE préféré. Dans Eclipse, ouvrir la vue « Servers » et ajoutez un serveur Tomcat (clic droit > new server > …). Puis ajouter le projet Petclinic à ce serveur. Enfin, ouvrir la vue du serveur et vérifier que le « Path » de Petclinic est bien « /petclinic » (clic droit sur le serveur > open > onglet modules).
Dézippez l’archive Coherence et installez le coherence.jar (situé dans lib) dans votre repo Maven :

mvn install:install-file -DgroupId=com.oracle -DartifactId=coherence -Dversion=3.7.0.0b23397 -Dfile=<path_to_your_jar_file> -Dpackaging=jar

Dans le pom.xml du projet, ajouter :

<dependencies>
	...
	<dependency>
		<groupId>com.oracle</groupId>
		<artifactId>coherence</artifactId>
		<version>3.7.0.0b23397</version>
	</dependency>
	...
</dependencies>

Nous sommes presque prêts à lancer l’application mais il reste une précaution à prendre. Au démarrage, Coherence va automatiquement chercher sur le réseau un cluster existant et tenter de le rejoindre. Afin d’éviter tout problème en développant, il est recommandé de s’isoler en limitant le Time To Live des paquets réseaux émis par Coherence. Pour celà, il suffit d’ajouter la propriété suivante à la ligne de commande de démarrage de Tomcat.

-Dtangosol.coherence.ttl=0

Cette ligne de commande est accessible dans Eclipse par le menu run > run configurations … > tomcat dans l’onglet « Arguments ». Ajouter la propriété à la fin des « VM Arguments »
Lancez l’application et connectez vous avec votre navigateur à l’url http://localhost:8080/petclinic pour vérifier que tout fonctionne jusque là.

Disclaimer

L’organisation des caches n’est volontairement pas optimale, mais a été choisie dans le but de présenter les features de Coherence de façon incrémentale et en respectant le modèle déjà existant dans PetClinic. Le code sera aussi dans un premier temps non optimal pour les mêmes raisons.

Let’s code !

Nous allons maintenant introduire Coherence dans l’application pour commencer à remplacer la base de donnée. Cette première implémentation triviale va gèrer les Owners dans un cache Coherence et déléguer le reste des opérations à la SimpleJdbcClinic.
Créez la classe com.zenika.petclinic.coherence.CoherenceClinic qui implémente Clinic. Ajoutez un champ jdbcClinic de type Clinic et le setter correspondant.

private Clinic jdbcClinic;
public void setJdbcClinic(Clinic jdbcClinic) {
	this.jdbcClinic = jdbcClinic;
}

Tou
tes les méthodes sauf loadOwner(), findOwners() et storeOwner() vont déléguer leur traitement à la méthode correspondante de la SimpleJdbcClinic sous-jacente avec un code similaire à celui ci :

public Collection<PetType> getPetTypes() throws DataAccessException {
	return jdbcClinic.getPetTypes();
}

Mise en place du cache au niveau des Owners

Nous allons dans un premier temps conserver le stockage en base de données et utiliser Coherence comme un cache standard pour les Owners, Pets et Visits. Le modèle actuel lie directement ces trois entités en un graphe d’objets simple que nous allons conserver. Notre cache stockera donc un ensemble d’entrées dont la racine sera le Owner.
Avant toute chose, nous avons besoin d’obtenir une référence sur un cache. Nous allons utiliser le cache nommé exemple-distributed défini dans le fichier coherence-cache-config.xml du jar Coherence. Ce cache par défaut suffira pour le moment, nous verrons dans le prochain article comment configurer nos propres caches. La CacheFactory est le point d’entrée pour accéder aux caches Coherence :

private NamedCache getOwnersCache() {
	return CacheFactory.getCache("example-distributed");
}

Le NamedCache que retourne la CacheFactory implémente l’interface java.util.Map. Nous allons donc pour le moment le manipuler comme tel en prenant comme clé l’id (Integer) du Owner.

  • storeOwner()
public void storeOwner(Owner owner) throws DataAccessException {
	// store the owner in database to maintain consistency as we don't yet cache everything
	// we also still rely on this call to generate the owner's id
	jdbcClinic.storeOwner(owner);
	// put the owner in the cache
	getOwnersCache().put(owner.getId(), owner);
}
  • loadOwner()
public Owner loadOwner(int id) throws DataAccessException {
	// search in the cache by key
	Owner owner = (Owner) getOwnersCache().get(id);
	if (owner == null) {
		// if not in cache, try to load from the database
		owner = jdbcClinic.loadOwner(id);
		if (owner != null) {
			// cache it
			getOwnersCache().put(id, owner);
		}
	}
	return owner;
}
  • findOwners()
public Collection<Owner> findOwners(String lastName) throws DataAccessException {
	// load from the database and cache all matching results
	Collection<Owner> owners = jdbcClinic.findOwners(lastName);
	// inserts are batched to improve performance
	Map<Integer, Owner> ownersMap = new HashMap<Integer, Owner>();
	for (Owner owner : owners) {
		ownersMap.put(owner.getId(), owner);
	}
	getOwnersCache().putAll(ownersMap);
	return owners;
}

Pour loadOwner(), nous avions utilisé la méthode put(key, value) du cache, dans findOwners() nous utilisons putAll(Map) afin de batcher les insertions.
Dans le cas d’un cache distribué, Coherence va de façon synchrone répartir les données sur le cluster et créer des copies de backup sur des noeuds différents de ceux portant la donnée principale. Les noeuds Coherence utilisent le multicast UDP ainsi qu’une surcouche propriétaire nommée TCMP pour communiquer entre eux. Chaque appel à put() nécessite donc un appel réseau et attend une valeur de retour. L’utilisation de putAll() permet d’améliorer les performances pour des insertions multiples en ne faisant qu’un unique appel réseau.
Les données sont insérées dans le cache sous forme de graphes d’objets sérialisés. Coherence supporte plusieurs méthodes de sérialisation dont la sérialisation Java et POF, un format propriétaire hautement optimisé. Laissons POF de côté pour le moment et utilisons ici la sérialisation Java standard : tous nos objets doivent donc implémenter java.io.Serializable. Ajoutons cette interface à la classe BaseEntity :

public class BaseEntity implements Serializable

Dans notre cas chaque graphe contient un Owner en racine avec ses Pets et leurs Visits. Toute modification se fait selon le pattern suivant:

  • récupération et de-sérialisation de la donnée du cache
  • modification directe des objets du graphe
  • réinsertion/sérialisation dans le cache

Dans notre cas, modifier un Pet ou une Visit impliquerait de modifier le graphe et de le réinsérer complètement dans le cache à partir du Owner. Pour l’instant, nous sommes dans un état intermédiaire et nous allons simplement invalider cette entrée du cache après insertion en base pour les méthodes storePet() et storeVisit(). Un problème similaire se pose avec la méthode deletePet() mais retrouver le Owner à partir de l’id du Pet requiert l’utilisation de fonctionnalités plus avancées de Coherence que nous découvrirons une autre fois. Pour le moment, cette opération n’invalidera donc pas le cache 🙂 (Nous verrons dans un prochain article comment exécuter des recherches complexes et mettre à jour les données dans le cache)

  • storePet() devient donc :
public void storePet(Pet pet) throws DataAccessException {
	jdbcClinic.storePet(pet);
	getOwnersCache().remove(pet.getOwner().getId());
}
  • De même pour storeVisit() :
public void storeVisit(Visit visit) throws DataAccessException {
	jdbcClinic.storeVisit(visit);
	getOwnersCache().remove(visit.getPet().getOwner().getId());
}

Configuration Spring

Copier le fichier src/main/webapp/WEB-INF/spring/applicationContext-jdbc.xml en applicationContext-coherence.xml et renommer le bean

<bean id="clinic" class="org.springframework.samples.petclinic.jdbc.SimpleJdbcClinic"/>

en

<bean id="jdbcClinic" class="org.springframework.samples.petclinic.jdbc.SimpleJdbcClinic"/>

puis ajouter le bean

<bean id="clinic" class="com.zenika.petclinic.coherence.CoherenceClinic">
    <property name="jdbcClinic" ref="jdbcClinic" />
</bean>

Pour que l’application utilise notre nouveau contexte, nous allons modifier le fichier src/main/webapp/WEB-INF/web.xml. Trouvez la balise <context-param> et remplacez

<param-value>/WEB-INF/spring/applicationContext-jdbc.xml</param-value>

par

<param-value>/WEB-INF/spring/applicationContext-coherence.xml</param-value>

The end

Et voilà, notre application est prête à tourner. Vous pouvez tester et vérifer que tout fonctionne. Lors du premier accès au cache, vous pourrez constater dans les logs que Coherence s’initialise et créé un nouveau cluster.
Pour les moins courageux, vous pouvez récupérer le résultat final sur Github :

git clone -n git://github.com/obourgain/petclinic-coherence.git
git checkout article1-end

ou en zip
A très bientôt pour le prochain épisode: mise en place de nos propres caches et retrait de la base de données !
Article co-écrit par Guillaume Tinon et Olivier Bourgain.
Index des articles de la série Coherence :

Laisser un commentaire

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

En savoir plus sur Blog Zenika

Abonnez-vous pour poursuivre la lecture et avoir accès à l’ensemble des archives.

Continue reading