Operaciones atómicas de Memcache en PHP

7

Esta publicación es un seguimiento de esta pregunta: PHP Atomic Memcache en StackOverflow.

Considerando que estoy usando Memcache (no d al final) en PHP 5.3.10, implementé un sistema de bloqueo personalizado donde un cliente esperará hasta que se destruya una clave de bloqueo antes de que comience a modificar una clave en memcache.

Entonces:

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

Esto funciona el 90% del tiempo. Funcionaría el 100% del tiempo si dos solicitudes se hacen muy alejadas entre sí (por ejemplo, 500 ms). Pero digamos que dos solicitudes se realizan casi al mismo tiempo (con una diferencia de 10 a 100 microsegundos), la solución anterior falla y ambos clientes escriben en la misma clave, lo que da como resultado datos incorrectos.

He intentado muchas cosas, incluido un ciclo que varía en el tiempo de espera en cada iteración:

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

Esto ayuda solo un poco.

¿Cuál sería la solución a este problema en particular? Estos procesos mecache deben ser atómicos. Estoy dispuesto a tolerar una tasa de fracaso del 1% (ya que significa que solo necesito trabajar un poco más para hacerlo 0), pero algo más es demasiado arriesgado.

Me he roto la cabeza tratando de resolver esto. No hay posibilidad de actualizar a Memcached, y los cambios de valor no son simples (no son incrementos)

    
pregunta Kovo 25.05.2012 - 17:06

1 respuesta

6
  

El cliente 1 se conecta, busca un bloqueo activo en la llave 1, no encuentra ninguno y   obtiene los datos

No debe probar el bloqueo, debe crearlo, sino intentar crearlo con Memcache::add , que creará bloqueo o falla. Lo hace de forma atómica, por lo que ya no tendrás condición de carrera 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')

Tras una inspección adicional, según la documentación de PHP, add es atómico. Pero al profundizar, resulta que puede no ser atómico en los clústeres de memcache con múltiples servidores de memcache. Sin embargo, hay una solución para esto:

$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')
    
respondido por el vartec 26.05.2012 - 02:43

Lea otras preguntas en las etiquetas