Question Contrôle de concurrence distribué


J'y travaille depuis quelques jours maintenant 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é avec conceptuellement sont:

  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 base de données et verrouiller la table entière pour effectuer un "test-set" pour un enregistrement de verrouillage.

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

  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?


52
2017-09-18 13:18


origine


Réponses:


vous voudrez peut-être envisager d'utiliser Hazelcast serrures distribuées. 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 distribuée, carte, set, liste, verrou


33
2017-09-18 23:11



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 P2P à son écoute, 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 très prometteur, car il est très léger et efficace, et se concentre uniquement sur la coordination. J'aime la vision et la promesse, même si elle reste trop verte.


13
2017-10-17 03:17



La terre cuite est plus proche d'un modèle "à plusieurs niveaux" - toutes les applications clientes communiquent avec un système de serveurs Terracotta (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(String[] args)
    {
        if (args.length > 0)  {
            // args --> Writer
            instance.write();
        } else {
            // no args --> Reader
            instance.read();
        }
    }
}

4
2017-09-19 16:19



Je recommande d'utiliser Redisson. Il implémente plus de 30 structures de données et services distribués, y compris 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();

3
2018-01-12 10:16



J'allais donner des conseils sur l'utilisation de memcached comme stockage de RAM très rapide et distribué 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 point de données associé, Google utilise «Chubby», un stockage de verrouillage distribué rapide, basé sur la RAM, à la racine de plusieurs systèmes, dont BigTable.


2
2017-10-17 03:36



J'ai beaucoup travaillé avec Coherence, ce qui a permis plusieurs approches pour mettre en place un verrou distribué. L'approche naïve consistait à demander à verrouiller le même objet 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 ne s'adapte pas très bien car le trafic réseau augmente linéairement lorsque vous ajoutez des nœuds. Une méthode plus intelligente consistait à utiliser un cache distribué, où chaque nœud du cluster est naturellement responsable d'une partie de l'espace clé. Le verrouillage 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'ajouterais que tout mécanisme de verrouillage 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.


1
2017-09-18 13:42



Vous ne savez pas si je comprends tout le contexte, mais il semblerait que vous ayez une seule base de données à cet effet? Pourquoi ne pas utiliser le verrouillage de la base de données: si la création du client est une seule INSERT, cette instruction seule peut servir de verrou puisque la base de données 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 base de données veillera à la cohérence et toute modification simultanée entraînera la défaillance de l'une d'entre elles.


1
2017-09-18 13:50



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 verrouillage).

Le RMI est une bonne solution car il est centralisé. vous ne pouvez pas le faire avec des 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.


0
2017-09-18 13:33



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’est-à-dire 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 s’améliorer.


0
2017-11-16 07:27



Vous pourriez également envisager Cacheonix pour les serrures distribuées. Contrairement à tout ce qui est mentionné ici, Cacheonix prend en charge les verrous ReadWrite avec une escalade du verrouillage 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.


0
2017-08-26 16:54



Étant donné que vous vous connectez déjà à une base de données, avant d’ajouter 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 spf4j bibliothèque.


0
2017-08-23 01:01