Contrôle de concurrence dissortingbué

Cela fait quelques jours que je travaille là-dessus et j’ai trouvé plusieurs solutions, mais incroyablement simples et légères. Le problème est essentiellement le suivant: nous avons un cluster de 10 machines, chacune exécutant le même logiciel sur une plate-forme ESB multithread. Je peux assez facilement gérer les problèmes de concurrence entre les threads sur la même machine, mais qu’en est-il de la concurrence sur les mêmes données sur des machines différentes?

Essentiellement, le logiciel reçoit des demandes pour alimenter les données d’un client d’une entreprise à une autre via des services Web. Cependant, le client peut ou non exister encore sur l’autre système. Si ce n’est pas le cas, nous le créons via une méthode de service Web. Donc, il faut une sorte de test-and-set, mais j’ai besoin d’un sémaphore de quelque sorte pour empêcher les autres machines de provoquer des conditions de course. J’ai déjà eu des situations où un client distant était créé deux fois pour un seul client local, ce qui n’est pas vraiment souhaitable.

Les solutions avec lesquelles j’ai joué sur le plan conceptuel sont les suivantes:

  1. Utilisation de notre système de fichiers partagé tolérant aux pannes pour créer des fichiers “verrouillés” qui seront vérifiés par chaque machine en fonction du client

  2. Utiliser une table spéciale dans notre firebase database et verrouiller la table entière pour effectuer un “test-set” pour un enregistrement de locking.

  3. Utilisation de Terracotta, un logiciel serveur open source qui aide à la mise à l’échelle, mais utilise un modèle en écanvas.

  4. Utiliser EHCache pour la réplication synchrone de mes “verrous” en mémoire

Je ne peux pas imaginer que je suis la seule personne à avoir eu ce genre de problème. Comment l’avez-vous résolu? Avez-vous cuisiné quelque chose en interne ou avez-vous un produit favori de tiers?

Vous pouvez envisager d’utiliser des verrous dissortingbués Hazelcast . Super lite et facile.

java.util.concurrent.locks.Lock lock = Hazelcast.getLock ("mymonitor"); lock.lock (); try { // do your stuff }finally { lock.unlock(); } 

Hazelcast – File d’attente dissortingbuée, carte, set, liste, verrou

Nous utilisons la terre cuite, alors je voudrais voter pour cela.

J’ai suivi Hazelcast et ça ressemble à une autre technologie prometteuse, mais je ne peux pas voter car je ne l’ai pas utilisé, et sachant qu’il utilise un système basé sur le P2P, je ne lui ferais vraiment pas confiance besoins de mise à l’échelle.

Mais j’ai aussi entendu parler de Zookeeper, qui est sorti de Yahoo et évolue sous l’égide d’Hadoop. Si vous êtes aventureux à essayer de nouvelles technologies, cela est vraiment prometteur, car il est très simple et efficace, il se concentre uniquement sur la coordination. J’aime la vision et la promesse, même si elle rest trop verte.

La terre cuite est plus proche d’un modèle «à plusieurs niveaux»: toutes les applications clientes communiquent avec un serveur Terracotta Server (et plus important encore, à l’échelle, elles ne se parlent pas). Le serveur Terracotta Server Array peut être mis en cluster à la fois pour la mise à l’échelle et la disponibilité (en miroir, pour la disponibilité et par bandes, pour la mise à l’échelle).

Dans tous les cas, comme vous le savez probablement, Terracotta vous permet d’exprimer la simultanéité sur le cluster de la même manière que dans une seule JVM en utilisant POJO synchronized / wait / notify ou en utilisant l’une des primitives java.util.concurrent telles que ReentrantReadWriteLock. , CyclicBarrier, AtomicLong, FutureTask, etc.

Il y a beaucoup de recettes simples démontrant l’utilisation de ces primitives dans le livre de cuisine en terre cuite .

Par exemple, je posterai l’exemple ReentrantReadWriteLock (notez qu’il n’y a pas de version “Terracotta” du verrou – vous utilisez juste Java ReentrantReadWriteLock normal)

 import java.util.concurrent.locks.*; public class Main { public static final Main instance = new Main(); private int counter = 0; private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(true); public void read() { while (true) { rwl.readLock().lock(); try { System.out.println("Counter is " + counter); } finally { rwl.readLock().unlock(); } try { Thread.currentThread().sleep(1000); } catch (InterruptedException ie) { } } } public void write() { while (true) { rwl.writeLock().lock(); try { counter++; System.out.println("Incrementing counter. Counter is " + counter); } finally { rwl.writeLock().unlock(); } try { Thread.currentThread().sleep(3000); } catch (InterruptedException ie) { } } } public static void main(Ssortingng[] args) { if (args.length > 0) { // args --> Writer instance.write(); } else { // no args --> Reader instance.read(); } } } 

Je recommande d’utiliser Redisson . Il implémente plus de 30 structures et services de données dissortingbués, dont java.util.Lock . Exemple d’utilisation:

 Config config = new Config(); config.addAddress("some.server.com:8291"); Redisson redisson = Redisson.create(config); Lock lock = redisson.getLock("anyLock"); lock.lock(); try { ... } finally { lock.unlock(); } redisson.shutdown(); 

J’allais donner des conseils sur l’utilisation de memcached comme stockage de RAM très rapide et dissortingbué pour conserver les journaux; mais il semble que EHCache soit un projet similaire mais plus centré sur le java.

L’un ou l’autre est la voie à suivre, tant que vous êtes sûr d’utiliser les mises à jour atomiques (memcached les prend en charge, vous ne connaissez pas EHCache). C’est de loin la solution la plus évolutive.

En tant que sharepoint données associé, Google utilise «Chubby», un stockage de locking dissortingbué rapide, basé sur la RAM, à la racine de plusieurs systèmes, dont BigTable.

J’ai beaucoup travaillé avec Coherence, ce qui a permis plusieurs approches pour mettre en place un verrou dissortingbué. L’approche naïve consistait à demander à verrouiller le même object logique sur tous les nœuds participants. En termes de cohérence, il s’agissait de verrouiller une clé sur un cache répliqué. Cette approche n’évolue pas bien car le trafic réseau augmente linéairement lorsque vous ajoutez des nœuds. Une méthode plus intelligente consistait à utiliser un cache dissortingbué, où chaque nœud du cluster est naturellement responsable d’une partie de l’espace clé. Le locking d’une clé dans un tel cache impliquait toujours une communication avec au plus un nœud. Vous pouvez lancer votre propre approche basée sur cette idée, ou mieux encore, obtenir la cohérence. C’est vraiment la boîte à outils d’évolutivité de vos rêves.

J’appendais que tout mécanisme de locking basé sur un réseau multi-nœuds à moitié décent devrait être raisonnablement sophistiqué pour agir correctement en cas de défaillance du réseau.

Vous ne savez pas si je comprends tout le contexte, mais il semblerait que vous ayez une seule firebase database à cet effet? Pourquoi ne pas utiliser le locking de la firebase database: si la création du client est une seule INSERT, cette instruction seule peut servir de verrou puisque la firebase database rejettera une seconde INSERT qui violerait une de vos contraintes (par exemple, le nom du client est unique par exemple).

Si l’opération “insertion d’un client” n’est pas atomique et consiste en un lot d’instructions, j’introduirais (ou utiliserais) une INSERT initiale qui crée un enregistrement de base simple identifiant votre client (avec les contraintes UNIQUE) nécessaires, puis effectue toutes les opérations autres insertions / mises à jour dans la même transaction. Encore une fois, la firebase database veillera à la cohérence et toute modification simultanée entraînera la défaillance de l’une d’entre elles.

J’ai fait un service RMI simple avec deux méthodes: verrouiller et libérer. les deux méthodes prennent une clé (mon modèle de données utilisait des UUID comme pk, donc c’était aussi la clé de locking).

RMI est une bonne solution car elle est centralisée. vous ne pouvez pas faire cela avec les EJB (spécialement dans un cluster car vous ne savez pas sur quelle machine votre appel va atterrir). de plus, c’est facile.

cela a fonctionné pour moi.

Si vous pouvez configurer votre équilibrage de charge pour que les requêtes pour un seul client soient toujours associées au même serveur, vous pouvez gérer cela via la synchronisation locale. Par exemple, prenez votre ID client mod 10 pour trouver lequel des 10 nœuds utiliser.

Même si vous ne voulez pas le faire dans le cas général, vos nœuds pourraient se prémunir mutuellement pour ce type de requête spécifique.

En supposant que vos utilisateurs soient suffisamment uniformes (c.-à-d. Si vous en avez une tonne) que vous ne vous attendez pas à voir apparaître des points chauds où un nœud est surchargé, cela devrait encore bien évoluer.

Vous pouvez également considérer Cacheonix pour les verrous dissortingbués. Contrairement à tout ce qui est mentionné ici, Cacheonix prend en charge les verrous ReadWrite avec une escalade du locking de la lecture à l’écriture lorsque cela est nécessaire:

 ReadWriteLock rwLock = Cacheonix.getInstance().getCluster().getReadWriteLock(); Lock lock = rwLock.getWriteLock(); try { ... } finally { lock.unlock(); } 

Divulgation complète: Je suis un développeur Cacheonix.

Étant donné que vous vous connectez déjà à une firebase database, avant d’append un autre élément, consultez JdbcSemaphore , il est simple à utiliser:

 JdbcSemaphore semaphore = new JdbcSemaphore(ds, semName, maxReservations); boolean acq = semaphore.acquire(acquire, 1, TimeUnit.MINUTES); if (acq) { // do stuff semaphore.release(); } else { throw new TimeoutException(); } 

Il fait partie de la bibliothèque spf4j .

Dans le passé, nous utilisions un “serveur de locking” spécifique sur le réseau pour gérer cela. Bleh.

Votre serveur de firebase database peut disposer de ressources spécifiques pour effectuer ce type de tâches. MS-SQL Server dispose de verrous d’application utilisables via les procédures sp_getapplock / sp_releaseapplock .

Nous avons développé une infrastructure de synchronisation dissortingbuée open source, actuellement le verrou DissortingbutedReentrantLock et DissortingbutedReentrantReadWrite a été implémenté, mais sont toujours en phase de test et de refactorisation. Dans notre architecture, les clés de locking sont divisées en compartiments et chaque nœud est responsable d’un certain nombre de compartiments. Donc, pour une demande de locking réussie, il n’y a qu’une seule requête réseau. Nous utilisons également la classe AbstractQueuedSynchronizer comme état de locking local. Ainsi, toutes les demandes de locking ayant échoué sont traitées localement, ce qui réduit considérablement le trafic sur le réseau. Nous utilisons JGroups ( http://jgroups.org ) pour la communication de groupe et Hessian pour la sérialisation.

pour plus de détails, consultez http://code.google.com/p/visortingt/ .

S’il vous plaît envoyez-moi vos précieux commentaires.

Kamran