Quando vários clientes leem e atualizam simultaneamente a mesma chave, a última gravação substitui silenciosamente todas as anteriores — uma condição de corrida conhecida como "última gravação vence". O TairString, tipo de dado de string estendida (exString) no Tair (Enterprise Edition), possui um número de versão integrado que torna o bloqueio otimista uma operação nativa. Esse recurso permite detectar e repetir gravações conflitantes sem locks ou transações externas.
Como funciona o bloqueio otimista com número de versão
O bloqueio otimista associa um número de versão a cada chave. Ao ler uma chave, você também obtém sua versão. Durante a gravação, forneça a versão lida anteriormente. Se outro cliente gravou na chave nesse intervalo, o servidor incrementou a versão e ela não corresponde mais à sua. Nesse caso, o sistema rejeita a gravação em vez de aplicá-la silenciosamente. Repita a operação com o valor e a versão atualizados.
O TairString resolve esse problema nativamente: strings nativas do Redis consistem apenas em chaves e valores, sem nenhum mecanismo para detectar se uma chave mudou entre uma leitura e uma gravação.
A condição de corrida: por que a última gravação vence causa perda de dados
O diagrama a seguir ilustra uma condição de corrida típica.

Considere um contador que dois clientes incrementam simultaneamente. Ambos leem o valor atual (10), calculam independentemente 11 e gravam o resultado. O valor final é 11 em vez do esperado 12 — um incremento se perde silenciosamente. O mesmo padrão se aplica a qualquer chave compartilhada:
key_1inicia com o valorhello.A Aplicação 1 lê
hello.A Aplicação 2 lê
hello.A Aplicação 1 grava
worldemkey_1.A Aplicação 2 grava
universeemkey_1.
A Aplicação 1 espera que key_1 seja world, mas o valor real é universe. Para evitar isso, a sequência de leitura-modificação-gravação deve ser atômica — exatamente o que o mecanismo de número de versão do TairString oferece.
Comandos do TairString para bloqueio otimista
Os TairStrings são compostos por chaves, valores e números de versão. O TairString integra todos os recursos de string do Redis, exceto operações de bits.
Os comandos do TairString e os comandos de string nativos do Redis não são intercambiáveis.
|
Comando |
Finalidade |
Comportamento |
|
EXSET |
Crie uma chave TairString |
O número de versão padrão é |
|
EXGET |
Consultar uma chave TairString |
Retorna tanto o |
|
EXSET com versão |
Atualize com verificação de versão |
Tem êxito e incrementa a versão em |
|
EXCAS |
Compare-And-Swap em uma única chamada |
Verifica a versão e aplica a gravação atomicamente; em caso de falha, retorna o erro, o valor atual e a versão atual em uma única resposta |
Para obter a referência completa de comandos, consulte exString.
Implementar bloqueio otimista com EXGET e EXSET
O padrão básico de bloqueio otimista utiliza um loop de repetição: leia o valor e a versão atuais com EXGET, calcule o novo valor e grave com EXSET, passando a versão lida. Se ocorrer uma gravação concorrente entre sua leitura e gravação, a verificação de versão falhará e o loop repetirá a operação.
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.
}
Isso garante que, se qualquer outro cliente atualizou a chave após seu EXGET, seu EXSET falhará em vez de substituir silenciosamente a alteração. Cada iteração desse loop requer duas viagens de ida e volta ao Tair: um EXGET para ler o valor e a versão atuais e um EXSET para tentar a gravação.
Quando uma chave TairString é excluída e recriada com o mesmo nome, a nova chave começa na versão
1. Ela não herda a versão anterior.Para ignorar a verificação de versão e forçar a substituição de um valor, use a opção ABS com EXSET. Consulte EXSET para obter detalhes.
Reduzir E/S com EXCAS
O loop EXGET seguido de EXSET exige duas operações de E/S por ciclo de repetição. O comando EXCAS (Compare-And-Swap) reduz isso para uma única operação de E/S por ciclo — uma melhoria significativa sob alta concorrência.
O EXCAS verifica atomicamente a versão e aplica a gravação em uma única viagem de ida e volta. Em caso de falha, ele retorna os três itens a seguir em uma única resposta:
"ERR update version is stale"O valor atual
O número de versão atual
Como o estado atual está incluído na resposta de falha, o próximo ciclo de repetição pode começar imediatamente — nenhuma chamada extra de EXGET é necessária.
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;
}