Alla ricerca di un modello di blocco distribuito


10

Devo trovare un meccanismo di blocco degli oggetti ricorsivo personalizzato \ modello per un sistema distribuito in C #. In sostanza, ho un sistema multi-nodo. Ogni nodo ha autorizzazioni di scrittura esclusive su n- number pezzi di stato. Lo stesso stato è disponibile anche in forma di sola lettura su almeno un altro nodo. Alcune scritture / aggiornamenti devono essere atomici su tutti i nodi, mentre altri aggiornamenti diventeranno coerenti attraverso i processi di replica in background, le code, ecc.

Per gli aggiornamenti atomici sto cercando un modello o campioni che mi consentano in modo efficiente di contrassegnare un oggetto come bloccato per le scritture che posso quindi distribuire, eseguire il commit, il rollback, ecc ... Dal momento che il sistema ha livelli elevati di concorrenza, io Suppongo che dovrò essere in grado di impilare i blocchi che andranno in timeout o verranno srotolati una volta rilasciati i blocchi.

Le transazioni o i messaggi di messaggistica non sono al centro di questa domanda, ma li ho forniti per un contesto aggiuntivo. Detto questo, sentiti libero di articolare quali messaggi pensi che sarebbero necessari se vuoi.

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

thing.AquireLock(LockLevel.Write);

//Do work

thing.ReleaseLock();

Stavo pensando di utilizzare metodi di estensione, che potrebbero assomigliare 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 mediante un protocollo binario di richiesta / risposta
  • Non ci sono tecnologie intermedie come code o database
  • Non esiste un nodo principale centrale. In questo caso, la disposizione del blocco è definita dall'iniziatore del blocco e dal partner che onorerà la richiesta con una qualche forma di timeout per governarne il comportamento

Qualcuno ha qualche suggerimento?


I blocchi sono generalmente una funzionalità standard nella maggior parte dei sistemi. Immagino che sia lì anche per C #. (Un risultato di ricerca di Google: albahari.com/threading/part2.aspx ) Stai cercando di ottenere qualcosa che va oltre il Mutex o i semafori di base?
Dipan Mehta,

2
@DipanMehta Mi dispiace, avrei dovuto affrontarlo più chiaramente. I nodi che ho citato sono macchine su una rete. La mia comprensione di Mutex e Semaphores è che si tratta di blocchi a livello di macchina ( ad esempio cross-process ) e non di blocchi che possono estendersi tra le macchine su una rete.
JoeGeeky,

@JoeGeeky La tua domanda è sull'argomento qui e potrebbe essere troppo teorica per StackTranslate.it . Se vuoi chiedere di nuovo lì, puoi, ma vorrai un fraseggio più focalizzato sul codice.
Adam Lear

Risposte:


4

Grazie per i chiarimenti.

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

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

Se il problema persiste, è possibile implementare la propria versione di Paxos utilizzando, ad esempio, uno dei soliti sospetti in termini di librerie di messaggistica: la libreria della coda messaggi zero , RabbitMQ o ActiveMQ .


Risposta precedente:

La maggior parte dei suggerimenti su SO ( [A] , [B] ) è destinata all'uso di una coda di messaggi per ottenere il blocco cross-machine.

Il tuo AcquireLockmetodo spingerebbe qualcosa che identifica l'oggetto lock nella coda, controllando le precedenti istanze di lock prima del successo. Il ReleaseLockmetodo rimuove l'oggetto lock dalla coda.

Quindi l'utente atlantis suggerisce, in questo post , il post di Jeff Key per alcuni dettagli.


Grazie, ma queste soluzioni non sarebbero adatte poiché non ho un master, un database o una coda centrali. Ho aggiornato la domanda con alcuni dettagli aggiuntivi per chiarire alcuni di questi dettagli.
JoeGeeky,

Non sarò in grado di utilizzare questi prodotti direttamente poiché esiste già un protocollo ben definito che devo utilizzare per tutte le comunicazioni tra nodi, ma Chubby e Paxos potrebbero avere schemi ben definiti da cui posso imparare. Darò un'occhiata.
JoeGeeky,

@JoeGeeky Sì, il collegamento Paxos ha diagrammi di sequenza che potrebbero consentire di implementarlo utilizzando il collegamento di comunicazione preferito.
Peter K.,

Sebbene non sia una risposta diretta, leggere tutte le cose di Chubby e Paxos mi ha aiutato a definire la mia soluzione. Non ho usato quegli strumenti, ma sono stato in grado di definire un modello ragionevole basato su alcuni dei loro concetti. Grazie.
JoeGeeky

@JoeGeeky: Buono a sapersi, mi è stato di aiuto, almeno. Grazie per il segno di spunta.
Peter K.

4

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

  • comunicazioni (su cui si fa affidamento essenzialmente come affidabili al 100% ... che possono essere fatali)

  • blocco / esclusione reciproca

  • timeout (per quale scopo)?

Un avvertimento: i timeout nei sistemi distribuiti possono essere carichi di pericoli e difficoltà. Se utilizzati, devono essere impostati e utilizzati con molta attenzione perché l'uso indiscriminato dei timeout non risolve un problema, ma difende solo la catastrofe. (Se volete vedere come i timeout dovrebbero essere utilizzati, leggere e capire la documentazione protocollo di comunicazione HDLC. Questo è un buon esempio di utilizzo adeguata e intelligente, in combinazione con un sistema di bit di codifica intelligente per consentire l'individuazione di cose come la linea IDLE) .

Per un certo periodo ho lavorato su sistemi distribuiti multiprocessore collegati tramite collegamenti di comunicazione (non TCP, qualcos'altro). Una delle cose che ho imparato è che come generalizzazione approssimativa, ci sono alcuni posti pericolosi multi-programmazione da percorrere:

  • 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 si riempirà mai, nel qual caso probabilmente potresti usare una soluzione senza coda)

  • fare affidamento sul blocco è doloroso, prova a pensare se esiste 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 usando il blocco, quindi:

Assumerò che userete i timeout solo come mezzo di recupero di ultima istanza, vale a dire per rilevare un guasto del sistema di comunicazione sottostante. Supporrò inoltre che il tuo sistema di comunicazione TCP / IP sia ad alta larghezza di banda e possa essere considerato a bassa latenza (idealmente zero, ma ciò non accade mai).

Quello che vorrei suggerire è che ogni nodo ha un elenco di connettività di altri nodi a cui può connettersi. (Ai nodi non importerebbe da dove provenga una connessione.) La popolazione delle tabelle a cui un nodo può connettersi viene lasciata come una cosa separata da risolvere, non hai detto se sarebbe staticamente impostato o meno. Anche convenientemente ignorato è qualcosa come l'allocazione dei numeri di porta IP in cui le connessioni verrebbero inserite in un nodo - ci possono essere buoni motivi per accettare richieste su una sola porta o su più porte. Questo deve essere attentamente considerato. I fattori includeranno l'accodamento implicito, l'ordinamento, l'uso delle risorse, il tipo di sistema operativo e le funzionalità.

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. È possibile raggruppare queste due operazioni in un wrapper per renderlo atomico. L'effetto di ciò è che i nodi che desiderano acquisire un blocco effettueranno una chiamata in modo simile a:

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 simili (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 fare molta attenzione con un sistema di blocco distribuito che le unità di lavoro eseguite mentre un blocco è tenuto sono piccole e veloci perché avrai molti nodi remoti potenzialmente in attesa di ottenere un blocco. Questo è effettivamente un sistema multiprocessore / 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 elimina le necessità di blocchi?


Rileggendo la domanda, sembra che tu non voglia davvero 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, credo che non sia possibile risolvere il problema di blocco senza ottenere anche le parti sottostanti. Analogia: la costruzione di una casa su basi sbagliate provoca la caduta ... Alla fine.


1
La semantica del timeout è in gran parte lì per gestire i nodi che scompaiono dalla rete o per gestire grandi arretrati negli stack di blocco ... Ciò limiterà il tempo trascorso bloccato in attesa di acquisire un blocco e fornirà a coloro che richiedono il blocco un'opportunità per dare il via ad altri processi in mezzo a ritardi imprevisti, guasti, ecc ... Inoltre, ciò impedirebbe che qualcosa venga bloccato per sempre nel caso in cui qualcosa fallisca. Apprezzo le tue preoccupazioni, anche se a questo punto non vedo alternative dato che alla fine qualcosa fallirà
JoeGeeky,

Per parlare con alcuni dei tuoi altri commenti, non sto usando le code di per sé (nel senso della comunicazione asincrona), anche se mi aspetto che i blocchi siano impilati e rilasciati in base a un modello FIFO. Non mi sono del tutto riconciliato su come funzionerà in termini di modello di richiesta / risposta richiesto diverso da quello che dovrà bloccare in qualche modo e far parte di una stretta di mano più ampia. Al momento, sto lavorando attraverso il meccanismo di blocco impilato all'interno di un singolo nodo e quindi come funzionerà attraverso lo scenario distribuito. Farò un po 'più di lettura come mi hai suggerito. Grazie
JoeGeeky il

@JoeGeeky - un FIFO è una coda. Attenzione alle code. Pensa attentamente a quel lato. Sembra che non avrai qualcosa di "pronto all'uso", ma dovrai riflettere attentamente sul tuo problema e sulla tua soluzione.
quick_now

Capisco ... Stavo cercando di chiarire la differenza tra una coda FIFO utilizzata nei processi asincroni ( ad esempio un processo si accoda e poi un altro dequeues ). In questo caso, le cose dovranno essere gestite in ordine, ma il processo che entra nella coda non si interromperà fino a quando (a) non ottengono il blocco, (b) viene negato un blocco o (c) scadono e lasciano la linea. Più come stare in fila al bancomat. Questo si comporta come un modello FIFO nel caso del successo, ma i processi potrebbero non funzionare prima di raggiungere il fronte. Per quanto riguarda lo scaffale? No, ma questo non è un nuovo problema
JoeGeeky il

0

La tua domanda può essere facilmente implementata usando una cache distribuita come NCache. Ciò di cui hai bisogno è un meccanismo di blocco pessimistico in cui è possibile acquisire un blocco utilizzando un oggetto. Quindi eseguire le attività e le operazioni e rilasciare il blocco per altre applicazioni da consumare in seguito.

Dai un'occhiata al seguente codice;

Qui acquisiresti un blocco su una chiave specifica e quindi eseguirai attività (che vanno da una o più operazioni) e infine rilascia 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: http://blogs.alachisoft.com/ncache/distributed-locking/

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.