Operazioni Atomic Memcache in PHP

7

Questo post è un seguito di questa domanda: PHP Atomic Memcache su StackOverflow.

Considerando che sto usando Memcache (no alla fine) su PHP 5.3.10, ho implementato un sistema di blocco personalizzato in cui un client attenderà che una chiave di blocco venga distrutta prima che inizi a modificare una chiave su memcache.

Quindi:

Client 1 connects, checks for an active lock on key 1, finds none, and gets the data
Client 2 connects a few microsecond after Client 1, requests the same data from key 1,
   but finds a lock
Client 2 enters a retry loop until Client 1 releases the lock
Client 1 saves new data to key 1, releases the lock
Client 2 gets the fresh data, sets a lock on key 1, and continues

Questo funziona il 90% delle volte. Funzionerebbe il 100% del tempo se due richieste sono fatte distanti tra loro (diciamo 500ms). Ma diciamo che due richieste sono fatte quasi allo stesso tempo (da 10 a 100 microsecondi a parte) la soluzione di cui sopra non riesce, ed entrambi i client scrivono sulla stessa chiave, risultando in dati errati.

Ho provato molte cose, tra cui un ciclo che varia nel tempo di attesa ogni iterazione:

while(/*lock key exists*/)
{
    usleep(mt_rand(1000,100000);
}

Questo aiuta solo un po '.

Quale sarebbe la soluzione a questo particolare problema? Questi processi di mecache devono essere atomici. Sono disposto a tollerare un tasso di errore dell'1% (poiché significa che devo solo lavorare un po 'più duramente per farlo 0), ma qualsiasi altra cosa è troppo rischiosa.

Mi sono rotto la testa cercando di capirlo. Non è possibile eseguire l'upgrade a Memcached e le modifiche del valore non sono semplici (non sono incrementi)

    
posta Kovo 25.05.2012 - 17:06
fonte

1 risposta

6

Client 1 connects, checks for an active lock on key 1, finds none, and gets the data

Non dovresti testare il lock, quindi crearlo, ma piuttosto tentare di crearlo con Memcache::add , che creerà lock o fail. Lo fa in modo atomico, quindi non avrai più condizioni di gara TOCTTOU .

$mc= new Memcache;
$mc->connect('localhost', 11211);

while(!$mc->add('your_lock_key', 1))
{
    usleep(mt_rand(1000,100000);
}

manipulate_data(...)

$mc->delete('your_lock_key')

Dopo un'ulteriore ispezione, secondo la documentazione di PHP add è atomico. Ma scavando più a fondo, risulta che potrebbe non essere atomico su cluster di memcache con più server memcache. C'è comunque una soluzione a questo:

$mc= new Memcache;
$mc->connect('localhost', 11211);

$locked = false;

$lock_key = $mc->get('your_lock_key');
if (!$lock_key) {
   $lock_key = uniqid();
   $mc->add('your_lock_key', $lock_key);
}

while(!$locked) {
   if($mc->cas($cas_token, 'your_lock_key', $lock_key) && 
      $mc->getResultCode() == Memcached::RES_SUCCESS) {
        $locked = true;
        break;
   } else {
     $other_key = $mc->get('your_lock_key');  //this is only to reset last access for CAS
   }
   usleep(mt_rand(1000,100000);
} 


manipulate_data(...)

$mc->delete('your_lock_key')
    
risposta data 26.05.2012 - 02:43
fonte

Leggi altre domande sui tag