// Define o valor de uma chave com expiração em segundos
SET chave valor EX 10
// Define o valor de uma chave com expiração em milissegundos
SET chave valor PX 1000
// Define tempo de expiração em segundos
EXPIRE nome_chave tempo_segundos
// Define tempo de expiração em milissegundos
PEXPIRE nome_chave tempo_milissegundos
// Retorna o tempo restante até a expiração da chave
TTL nome_chave
// Define valor sem sobrescrever, apenas se a chave não existir
SETNX chave valor
// Define valor com tempo de expiração fixo (em segundos)
SETEX chave segundos valor
// Define valor com tempo de expiração fixo (em milissegundos)
PSETEX chave milissegundos valor
Solução Básica: Uso do comando SETNX
Implementação simples com riscos de inconsistência:
public class BloqueioSimples {
private Jedis cliente;
private String chaveBloqueio;
public BloqueioSimples(Jedis cliente, String chave) {
this.cliente = cliente;
this.chaveBloqueio = chave;
}
public boolean tentarBloquear() {
Long resultado = cliente.setnx(chaveBloqueio, "bloqueado");
if (resultado == 1L) {
cliente.expire(chaveBloqueio, 30); // Tempo de vida de 30s
return true;
}
return false;
}
public void liberar() {
cliente.del(chaveBloqueio);
}
}
Problemas identificados:
- Operações não atômicas:
setnxeexpirepodem ser interrompidas. - Liberação incorreta: qualquer cliente pode remover o bloqueio.
- Não suporta reentrada: threads repetidas falham ao tentar adquirir.
Solução Melhorada: Comando SET Atômico com Lua
Garante atomicidade na aquisição e liberação segura:
public class BloqueioAtômico {
private Jedis cliente;
private String chaveBloqueio;
private String identificadorCliente;
public BloqueioAtômico(Jedis cliente, String chave) {
this.cliente = cliente;
this.chaveBloqueio = chave;
this.identificadorCliente = UUID.randomUUID().toString();
}
public boolean tentarBloquear(int segundosExpiração) {
String resultado = cliente.set(
chaveBloqueio,
identificadorCliente,
SetParams.setParams().nx().ex(segundosExpiração)
);
return "OK".equals(resultado);
}
public boolean liberar() {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
"return redis.call('del', KEYS[1]) else return 0 end";
Object resultado = cliente.eval(script,
Collections.singletonList(chaveBloqueio),
Collections.singletonList(identificadorCliente));
return resultado.equals(1L);
}
}
Melhorias principais:
- Comando
SETatômico comNXeEX. - Script Lua garante que só o dono da chave possa removê-la.
- Identificador único por cliente evita exclusão acidental.
Limitações ainda presentes:
- Dificuldade em renovar o bloqueio durante execução prolongada.
- Risco de falha única no servidor Redis.
- Tempo de execução da aplicação pode exceder o tempo de validade do bloqueio.
Solução Alta Disponibilidade: Algoritmo RedLock
Implementação distribuída com tolerância a falhas:
public class RedLock {
private List<jedis> instanciasRedis;
private String chaveBloqueio;
private String identificador;
private int quorum;
public RedLock(List<Jedis> instancias, String chave) {
this.instanciasRedis = instancias;
this.chaveBloqueio = chave;
this.identificador = UUID.randomUUID().toString();
this.quorum = instancias.size() / 2 + 1;
}
public boolean tentarBloquear(long timeoutMillis) {
long inicio = System.currentTimeMillis();
int sucesso = 0;
for (Jedis jedis : instanciasRedis) {
if (tentarAdquirir(jedis, timeoutMillis)) {
sucesso++;
}
if ((System.currentTimeMillis() - inicio) > timeoutMillis) break;
}
if (sucesso >= quorum) {
long tempoRestante = timeoutMillis - (System.currentTimeMillis() - inicio);
return tempoRestante > 0;
}
// Liberar locks parciais
for (Jedis jedis : instanciasRedis) {
liberar(jedis);
}
return false;
}
private boolean tentarAdquirir(Jedis jedis, long expireMs) {
try {
String resultado = jedis.set(
chaveBloqueio,
identificador,
SetParams.setParams().nx().px(expireMs)
);
return "OK".equals(resultado);
} catch (Exception e) {
return false;
}
}
private void liberar(Jedis jedis) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
"return redis.call('del', KEYS[1]) else return 0 end";
jedis.eval(script,
Collections.singletonList(chaveBloqueio),
Collections.singletonList(identificador));
}
}
</jedis>
Requisitos de implantação:
- Mínimo de 5 instâncias Redis idnependentes.
- Sincronização de relógios entre nós.
- Configuração cuidadosa do tempo limite de bloqueio.
Cenários recomendados:
- Sistemas financeiros críticos.
- Aplicações distribuídas com replicação cross-datacenter.
Solução Produtiva: Redisson
Framework completo com suporte nativo a bloqueios avançados:
// Configuração do cliente Redisson
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
RedissonClient redisson = Redisson.create(config);
// Obter lock com tempo de espera e renovação automática
RLock lock = redisson.getLock("transacaoPedido");
try {
boolean obteve = lock.tryLock(100, 30, TimeUnit.SECONDS);
if (obteve) {
// Processamento de negócio
}
} finally {
lock.unlock();
}
// Encerrar conexão
redisson.shutdown();
Mecanismo de renovação automática (Watchdog):
private void renovarValidade() {
Timeout tarefa = commandExecutor.schedule(() -> {
if (redisClient.eval(...).equals(true)) { // Verifica posse
expirarAssincrono(); // Renova tempo
renovarValidade(); // Recursividade
}
}, internaLockLeaseTime / 3, TimeUnit.MILLISECONDS);
}
Recursos principais:
- Bloqueios reentrantes.
- Implementações de bloqueios justos, múltiplos (MultiLock) e RedLock.
- Meacnismos robustos de recuperação de falhas e reconexão.