Cuando varios clientes leen y actualizan simultáneamente la misma clave, la última escritura sobrescribe silenciosamente todas las anteriores. Esta condición de carrera se conoce como "last-write-wins". TairString, el tipo de datos de cadena extendida (exString) en Tair (Enterprise Edition), incluye un número de versión integrado que convierte el bloqueo optimista en una operación nativa. Esto permite detectar y reintentar escrituras conflictivas sin necesidad de bloqueos externos ni transacciones.
Funcionamiento del bloqueo optimista basado en números de versión
El bloqueo optimista asigna un número de versión a cada clave. Al leer una clave, también obtiene su versión. Al escribir, proporciona la versión leída previamente. Si otro cliente escribió en la clave durante ese intervalo, la versión del servidor se incrementó y ya no coincide con la suya; por lo tanto, la escritura se rechaza en lugar de aplicarse silenciosamente. Posteriormente, reintente la operación con el valor y la versión actualizados.
TairString resuelve este problema de forma nativa: las cadenas nativas de Redis solo constan de claves y valores, sin ningún mecanismo para detectar si una clave cambió entre una lectura y una escritura.
La condición de carrera: por qué last-write-wins provoca pérdida de datos
A continuación se muestra un diagrama que ilustra una condición de carrera típica.

Considere un contador que dos clientes incrementan simultáneamente. Ambos leen el valor actual (10), calculan independientemente 11 y escriben el resultado. El resultado final es 11 en lugar del 12 esperado, por lo que uno de los incrementos se pierde silenciosamente. Este mismo patrón aplica a cualquier clave compartida:
key_1comienza con el valorhello.La aplicación 1 lee
hello.La aplicación 2 lee
hello.La aplicación 1 escribe
worldenkey_1.La aplicación 2 escribe
universeenkey_1.
La aplicación 1 espera que key_1 sea world, pero el valor real es universe. Para evitar esto, la secuencia de lectura-modificación-escritura debe ser atómica, función que proporciona exactamente el mecanismo de números de versión de TairString.
Comandos de TairString para bloqueo optimista
Los TairStrings constan de claves, valores y números de versión. TairString integra todas las funciones de cadenas de Redis, excepto las operaciones de bits.
Los comandos de TairString y los comandos de cadenas nativas de Redis no son intercambiables.
|
Comando |
Propósito |
Comportamiento |
|
EXSET |
Cree una clave TairString |
El número de versión predeterminado es |
|
EXGET |
Consultar una clave TairString |
Devuelve tanto |
|
EXSET con versión |
Actualice con verificación de versión |
Se ejecuta correctamente e incrementa la versión en |
|
EXCAS |
Compare-And-Swap en una sola llamada |
Verifica la versión y aplica la escritura atómicamente; en caso de fallo, devuelve el error, el valor actual y la versión actual en una sola respuesta |
Para consultar la referencia completa de comandos, consulte exString.
Implementación de bloqueo optimista con EXGET y EXSET
El patrón básico de bloqueo optimista utiliza un bucle de reintento: lea el valor y la versión actuales con EXGET, calcule el nuevo valor y luego escriba con EXSET al pasar la versión leída. Si ocurrió una escritura competitiva entre su lectura y su escritura, la verificación de versión falla y el bucle se reintenta.
while(true){
{value, version} = EXGET(key); // Retrieve the value and version number of the key.
value2 = update(...); // Save the new value as value 2.
ret = EXSET(key, value2, version); // Update the key and assign the return value to the ret variable.
if(ret == OK)
break; // If the return value is OK, the update is successful and the while loop exits.
else if (ret.contains("version is stale"))
continue; // If the return value contains the "version is stale" error message, the update fails and the while loop is repeated.
}
Esto garantiza que, si algún otro cliente actualizó la clave después de su EXGET, su EXSET fallará en lugar de sobrescribir silenciosamente dicho cambio. Cada iteración de este bucle requiere dos viajes de ida y vuelta a Tair: un EXGET para leer el valor y la versión actuales, y un EXSET para intentar la escritura.
Cuando se elimina una clave TairString y se vuelve a crear con el mismo nombre, la nueva clave comienza en la versión
1. No hereda la versión anterior.Para omitir la verificación de versión y forzar la sobreescritura de un valor, utilice la opción ABS con EXSET. Consulte EXSET para obtener más detalles.
Reducción de E/S con EXCAS
El bucle EXGET seguido de EXSET requiere dos operaciones de E/S por ciclo de reintento. El comando EXCAS (Compare-And-Swap) reduce esto a una sola operación de E/S por ciclo, lo que representa una mejora significativa bajo alta concurrencia.
EXCAS verifica atómicamente la versión y aplica la escritura en un solo viaje de ida y vuelta. En caso de fallo, devuelve a continuación tres elementos en una sola respuesta:
"ERR update version is stale"El valor actual
El número de versión actual
Dado que el estado actual se incluye en la respuesta de fallo, el siguiente ciclo de reintento puede comenzar inmediatamente sin necesidad de una llamada adicional a EXGET.
while(true){
{ret, value, version} = excas(key, new_value, old_version) // Use the CAS command to replace the original value with a new value.
if(ret == OK)
break; // If the return value is OK, the update is successful and the while loop exits.
else (if ret.contains("update version is stale")) // If the return value contains the "update version is stale" error message, the update fails. The values of the value and old_version variables are updated.
update(value);
old_version = version;
}