Quando vários hosts competem por um recurso compartilhado, locks distribuídos evitam corrupção de dados e falhas lógicas. Este tópico explica o funcionamento dos locks distribuídos, como implementá-los no ApsaraDB for Redis e Tair (Enterprise Edition) e como manter a consistência do lock após um failover. Os comandos CAD e CAS, específicos do Tair, eliminam a necessidade de scripts Lua exigidos pelo Redis padrão, reduzem a complexidade de implementação e aumentam o throughput.
Quando usar locks distribuídos
Cenários de concorrência diferentes exigem mecanismos de coordenação distintos:
|
Cenário |
Mecanismo |
|
Várias threads no mesmo processo |
Mutex ou lock de leitura/escrita |
|
Vários processos no mesmo host |
Semáforo, pipeline ou memória compartilhada |
|
Vários hosts em um sistema distribuído |
Lock distribuído |
Locks distribuídos são mecanismos de exclusão mútua com escopo global. Use-os para coordenar o acesso a recursos compartilhados entre hosts e prevenir falhas lógicas causadas por contenção de recursos.

Propriedades de um lock distribuído bem implementado
Um lock distribuído deve atender a três propriedades. Cada abordagem de implementação neste tópico é avaliada com base nesses critérios:
Exclusividade mútua: Em qualquer momento, apenas um cliente detém o lock.
Livre de deadlocks: Os locks usam um mecanismo baseado em concessão (lease). Se um cliente sofrer uma exceção após adquirir o lock, este expira automaticamente e evita o bloqueio indefinido do recurso.
Consistência: Após um switchover de alta disponibilidade (HA), o estado do lock deve permanecer intacto. Switchovers podem ser acionados por erros externos (falhas de hardware, exceções de rede) ou internos (consultas lentas, defeitos do sistema). Quando ocorre um switchover, um nó réplica é promovido a novo nó mestre.
Implementar locks distribuídos no Redis
Os métodos nesta seção aplicam-se tanto ao ApsaraDB for Redis quanto ao Redis Open-Source Edition.
Adquirir um lock
Execute o comando SET com as opções NX e EX para adquirir um lock atomicamente:
SET resource_1 random_value NX EX 5
|
Parâmetro |
Descrição |
|
|
Chave do lock. Se esta chave existir, o recurso estará bloqueado e inacessível para outros clientes. |
|
|
String aleatória única entre todos os clientes. Usada para verificar a propriedade antes de liberar o lock. |
|
|
Período de validade do lock em segundos. Use |
|
|
Cancela a operação SET se a chave já existir. |
Neste exemplo, o lock expira após 5 segundos caso não seja liberado explicitamente. A expiração previne deadlocks: se o cliente falhar, o sistema recupera o lock automaticamente.
Liberar um lock
Executar DEL resource_1 diretamente não é seguro. Considere este cenário de falha:
No instante t1, a aplicação 1 adquire
resource_1com uma concessão de 3 segundos.A aplicação 1 trava por mais de 3 segundos. A chave expira no instante t2 e o lock é liberado automaticamente.
No instante t3, a aplicação 2 adquire o lock.
A aplicação 1 retoma no instante t4 e executa
DEL resource_1, liberando acidentalmente um lock que não lhe pertence mais.
Somente o cliente que definiu o lock deve liberá-lo. Use o script Lua a seguir para verifique a propriedade e exclua atomicamente:
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
Renovar um lock
Caso o cliente não consiga concluir seu trabalho dentro do período de concessão, ele deve renovar o lock. Apenas o cliente que defina o lock pode renová-lo:
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("expire", KEYS[1], ARGV[2])
else
return 0
end
Implementar locks distribuídos no Tair
Instâncias suportadas: Instâncias baseadas em DRAM do Tair e instâncias otimizadas para memória persistente.
Nessas instâncias, o tipo de dado TairString fornece os comandos CAD e CAS. Esses comandos substituem os scripts Lua acima por operações atômicas únicas, reduzem a complexidade de implementação e melhoram o throughput.
Adquirir um lock
A aquisição do lock é idêntica à abordagem do Redis:
SET resource_1 random_value NX EX 5
Liberar um lock
O comando CAD (Compare-And-Delete) verifica a propriedade e exclui a chave em uma única operação atômica, substituindo totalmente o script Lua:
CAD resource_1 my_random_value
Isso equivale a: se GET(resource_1) == my_random_value, execute DEL(resource_1).
Renovar um lock
O comando CAS (Compare-And-Swap) estende a expiração do lock em uma única operação atômica, substituindo totalmente o script Lua:
CAS resource_1 my_random_value my_random_value EX 10
O comando CAS não verifica se o novo valor é igual ao valor original. Passe o mesmo valor aleatório tanto para o campo de valor antigo quanto para o novo para preservar a identidade do lock enquanto estende a concessão.
Código de exemplo com Jedis
Os exemplos Java a seguir mostram um ciclo de vida completo de lock usando Jedis. Todas as três operações — adquirir, liberar e renovar — usam a mesma resourceKey e o mesmo randomValue.
Defina os comandos CAS e CAD
enum TairCommand implements ProtocolCommand {
CAD("CAD"), CAS("CAS");
private final byte[] raw;
TairCommand(String alt) {
raw = SafeEncoder.encode(alt);
}
@Override
public byte[] getRaw() {
return raw;
}
}
Adquirir um lock
public boolean acquireDistributedLock(Jedis jedis, String resourceKey, String randomValue, int expireTime) {
SetParams setParams = new SetParams();
setParams.nx().ex(expireTime);
String result = jedis.set(resourceKey, randomValue, setParams);
return "OK".equals(result);
}
Liberar um lock
public boolean releaseDistributedLock(Jedis jedis, String resourceKey, String randomValue) {
jedis.getClient().sendCommand(TairCommand.CAD, resourceKey, randomValue);
Long ret = jedis.getClient().getIntegerReply();
return 1 == ret;
}
Renovar um lock
public boolean renewDistributedLock(Jedis jedis, String resourceKey, String randomValue, int expireTime) {
jedis.getClient().sendCommand(TairCommand.CAS, resourceKey, randomValue, randomValue, "EX", String.valueOf(expireTime));
Long ret = jedis.getClient().getIntegerReply();
return 1 == ret;
}
Garantir a consistência do lock após um failover
A replicação de mestre para réplica é assíncrona. Se um nó mestre falhar após gravar um lock, mas antes da replicação da gravação, a réplica promovida não terá registro desse lock. Dois clientes poderiam então manter o mesmo lock simultaneamente, violando a propriedade de consistência.
Três abordagens mitigam esse risco:
|
Abordagem |
Custo |
Limitações |
Ideal para |
|
Algoritmo Redlock |
Alto (requer múltiplas instâncias) |
Incompatível com instâncias cluster ou mestre-réplica padrão; aquisição de lock mais lenta |
Requisitos máximos de tolerância a falhas |
|
Comando WAIT |
Baixo |
Não garante consistência se ocorrer um switchover antes do retorno do WAIT |
Implantações padrão onde o custo é relevante |
|
Replicação semissíncrona do Tair |
Integrado |
Regride para assíncrono se uma réplica falhar |
Cargas de trabalho de alta concorrência em instâncias Tair |
Usar o algoritmo Redlock
O algoritmo Redlock, proposto pelos fundadores do projeto open source Redis, reduz a probabilidade de perda de lock distribuindo os locks entre N instâncias Redis independentes. Uma única instância mestre-réplica pode perder um lock com probabilidade de k% durante um switchover. Com o Redlock, a probabilidade de todas as N instâncias perderem seus locks simultaneamente cai para (k%)^N.
O algoritmo exige que os locks tenham sucesso em M de N nós, onde 1 < M ≤ N. Não é necessário que todos os N locks tenham sucesso simultaneamente.
Compensações do Redlock:
A aquisição e liberação de locks são mais lentas devido ao envolvimento de múltiplas instâncias.
Requer várias instâncias independentes do ApsaraDB for Redis ou Redis autogerenciado, aumentando os custos de infraestrutura.
Incompatível com instâncias cluster e instâncias mestre-réplica padrão.
Usar o comando WAIT
O comando WAIT bloqueia o cliente atual até que todos os comandos de escrita anteriores sejam replicados para um número especificado de nós réplica, ou até que o tempo limite (em milissegundos) expire. O comando WAIT é muito mais econômico que o algoritmo Redlock.
Após adquirir um lock, execute WAIT para confirme a replicação:
SET resource_1 random_value NX EX 5
WAIT 1 5000
Neste exemplo, o período de tempo limite é de 5.000 milissegundos. Se WAIT retornar 1, os dados estarão sincronizados entre o nó mestre e os nós réplica, garantindo a consistência. No entanto, se um switchover de HA for acionado antes que WAIT retorne uma resposta bem-sucedida, os dados podem ser perdidos — o valor de retorno indica apenas uma possível falha de sincronização. Após WAIT retornar um erro, readquira o lock ou verifique seu estado antes de prosseguir.
Notas de uso:
WAIT bloqueia apenas o cliente que o envia; outros clientes não são afetados.
Não execute WAIT antes de liberar um lock. Locks distribuídos são mutuamente exclusivos por design, portanto, uma liberação ligeiramente atrasada não causa falhas lógicas.
Usar o Tair
As instâncias baseadas em DRAM do Tair resolvem a consistência de locks no nível de infraestrutura:
Performance 3x superior: Instâncias baseadas em DRAM do Tair oferecem três vezes mais performance que o Redis open source, sustentando cargas de trabalho de locks com alta concorrência sem interrupção de serviço.
Replicação semissíncrona: Uma resposta de sucesso é retornada somente após a gravação ser confirmada no nó mestre e replicada para pelo menos um nó réplica. Isso evita a perda de locks após um switchover.
A replicação semissíncrona regride para replicação assíncrona se um nó réplica falhar ou se ocorrer uma exceção de rede durante a sincronização.
Os comandos CAD e CAS reduzem ainda mais a complexidade de implementação em comparação aos scripts Lua e eliminam a necessidade de múltiplas instâncias independentes exigidas pelo Redlock.