mi sento bene oggi e non voglio downvote tutti gli altri manifesti per dire che non è neanche lontanamente possibile creare una serratura con solo e solo un ammasso di Cassandra. Ho appena implementato l'algoritmo di panificazione di Lamport¹ e funziona perfettamente. Non c'è bisogno di altre strane cose come zoo, gabbie, tavoli di memoria, ecc.
Invece è possibile implementare un meccanismo di blocco multi-processo/multi-computer di un uomo povero purché sia possibile ottenere una lettura e una scrittura con almeno la consistenza QUORUM. Questo è tutto ciò di cui hai veramente bisogno per essere in grado di implementare correttamente questo algoritmo. (il livello QUORUM può variare in base al tipo di blocco necessario: locale, rack, rete completa.)
La mia implementazione verrà visualizzata nella versione 0.4.7 di libQtCassandra (in C++). Ho già provato e si blocca perfettamente. Ci sono alcune altre cose che voglio testare e ti permettono di definire una serie di parametri che al momento sono codificati. Ma il meccanismo funziona perfettamente.
Quando ho trovato questo thread ho pensato che qualcosa non andava. Ho cercato un po 'di più e ho trovato una pagina su Apache che menziono di seguito. La pagina non è molto avanzata ma il loro MoinMoin non offre una pagina di discussione ... Comunque, penso che valga la pena menzionarlo. Speriamo che le persone inizieranno ad implementare quel meccanismo di blocco in tutti i tipi di linguaggi come PHP, Ruby, Java, ecc. In modo che venga utilizzato e noto che funzioni.
Fonte: http://wiki.apache.org/cassandra/Locking
¹ http://en.wikipedia.org/wiki/Lamport%27s_bakery_algorithm
Il seguente è più o meno il modo in cui ho implementato la mia versione. Questa è solo una sinossi semplificata. Potrei aver bisogno di aggiornarlo un po 'di più perché ho apportato alcuni miglioramenti durante il test del codice risultante (anche il codice reale utilizza RAII e include una funzionalità di timeout in cima al TTL.) La versione finale sarà trovata nello libQtCassandra library.
// lock "object_name"
void lock(QString object_name)
{
QString locks = context->lockTableName();
QString hosts_key = context->lockHostsKey();
QString host_name = context->lockHostName();
int host = table[locks][hosts_key][host_name];
pid_t pid = getpid();
// get the next available ticket
table[locks]["entering::" + object_name][host + "/" + pid] = true;
int my_ticket(0);
QCassandraCells tickets(table[locks]["tickets::" + object_name]);
foreach(tickets as t)
{
// we assume that t.name is the column name
// and t.value is its value
if(t.value > my_ticket)
{
my_ticket = t.value;
}
}
++my_ticket; // add 1, since we want the next ticket
table[locks]["tickets::" + object_name][my_ticket + "/" + host + "/" + pid] = 1;
// not entering anymore, by deleting the cell we also release the row
// once all the processes are done with that object_name
table[locks]["entering::" + object_name].dropCell(host + "/" + pid);
// here we wait on all the other processes still entering at this
// point; if entering more or less at the same time we cannot
// guarantee that their ticket number will be larger, it may instead
// be equal; however, anyone entering later will always have a larger
// ticket number so we won't have to wait for them they will have to wait
// on us instead; note that we load the list of "entering" once;
// then we just check whether the column still exists; it is enough
QCassandraCells entering(table[locks]["entering::" + object_name]);
foreach(entering as e)
{
while(table[locks]["entering::" + object_name].exists(e))
{
sleep();
}
}
// now check whether any other process was there before us, if
// so sleep a bit and try again; in our case we only need to check
// for the processes registered for that one lock and not all the
// processes (which could be 1 million on a large system!);
// like with the entering vector we really only need to read the
// list of tickets once and then check when they get deleted
// (unfortunately we can only do a poll on this one too...);
// we exit the foreach() loop once our ticket is proved to be the
// smallest or no more tickets needs to be checked; when ticket
// numbers are equal, then we use our host numbers, the smaller
// is picked; when host numbers are equal (two processes on the
// same host fighting for the lock), then we use the processes
// pid since these are unique on a system, again the smallest wins.
tickets = table[locks]["tickets::" + object_name];
foreach(tickets as t)
{
// do we have a smaller ticket?
// note: the t.host and t.pid come from the column key
if(t.value > my_ticket
|| (t.value == my_ticket && t.host > host)
|| (t.value == my_ticket && t.host == host && t.pid >= pid))
{
// do not wait on larger tickets, just ignore them
continue;
}
// not smaller, wait for the ticket to go away
while(table[locks]["tickets::" + object_name].exists(t.name))
{
sleep();
}
// that ticket was released, we may have priority now
// check the next ticket
}
}
// unlock "object_name"
void unlock(QString object_name)
{
// release our ticket
QString locks = context->lockTableName();
QString hosts_key = context->lockHostsKey();
QString host_name = context->lockHostName();
int host = table[locks][hosts_key][host_name];
pid_t pid = getpid();
table[locks]["tickets::" + object_name].dropCell(host + "/" + pid);
}
// sample process using the lock/unlock
void SomeProcess(QString object_name)
{
while(true)
{
[...]
// non-critical section...
lock(object_name);
// The critical section code goes here...
unlock(object_name);
// non-critical section...
[...]
}
}
grazie - come ho lo aspettavo. Potrebbe anche qualcun altro confermare che - Vorrei essere sicuro al 101% :) –
Potresti ovviamente fare un sacco di scritture e poi controllare per vedere se il tuo vincolo è stato violato. Non so se sarebbe utile per te, comunque. –
Theodore è corretto. In genere quando si desidera unicità, è necessario utilizzare un UUID o una combinazione di dettagli che garantisca univocità. –