.env.
Lição 0: do zero ao primeiro comando (15 min)
Timeouts, pool e retry¶
Quando o link entre a aplicação e o Redis oscila, threads sem timeout ficam presas esperando uma resposta que não vem, o pool esgota e a aplicação congela. Timeout explícito, pool limitado e repetição apenas de operações seguras ajudam a limitar o impacto da falha. No Redis Cloud, um failover pode mudar o IP atrás do nome. A política de DNS deve estar aplicada antes da primeira resolução; o ponto de entrada Main faz isso no curso.
O que o lab faz¶
- Configurar timeout de conexão e de comando (2 s) no Jedis e no Lettuce
- Limitar o pool do Jedis a 8 conexões, com
PINGnas conexões ociosas - Refazer um
SETcom retry e backoff exponencial, só para erros de conexão - Medir uma falha rápida contra
10.255.255.1com connect timeout de 500 ms - Desligar o cache de DNS da JVM e entender por quê
Faça agora¶
./quest run 301-01 jedis
./quest run 301-01 lettuce # opcional: mesmo lab, outro client
./quest verify 301-01
O código¶
// Applied at application startup, before creating any client:
Security.setProperty("networkaddress.cache.ttl", "0");
Security.setProperty("networkaddress.cache.negative.ttl", "0");
DefaultJedisClientConfig config = DefaultJedisClientConfig.builder()
.connectionTimeoutMillis(2000) // max time to open the TCP connection
.socketTimeoutMillis(2000) // max time waiting for a reply
.build();
ConnectionPoolConfig pool = new ConnectionPoolConfig();
pool.setMaxTotal(8);
pool.setMaxWait(Duration.ofSeconds(1)); // fail instead of queueing forever
pool.setTestWhileIdle(true); // PING idle connections
pool.setTimeBetweenEvictionRuns(Duration.ofSeconds(5));
try (RedisClient jedis = RedisClient.builder()
.hostAndPort(new HostAndPort(host, port))
.clientConfig(config)
.poolConfig(pool)
.build()) {
withRetry(4, 200, () -> jedis.set(bossKey, when)); // retries JedisConnectionException only
}
// fast fail: an address that never answers the SYN
DefaultJedisClientConfig ghostConfig = DefaultJedisClientConfig.builder()
.connectionTimeoutMillis(500).socketTimeoutMillis(500).build();
try (RedisClient phantom = RedisClient.builder()
.hostAndPort(new HostAndPort("10.255.255.1", 6379))
.clientConfig(ghostConfig).build()) {
phantom.ping(); // JedisConnectionException after about 500 ms
}
// Applied before any DNS lookup/client creation:
Security.setProperty("networkaddress.cache.ttl", "0");
Security.setProperty("networkaddress.cache.negative.ttl", "0");
RedisURI uri = RedisURI.create(url);
uri.setTimeout(Duration.ofSeconds(2)); // command timeout (sync API)
SocketOptions socket = SocketOptions.builder()
.connectTimeout(Duration.ofSeconds(2))
.keepAlive(SocketOptions.KeepAliveOptions.builder()
.enable().idle(Duration.ofSeconds(5)).interval(Duration.ofSeconds(5)).count(3).build())
.build(); // tcpUserTimeout(...) needs netty epoll (Linux)
ClientOptions options = ClientOptions.builder()
.autoReconnect(true) // default: at-least-once
.socketOptions(socket)
.timeoutOptions(TimeoutOptions.enabled(Duration.ofSeconds(2)))
.replayFilter(cmd -> "INCR".equalsIgnoreCase(cmd.getType().toString())) // never replay INCR
.build();
RedisClient client = RedisClient.create(uri);
client.setOptions(options);
try (StatefulRedisConnection<String, String> connection = client.connect()) {
RedisCommands<String, String> redis = connection.sync();
withRetry(4, 200, () -> redis.set(bossKey, when)); // RedisConnectionException, RedisCommandTimeoutException
}
// fast fail with a 500 ms connect timeout
RedisClient phantom = RedisClient.create(RedisURI.builder().redis("10.255.255.1", 6379).build());
phantom.setOptions(ClientOptions.builder()
.socketOptions(SocketOptions.builder().connectTimeout(Duration.ofMillis(500)).build())
.build());
phantom.connect(); // RedisConnectionException after about 500 ms
No Redis Insight¶
No Browser, filtre pelo seu prefixo: quest:boss:spawn é a STRING com o instante do próximo spawn, gravada pelo SET protegido por retry, e quest:ops:clients é o HASH que diz qual client rodou e quando. No Profiler, os dois labs mostram o PING inicial e o SET; do datacenter fantasma não chega nada, porque a falha acontece antes de existir conexão. No Workbench, CLIENT LIST durante o lab mostra as conexões do pool do Jedis (no máximo 8) ou a conexão única do Lettuce.
Por dentro
| Comando | O que faz |
|---|---|
PING |
Prova que a conexão está viva; é o que o pool do Jedis manda nas conexões ociosas com testWhileIdle |
SET quest:boss:spawn <instante> |
A escrita protegida pelo retry: idempotente, pode repetir sem efeito colateral |
HSET quest:ops:clients jedis <instante> |
Registra qual client rodou, para o verify |
CLIENT LIST |
No Workbench, mostra quantas conexões cada client abriu e há quanto tempo estão paradas |
| Ajuste | Jedis | Lettuce |
|---|---|---|
| Timeout de conexão | connectionTimeoutMillis |
SocketOptions.connectTimeout |
| Timeout de comando | socketTimeoutMillis (e blockingSocketTimeoutMillis para BLPOP e afins) |
RedisURI.setTimeout (sync) e TimeoutOptions (async e reactive) |
| Conexão morta sem tráfego | testWhileIdle + timeBetweenEvictionRuns |
KeepAliveOptions; TcpUserTimeoutOptions só com netty epoll no Linux |
| Reconexão | cada comando pega outra conexão do pool | autoReconnect(true): comandos na fila são reenviados (at-least-once) |
| O que não repetir | não envolva comandos não idempotentes em retry | replayFilter: quem casa com o predicado fica fora do reenvio |
Em produção
- Dimensione o pool pela concorrência real mais os comandos bloqueantes (lição 102-04). O plano free aceita 30 conexões, então
maxTotal8 por instância da aplicação já é generoso.maxWaitcurto: melhor um erro em 1 s do que uma fila que só cresce. - Uma falha de conexão ou timeout pode ocorrer depois de o servidor aplicar a escrita. Use retry limitado e backoff apenas se repetir a operação for seguro. No exemplo, repetir
SETcom o mesmo valor é idempotente.INCReLPUSHnão são: não aplique retry genérico. No Lettuce,replayFiltercontrola o reenvio automático; filtre todas as operações relevantes ao workload, não apenas oINCRdemonstrado. - O curso aplica
networkaddress.cache.ttl=0e a política de cache negativo no início deMain, antes de criar clients. Alterar a propriedade depois de uma resolução não limpa automaticamente entradas já armazenadas. Em produção, alinhe a política ao mecanismo de DNS e failover do serviço.
Desafio
Troque o connect timeout do fantasma para 3000 ms e rode de novo: a primeira falha deve levar cerca de 3 s. Depois aponte REDIS_URL para uma porta fechada, como redis://localhost:6399, e compare: connection refused chega na hora, porque o sistema operacional responde; o timeout só existe quando a rede engole o pacote.