Domanda

Ci sto lavorando da alcuni giorni ormai e ho trovato diverse soluzioni ma nessuna di queste incredibilmente semplice o leggera.Il problema sostanzialmente è questo:Abbiamo un cluster di 10 macchine, ognuna delle quali esegue lo stesso software su una piattaforma ESB multithread.Posso gestire abbastanza facilmente i problemi di concorrenza tra thread sulla stessa macchina, ma per quanto riguarda la concorrenza sugli stessi dati su macchine diverse?

Essenzialmente il software riceve richieste per fornire i dati di un cliente da un'azienda all'altra tramite servizi web.Tuttavia, il cliente potrebbe non esistere ancora sull'altro sistema.In caso contrario, lo creiamo tramite un metodo di servizio web.Quindi richiede una sorta di test-and-set, ma ho bisogno di un semaforo di qualche tipo per impedire alle altre macchine di causare condizioni di gara.Ho già avuto situazioni in cui un cliente remoto veniva creato due volte per un singolo cliente locale, il che non è realmente auspicabile.

Le soluzioni con cui ho giocato concettualmente sono:

  1. Utilizzo del nostro file system condiviso con tolleranza agli errori per creare file "bloccati" che verranno controllati da ciascuna macchina a seconda del cliente

  2. Utilizzando una tabella speciale nel nostro database e bloccando l'intera tabella per eseguire un "test e impostazione" per un record di blocco.

  3. Utilizzando Terracotta, un software server open source che assiste nella scalabilità, ma utilizza un modello hub-and-spoke.

  4. Utilizzo di EHCache per la replica sincrona dei miei "blocchi" in memoria.

Non riesco a immaginare di essere l'unica persona che abbia mai avuto questo tipo di problema.come l'hai risolto?Hai preparato qualcosa internamente o hai un prodotto di terze parti preferito?

È stato utile?

Soluzione

potresti prendere in considerazione l'utilizzo Hazelcast serrature distribuite.Super leggero e facile.

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

Hazelcast: coda distribuita, mappa, set, elenco, blocco

Altri suggerimenti

Usiamo Terracotta, quindi vorrei votare a favore.

Ho seguito Hazelcast e sembra un'altra tecnologia promettente, ma non posso votarla poiché non l'ho usata, e sapendo che utilizza un sistema basato su P2P, non mi fiderei davvero di essa per grandi esigenze di ridimensionamento.

Ma ho anche sentito parlare di Zookeeper, che è uscito da Yahoo e si sta muovendo sotto l'egida di Hadoop.Se sei avventuroso nel provare qualche nuova tecnologia, questa è davvero molto promettente poiché è molto snella e cattiva, concentrandosi solo sulla coordinazione.Mi piace la visione e la promessa, anche se potrebbe essere ancora troppo verde.

Terracotta è più vicino a un modello "a livelli": tutte le applicazioni client comunicano con un Terracotta Server Array (e, cosa più importante per la scalabilità, non comunicano tra loro).Il Terracotta Server Array può essere raggruppato sia per la scalabilità che per la disponibilità (mirroring, per la disponibilità, e striping, per la scalabilità).

In ogni caso, come probabilmente saprai, Terracotta ti dà la possibilità di esprimere la concorrenza attraverso il cluster nello stesso modo in cui lo fai in una singola JVM utilizzando POJO sincronizzato/attendi/notifica o utilizzando una qualsiasi delle primitive java.util.concurrent come ReentrantReadWriteLock , CyclicBarrier, AtomicLong, FutureTask e così via.

Ci sono molte ricette semplici che dimostrano l'uso di queste primitive nel Libro di cucina in terracotta.

Ad esempio, pubblicherò l'esempio ReentrantReadWriteLock (nota che non esiste una versione "Terracotta" del blocco: usi semplicemente Java ReentrantReadWriteLock)

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();
        }
    }
}

Consiglio di usare Redisson.Implementa oltre 30 strutture e servizi di dati distribuiti inclusi java.util.Lock.Esempio di utilizzo:

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();

Stavo per consigliarti sull'utilizzo di memcached come archivio RAM distribuito molto veloce per conservare i log;ma sembra che EHCache sia un progetto simile ma più incentrato su Java.

Entrambe le opzioni sono la strada da percorrere, a patto che tu sia sicuro di utilizzare gli aggiornamenti atomici (memcached li supporta, non conosco EHCache).È di gran lunga la soluzione più scalabile.

Come punto dati correlato, Google utilizza "Chubby", un veloce sistema di archiviazione di blocchi distribuiti basato su RAM come root di diversi sistemi, tra cui BigTable.

Ho lavorato molto con Coherence, che ha consentito diversi approcci all'implementazione di un blocco distribuito.L'approccio ingenuo consisteva nel richiedere di bloccare lo stesso oggetto logico su tutti i nodi partecipanti.In termini di coerenza questo significava bloccare una chiave su una cache replicata.Questo approccio non è molto scalabile perché il traffico di rete aumenta in modo lineare man mano che si aggiungono nodi.Un modo più intelligente era utilizzare una cache distribuita, in cui ciascun nodo nel cluster è naturalmente responsabile di una porzione dello spazio delle chiavi, quindi il blocco di una chiave in tale cache implicava sempre la comunicazione con al massimo un nodo.Potresti sviluppare il tuo approccio basato su questa idea o, meglio ancora, ottenere Coerenza.È davvero il toolkit di scalabilità dei tuoi sogni.

Vorrei aggiungere che qualsiasi meccanismo di blocco basato su una rete multinodo semidecente dovrebbe essere ragionevolmente sofisticato per agire correttamente in caso di guasto della rete.

Non sono sicuro di aver compreso l'intero contesto, ma sembra che tu abbia 1 singolo database a supporto di questo?Perché non utilizzare il blocco del database:se la creazione del cliente è un singolo INSERT, questa istruzione da sola può fungere da blocco poiché il database rifiuterà un secondo INSERT che violerebbe uno dei tuoi vincoli (ad es.il fatto che il nome del cliente sia unico, ad esempio).

Se l'operazione di "inserimento di un cliente" non è atomica ed è un batch di istruzioni, allora introdurrei (o utilizzerei) un INSERT iniziale che crea alcuni semplici record di base che identificano il tuo cliente (con i necessari vincoli di UNICITÀ) e quindi fare tutto il altri inserimenti/aggiornamenti nella stessa transazione.Anche in questo caso il database si prenderà cura della coerenza e qualsiasi modifica simultanea comporterà il fallimento di una di esse.

Ho realizzato un semplice servizio RMI con due metodi:bloccare e rilasciare.entrambi i metodi accettano una chiave (il mio modello di dati utilizzava gli UUID come pk, quindi quella era anche la chiave di blocco).

RMI è una buona soluzione per questo perché è centralizzato.non puoi farlo con gli EJB (specialmente in un cluster poiché non sai su quale macchina atterrerà la tua chiamata).inoltre, è facile.

ha funzionato per me.

Se puoi impostare il bilanciamento del carico in modo che le richieste per un singolo cliente vengano sempre mappate sullo stesso server, puoi gestirlo tramite la sincronizzazione locale.Ad esempio, prendi il tuo ID cliente mod 10 per trovare quale dei 10 nodi utilizzare.

Anche se non vuoi farlo nel caso generale, i tuoi nodi potrebbero delegarsi a vicenda per questo specifico tipo di richiesta.

Supponendo che i tuoi utenti siano sufficientemente uniformi (ad es.se ne hai un sacco) che non ti aspetti che appaiano hot spot dove un nodo viene sovraccaricato, questo dovrebbe comunque scalare abbastanza bene.

Potresti anche considerare Cacheonix per serrature distribuite.A differenza di qualsiasi altra cosa menzionata qui, Cacheonix supporta i blocchi ReadWrite con escalation dei blocchi da lettura a scrittura quando necessario:

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

Informativa completa:Sono uno sviluppatore Cacheonix.

Dato che ti stai già connettendo a un database, prima di aggiungere un altro pezzo infra, dai un'occhiata a JdbcSemaforo, E 'semplice da usare:

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();
}

Fa parte di spf4j biblioteca.

In passato, utilizzavamo uno specifico "server di blocco" sulla rete per gestire questa situazione.Bleh.

Il tuo server database potrebbe avere risorse specifiche per fare questo genere di cose.MS-SQL Server dispone di blocchi delle applicazioni utilizzabili tramite sp_getapplock/sp_releaseapplock procedure.

Abbiamo sviluppato un framework di sincronizzazione distribuito open source, attualmente DistributedReentrantLock e il blocco DistributedReentrantReadWrite sono stati implementati, ma siamo ancora in fase di test e refactoring.Nella nostra architettura le chiavi di blocco sono divise in secchi e ciascun nodo è responsabile di un certo numero di secchi.Quindi, in effetti, per una richiesta di blocco riuscita, esiste una sola richiesta di rete.Stiamo anche utilizzando la classe AbstractQueuedSynchronizer come stato di blocco locale, quindi tutte le richieste di blocco non riuscite vengono gestite localmente, questo riduce drasticamente il traffico di rete.Stiamo usando JGroups (http://jgroups.org) per la comunicazione di gruppo e Hessian per la serializzazione.

per i dettagli, consultare http://code.google.com/p/vitrit/.

Per favore inviami il tuo prezioso feedback.

Kamran

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top