Alla ricerca di un modello di blocco distribuito

10

Ho bisogno di creare un meccanismo / meccanismo di blocco degli oggetti ricorsivo personalizzato per un sistema distribuito in C #. Essenzialmente, ho un sistema multi-nodo. Ogni nodo ha permessi esclusivi di scrittura su n - numero di pezzi di stato. Lo stesso stato è disponibile anche nel modulo di sola lettura su almeno un altro nodo. Alcune scritture / aggiornamenti devono essere atomici su tutti i nodi, mentre altri aggiornamenti alla fine diventeranno coerenti attraverso processi di replica in background, code, ecc ...

Per gli aggiornamenti atomici sto cercando un pattern o campioni che consentano in modo efficiente di contrassegnare un oggetto come bloccato per le scritture che posso quindi distribuire, commettere, rollback, ecc ... Dal il sistema ha livelli elevati di concorrenza, presumo che dovrò essere in grado di impilare blocchi che verranno sospesi o srotolati dopo il rilascio dei blocchi.

I pezzi di transazione o di messaggistica non sono al centro di questa domanda, ma li ho forniti per un ulteriore contesto. Detto questo, sentiti libero di articolare i messaggi che ritieni siano necessari se lo desideri.

Ecco un esempio vago di ciò che stavo immaginando anche se sono aperto a nuove idee oltre all'implementazione di nuovi prodotti interi

thing.AquireLock(LockLevel.Write);

//Do work

thing.ReleaseLock();

Stavo pensando di utilizzare i metodi di estensione, che potrebbero essere simili a questo

public static void AquireLock(this IThing instance, TupleLockLevel lockLevel)
{ 
    //TODO: Add aquisition wait, retry, recursion count, timeout support, etc...  
    //TODO: Disallow read lock requests if the 'thing' is already write locked
    //TODO: Throw exception when aquisition fails
    instance.Lock = lockLevel;
}

public static void ReleaseLock(this IThing instance)
{
    instance.Lock = TupleLockLevel.None;
}

Per chiarire un paio di dettagli ...

  • Tutte le comunicazioni sono TCP / IP utilizzando un protocollo di richiesta / risposta binario
  • Non ci sono tecnologie intermedie come code o database
  • Non esiste un nodo principale centrale. In questo caso, la disposizione di blocco è definita dall'iniziatore della serratura e dal partner che onorerà la richiesta con qualche forma di timeout per governarne il comportamento

Qualcuno ha qualche suggerimento?

    
posta JoeGeeky 26.12.2011 - 19:19
fonte

3 risposte

4

Grazie per i chiarimenti.

In tal caso, ciò che consiglierei è utilizzare un modello di pubblicazione / sottoscrizione. Il protocollo di blocco distribuito Chubby di Google (un'implementazione di Paxos )

Non ho mai usato Paxos (o Chubby), ma sembra esserci un'implementazione open source qui .

Se ciò non funziona, potresti implementare la tua versione di Paxos usando, ad esempio, uno dei soliti sospetti in termini di librerie di messaggistica: il libreria di code di messaggi zero , RabbitMQ o ActiveMQ .

Risposta precedente:

La maggior parte dei suggerimenti su SO ( [A] , [B] ) utilizza la coda dei messaggi per ottenere il blocco cross-machine.

Il tuo metodo AcquireLock spingerebbe qualcosa che identifica l'oggetto lock nella coda, controllando le precedenti istanze di blocchi prima del successo. Il tuo metodo ReleaseLock rimuoverà l'oggetto lock dalla coda.

SO utente atlantis suggerisce, in questo post , post di Jeff Key per alcuni di il dettaglio.

    
risposta data 26.12.2011 - 21:00
fonte
4

Mi sembra che tu abbia un paio di tecnologie miste qui:

  • comunicazioni (su cui si basa essenzialmente l'affidabilità al 100% ... che può essere fatale)

  • blocco / esclusione reciproca

  • timeout (per quale scopo)?

Un avvertimento: i timeout nei sistemi distribuiti possono essere irti di pericolo e difficoltà. Se utilizzati, devono essere impostati e utilizzati con molta attenzione perché l'uso indiscriminato dei timeout non risolve un problema, ma rimanda la catastrofe. (Se vuoi vedere come usare dovrebbe , leggere e comprendere la documentazione del protocollo di comunicazione HDLC.Questo è un buon esempio di uso appropriato e intelligente, in combinazione con un intelligente sistema di codifica dei bit per consentire rilevamento di cose come linea IDLE).

Per un certo periodo di tempo ho lavorato su sistemi distribuiti multiprocessore connessi tramite collegamenti di comunicazione (non TCP, altro). Una delle cose che ho imparato è che, come generalizzazione approssimativa, ci sono alcuni posti pericolosi per la programmazione multipla:

  • la dipendenza dalle code di solito finisce in lacrime (se la coda si riempie, sei nei guai. A MENO CHE tu possa calcolare una dimensione della coda che non riempirà mai, nel qual caso probabilmente potresti usare una soluzione senza coda)

  • la dipendenza dal blocco è dolorosa, prova a pensare se c'è un altro modo (se devi usare il blocco, guarda la letteratura, il blocco distribuito multiprocessore è stato oggetto di molti documenti acedemici degli ultimi 2- 3 decenni)

Devo procedere utilizzando il blocco, quindi:

MI ASSUMERò che utilizzerai i timeout solo come mezzo di recupero di ultima istanza, cioè per rilevare un guasto del sistema di comunicazione sottostante. Immaginerò inoltre che il tuo sistema di comunicazione TCP / IP abbia un'elevata larghezza di banda e possa essere pensato come una bassa latenza (idealmente zero, ma questo non succede mai).

Quello che suggerirei è che ogni nodo ha una lista di connettività di altri nodi a cui può connettersi. (I nodi non si preoccupano da dove proviene una connessione). La popolazione delle tabelle di cui un nodo può connettersi è lasciata come una cosa separata da risolvere, non è stato detto se sarebbe stato impostato staticamente o in altro modo. Anche opportunamente ignorate sono cose come l'allocazione dei numeri di porta IP in cui le connessioni verrebbero in un nodo: ci possono essere buoni motivi per accettare richieste solo su una singola porta o su più porte. Questo deve essere attentamente considerato. I fattori includeranno l'accodamento implicito, l'ordinazione, l'uso delle risorse, il tipo di sistema operativo e le capacità.

Una volta che i nodi sanno a chi si connettono, possono inviare a quel nodo una richiesta di blocco e devono ricevere una risposta di blocco da quel nodo remoto. Puoi impacchettare queste due operazioni in un wrapper per farlo sembrare atomico. L'effetto di questo è che i nodi che desiderano acquisire un blocco effettueranno una chiamata come:

if (get_lock(remote_node) == timeout) then
  {
    take some failure action - the comms network is down
  }

/* Lock is now acquired - do work here */

if (release_lock(remote_node) == timeout) then
  {
    take some failure action - the comms network is down
  }

le chiamate get_lock e release_lock dovrebbero essere qualcosa di simile (in linea di principio):

send_to_remote_node(lock_request)
get_from_remote_node_or_timeout(lock_reply, time)
if (result was timeout) then
  return timeout
else
  return ok

Dovrai prestare molta attenzione con un sistema di blocco distribuito che le unità di lavoro eseguite mentre un blocco è bloccato sono piccole e veloci perché avrai molti nodi remoti potenzialmente in attesa di ottenere un blocco. Questo è effettivamente un multiprocessore / sistema di comunicazione stop-and-wait che è robusto ma non ha le massime prestazioni possibili.

Un suggerimento è di adottare un approccio completamente diverso. È possibile utilizzare una chiamata di procedura remota in cui ogni chiamata RPC contiene un pacchetto di informazioni che può essere gestito dal destinatario e che rimuove le esigenze di blocco?

Sulla rilettura della domanda, sembra che tu non voglia veramente preoccuparti del lato comunicativo delle cose, vuoi solo risolvere il tuo problema di blocco.

La mia risposta potrebbe quindi sembrare un po 'fuori tema, tuttavia, ritengo che non sia possibile risolvere il problema della chiusura senza ottenere anche le parti sottostanti. Analogia: Costruire una casa su cattive fondazioni fa sì che cada giù ... Alla fine.

    
risposta data 27.12.2011 - 03:07
fonte
0

La tua domanda può essere facilmente implementata utilizzando una cache distribuita come NCache. Quello che ti serve è un meccanismo di blocco pessimistico in cui puoi acquisire un lucchetto usando un oggetto. Quindi esegui i tuoi compiti e le operazioni e rilascia il blocco per altre applicazioni da consumare in seguito.

Dai un'occhiata al seguente codice;

Qui acquisirai un blocco su una chiave specifica e quindi eseguirai le attività (che vanno da una o più operazioni) e infine rilascerai il blocco quando hai finito.

// Instance of the object used to lock and unlock cache items in NCache
LockHandle lockHandle = new LockHandle();

// Specify time span of 10 sec for which the item remains locked
// NCache will auto release the lock after 10 seconds.
TimeSpan lockSpan = new TimeSpan(0, 0, 10); 

try
{
    // If item fetch is successful, lockHandle object will be populated
    // The lockHandle object will be used to unlock the cache item
    // acquireLock should be true if you want to acquire to the lock.
    // If item does not exists, account will be null
    BankAccount account = cache.Get(key, lockSpan, 
    ref lockHandle, acquireLock) as BankAccount;
    // Lock acquired otherwise it will throw LockingException exception

    if(account != null && account.IsActive)
    {
        // Withdraw money or Deposit
        account.Balance += withdrawAmount;
        // account.Balance -= depositAmount;

        // Insert the data in the cache and release the lock simultaneously 
        // LockHandle initially used to lock the item must be provided
        // releaseLock should be true to release the lock, otherwise false
        cache.Insert("Key", account, lockHandle, releaseLock); 
        //For your case you should use cache.Unlock("Key", lockHandle);
    }
    else
    {
        // Either does not exist or unable to cast
        // Explicitly release the lock in case of errors
        cache.Unlock("Key", lockHandle);
    } 
}
catch(LockingException lockException)
{
    // Lock couldn't be acquired
    // Wait and try again
}

Tratto dal link: link

    
risposta data 03.05.2016 - 08:21
fonte