Um único cache Redis compartilhado por todas as réplicas: o banco só responde em caso de miss
O gateway roda com 3 réplicas, cada JVM com o seu próprio cache local. A réplica 1 guarda a conta 42 em cache. A próxima requisição para a conta 42 cai na réplica 2. O que acontece?
GET account:42
nil
SELECT * FROM accounts WHERE id = 42
SET account:42 ... EX 300
O banco de dados continua sendo a fonte da verdade; o cache só economiza idas e voltas.
Remote Dictionary Server: cache, armazenamento de sessões, message broker — o cache distribuído padrão de fato.
SET
GET
INCR
EXPIRE
HSET
HGET
HGETALL
LPUSH
RPOP
LRANGE
SADD
SMEMBERS
SISMEMBER
ZADD
ZRANGE
ZRANK
XADD
XREAD
XACK
Diferentemente do Memcached (só strings), o Redis tem estruturas de dados nativas com operações O(1) ou O(log n).
Este hands-on habilita o AOF: --appendonly yes.
--appendonly yes
internet → :80 Nginx → gateway ×3 → auth, account → Redis :6379 → PostgreSQL (só em caso de miss)
:80
auth
account
:6379
redis: image: redis:7-alpine hostname: redis ports: - 6379:6379 volumes: - redis_data:/data command: redis-server --appendonly yes volumes: redis_data:
account: # ... build, DATABASE_* ... environment: REDIS_HOST: redis REDIS_PORT: 6379 deploy: replicas: 2 depends_on: - db - redis
As mesmas REDIS_HOST / REDIS_PORT em todo serviço que usa o cache (por exemplo, auth).
REDIS_HOST
REDIS_PORT
spring-boot-starter-data-redis
spring-boot-starter-cache
@Cacheable
@CachePut
@CacheEvict
@EnableCaching
spring: data: redis: host: ${REDIS_HOST:localhost} port: ${REDIS_PORT:6379} cache: type: redis redis: time-to-live: 300000 cache-null-values: false
type: redis substitui o ConcurrentMapCache padrão, em memória; TTL de 5 minutos; null não vai para o cache.
type: redis
ConcurrentMapCache
null
@Cacheable(value = "accounts", key = "#id") public AccountOut findById(String id) { return accountRepository.findById(id) .map(AccountOut::from) .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); }
id = "42"
accounts::42
As anotações vão nos métodos da camada de serviço, não nos controllers nem nos repositories.
@CachePut(value = "accounts", key = "#id") public AccountOut update(String id, AccountIn dto)
@CacheEvict(value = "accounts", key = "#id") public void delete(String id)
@CacheEvict(value = "accounts", allEntries = true) limpa o cache inteiro (atualizações em massa).
@CacheEvict(value = "accounts", allEntries = true)
A serialização Java padrão grava binário ilegível. Um bean RedisConfig troca para JSON:
RedisConfig
@Bean public RedisCacheConfiguration cacheConfiguration(ObjectMapper objectMapper) { return RedisCacheConfiguration.defaultCacheConfig() .serializeValuesWith(RedisSerializationContext.SerializationPair .fromSerializer(new GenericJackson2JsonRedisSerializer(objectMapper))) .entryTtl(Duration.ofMinutes(5)) .disableCachingNullValues(); }
Classes em cache (AccountOut) devem ser serializáveis em JSON: construtor sem argumentos, campos públicos ou getters, @JsonIgnoreProperties(ignoreUnknown = true).
AccountOut
@JsonIgnoreProperties(ignoreUnknown = true)
docker compose up -d --build curl -s http://localhost:8080/accounts/42 | jq # 1st: miss, hits the database docker compose exec redis redis-cli > KEYS accounts::* # 1) "accounts::42" > GET "accounts::42" # {"id":"42","name":"Ada Lovelace",...} curl -s http://localhost:8080/accounts/42 | jq # 2nd: hit docker compose logs account --tail 20 # no database query this time
curl -o /dev/null -s -w "Cache miss: %{time_total}s\n" http://localhost:8080/accounts/42 curl -o /dev/null -s -w "Cache hit: %{time_total}s\n" http://localhost:8080/accounts/42
TTL "accounts::42"
redis-cli INFO stats
keyspace_hits
keyspace_misses
Taxa de miss alta? Aumente o TTL ou pré-aqueça o cache na inicialização.
Um cache pode servir dados desatualizados: @CachePut / @CacheEvict nas escritas e um TTL o mantêm eventualmente consistente.
redis-cli
MONITOR
6379
KEYS
TTL
INFO stats
Roteiro: (1) por que um cache compartilhado quando o gateway escala; (2) bancos em memória e cache-aside; (3) estruturas de dados e persistência do Redis; (4) compose; (5) anotações do Spring Cache; (6) executar, medir.