Spring Boot 4 Complete Guide · Ders

Redis ve TTL'lerle Dağıtık Önbellekleme

Serileştirme denetimiyle merkezi bir önbellek arka ucu olarak Redis kullanarak önbellek durumunu örnekler arasında paylaşın.

3. ders / 413 adım

Redis ve TTL'lerle Dağıtık Önbellekleme, CoddyKit'te ücretsiz bir Spring Boot 4 Complete Guide dersidir. Bu, 4 dersinin 3. dersidir. Aşağıdan dersin tamamını ücretsiz okuyabilir, sonra tarayıcıda yerleşik kod editörü ve 7/24 yapay zeka koçu ile uygulamalı olarak pratik yapabilirsin. Bu, Spring Boot 4 Complete Guide öğrenme yolunun bir parçasıdır ve ilerlemeniz web ve CoddyKit uygulaması arasında senkronize olur. Spring Boot 4 Complete Guide kursu toplamda 4 dersten oluşur.

Bu dersin bazı bölümleri henüz çevrilmemiş olup İngilizce olarak gösterilmektedir.

Why Distributed Caching?

Caffeine is a brilliant in-process cache: ultra-fast, but each application instance keeps its own private copy. The moment you scale horizontally to several pods, those copies drift apart.

  • Instance A evicts a user; instance B still serves the stale entry.
  • A cache warm-up on one node helps nobody else.
  • Total memory cost grows linearly with replica count.

A distributed cache solves this by putting cache state in a shared backend that every instance reads and writes. In Spring Boot, Redis is the most common choice for this role.

Adding the Redis Cache Starter

Spring Boot's caching abstraction is backend-agnostic. To swap Caffeine for Redis you mostly change dependencies and a property — your @Cacheable annotations stay the same.

Pull in the Redis starter and the cache abstraction:

  • spring-boot-starter-data-redis provides the connection and RedisCacheManager.
  • spring-boot-starter-cache enables the @Cacheable/@CacheEvict annotations.

Then declare Redis as the cache type so Boot auto-configures a RedisCacheManager instead of a simple map.

<!-- pom.xml -->
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>
</dependencies>

Configuring the Connection and Cache Type

Point Boot at your Redis server and tell the cache abstraction to use Redis. With spring.cache.type=redis, Boot wires a RedisCacheManager automatically.

  • spring.data.redis.host / port configure the Lettuce client (the default driver).
  • spring.cache.redis.time-to-live sets a default TTL for every entry.
  • spring.cache.cache-names can pre-declare caches at startup.
# application.yml
spring:
  data:
    redis:
      host: localhost
      port: 6379
  cache:
    type: redis
    cache-names: products, users
    redis:
      time-to-live: 10m
      cache-null-values: false
      use-key-prefix: true

The Same @Cacheable, a Shared Backend

This is the payoff of Spring's abstraction: the service code is identical to the Caffeine version. Only the CacheManager behind it changed.

Now when instance A populates products::42, instance B reads the very same key from Redis on its next call — no duplicate computation, no drift.

@Service
public class ProductService {

    private final ProductRepository repository;

    public ProductService(ProductRepository repository) {
        this.repository = repository;
    }

    @Cacheable(cacheNames = "products", key = "#id")
    public Product findById(Long id) {
        // Runs only on a cache miss across the whole cluster
        return repository.findById(id)
                .orElseThrow(() -> new ProductNotFoundException(id));
    }

    @CacheEvict(cacheNames = "products", key = "#product.id")
    public Product update(Product product) {
        return repository.save(product);
    }
}

TTLs: Bounding Staleness

A TTL (time-to-live) is the maximum age of a cache entry before Redis evicts it automatically. TTLs are the primary defense against serving stale data in a distributed cache.

  • Short TTL (seconds) → fresher data, more backend load.
  • Long TTL (hours) → cheaper, but staleness risk grows.
  • TTL is enforced server-side by Redis, so it applies uniformly to every instance.

Unlike Caffeine's expireAfterWrite, Redis TTLs survive a single instance restart because the data lives outside the JVM.

Per-Cache TTLs with a Custom RedisCacheManager

A single global TTL rarely fits every cache. Override the auto-configuration to give each cache its own expiry by supplying a RedisCacheManagerBuilderCustomizer (or a full RedisCacheManager bean).

Here products tolerates 30 minutes of staleness while volatile prices expires after 1 minute.

@Configuration
public class CacheConfig {

    @Bean
    public RedisCacheManagerBuilderCustomizer cacheCustomizer() {
        return builder -> builder
            .withCacheConfiguration("products",
                RedisCacheConfiguration.defaultCacheConfig()
                    .entryTtl(Duration.ofMinutes(30)))
            .withCacheConfiguration("prices",
                RedisCacheConfiguration.defaultCacheConfig()
                    .entryTtl(Duration.ofMinutes(1)));
    }
}

Serialization: How Values Reach Redis

Redis stores bytes, not Java objects. Every cached value must be serialized on write and deserialized on read. The default RedisCacheManager uses Java's native serialization (JdkSerializationRedisSerializer), which has real drawbacks:

  • Values are opaque binary blobs — unreadable with redis-cli.
  • The cached class must implement Serializable.
  • Tight coupling to class internals breaks across versions.

For interoperable, human-readable entries, switch the value serializer to JSON.

Controlling Serialization with JSON

Use GenericJackson2JsonRedisSerializer for values and a plain StringRedisSerializer for keys. JSON keeps entries inspectable and decouples them from Java class internals.

The serializer embeds type metadata (@class) so polymorphic values deserialize back to the correct concrete type.

@Bean
public RedisCacheConfiguration cacheConfiguration() {
    return RedisCacheConfiguration.defaultCacheConfig()
        .entryTtl(Duration.ofMinutes(10))
        .disableCachingNullValues()
        .serializeKeysWith(
            RedisSerializationContext.SerializationPair.fromSerializer(
                new StringRedisSerializer()))
        .serializeValuesWith(
            RedisSerializationContext.SerializationPair.fromSerializer(
                new GenericJackson2JsonRedisSerializer()));
}

Key Prefixes Prevent Collisions

When several caches share one Redis database, their keys must not collide. By default Spring prefixes every entry with the cache name, so products::42 and users::42 stay separate.

  • use-key-prefix: true (default) keeps caches isolated.
  • You can supply a custom prefix, e.g. an app or tenant name, to share a Redis instance safely across services.

This matters most in multi-tenant or shared-infrastructure setups where one Redis serves many apps.

@Bean
public RedisCacheConfiguration cacheConfiguration() {
    return RedisCacheConfiguration.defaultCacheConfig()
        .entryTtl(Duration.ofMinutes(10))
        .computePrefixWith(cacheName -> "shop:" + cacheName + "::");
    // key becomes shop:products::42
}

Avoiding the Thundering Herd

A distributed cache concentrates risk: when a hot key's TTL expires, every instance misses at once and stampedes the database — the thundering herd problem.

Mitigations:

  • Stagger TTLs by adding a small random jitter so keys don't expire together.
  • Refresh entries proactively before expiry rather than lazily on miss.
  • Use a short-lived lock so only one instance recomputes a missed key while others wait.

This pure-Java helper shows how to compute a jittered TTL you would feed into entryTtl(...).

import java.time.Duration;
import java.util.concurrent.ThreadLocalRandom;

public class TtlJitter {

    static Duration withJitter(Duration base, double jitterFraction) {
        long baseMs = base.toMillis();
        long spread = (long) (baseMs * jitterFraction);
        long offset = ThreadLocalRandom.current().nextLong(-spread, spread + 1);
        return Duration.ofMillis(baseMs + offset);
    }

    public static void main(String[] args) {
        Duration base = Duration.ofMinutes(10);
        for (int i = 0; i < 3; i++) {
            Duration ttl = withJitter(base, 0.1); // +/- 10%
            System.out.println("TTL seconds: " + ttl.getSeconds());
        }
    }
}

Two-Tier Caching: Caffeine + Redis

You don't have to choose. A common production pattern is a two-tier (near) cache:

  • L1 — Caffeine in each instance, very short TTL, absorbs hot reads at nanosecond speed.
  • L2 — Redis, shared across the cluster, the source of truth for cached state.

A request checks Caffeine first; on a miss it falls back to Redis; on a Redis miss it hits the database. This cuts network round-trips while keeping the cluster consistent. Spring can compose this with a CompositeCacheManager or a dedicated near-cache library.

Quick Check: Choosing a TTL Strategy

Test your understanding of distributed cache trade-offs.

Recap: Distributed Caching with Redis

You moved from a private in-process cache to a shared, distributed one:

  • Why Redis: one cache state across all instances eliminates drift and duplicated work.
  • Drop-in swap: set spring.cache.type=redis and your @Cacheable code is unchanged.
  • TTLs: Redis enforces expiry server-side, uniformly across the cluster, surviving instance restarts; tune per cache with a RedisCacheManagerBuilderCustomizer.
  • Serialization: prefer GenericJackson2JsonRedisSerializer for readable, version-tolerant values over default JDK serialization.
  • Key prefixes isolate caches sharing one Redis; TTL jitter and two-tier Caffeine+Redis caches tame the thundering herd.
Başlamak ücretsiz

Yapay zeka eğitmeniyle Java öğren — ücretsiz

Tarayıcında gerçek kod yaz ve çalıştır, 7/24 yapay zeka eğitmeninden anında yardım al; web'de ya da uygulamada kaldığın yerden devam et.

Kurslar
21
Dersler
84

Sıkça Sorulan Sorular

“Redis ve TTL'lerle Dağıtık Önbellekleme” dersi ücretsiz mi?

Evet — “Redis ve TTL'lerle Dağıtık Önbellekleme” dersin tüm metni burada web'de ücretsiz olarak okunabilir. Etkileşimli olarak pratik yapmak (yerleşik kod editörü ve 7/24 yapay zeka koçu) ve Spring Boot 4 Complete Guide kursunun geri kalanını açmak için CoddyKit PRO'ya yükselt. Spring Boot 4 Complete Guide kursu toplamda 4 dersten oluşur.

“Redis ve TTL'lerle Dağıtık Önbellekleme” dersinde ne öğreneceğim?

Serileştirme denetimiyle merkezi bir önbellek arka ucu olarak Redis kullanarak önbellek durumunu örnekler arasında paylaşın. Spring Boot 4 Complete Guide ile uygulamalı kodu tarayıcıda doğrudan çalıştırarak pratik yaparsın ve 7/24 yapay zeka koçu dersi çalışırken sorularını yanıtlar.

Spring Boot 4 Complete Guide öğrenmeye başlamak için deneyim gerekli mi?

Önceden deneyim gerekmez. CoddyKit'te Spring Boot 4 Complete Guide, başlangıçtan ileri seviyeye kadar yapılandırıldığı için buradan başlayabilir veya başından başlayıp kendi hızında ilerleme yapabilirsin. Bu, 4 dersinin 3. dersidir.

“Redis ve TTL'lerle Dağıtık Önbellekleme” dersi ne kadar sürer?

Çoğu CoddyKit dersi yaklaşık 5–10 dakika sürer. Her biri kısa ve etkileşimli olduğu için sabit ilerleme yaparsın ve web ile uygulama arasında tam olarak bıraktığın yerden devam edebilirsin.

Bu Spring Boot 4 Complete Guide dersinde kod yazıp çalıştırabilir miyim?

Evet. Her Spring Boot 4 Complete Guide dersi yerleşik bir kod editörü içerir, bu sayede tarayıcıda gerçek kod yazıp çalıştırabilir ve anlık yapay zeka geri bildirimi alırsın — yerel kurulum gerekli değildir.

Bu kursun tüm dersleri

  1. Spring Önbellek Soyutlamasının Temelleri
  2. Caffeine Ayarlarıyla Bellek İçi Önbellekleme
  3. Redis ve TTL'lerle Dağıtık Önbellekleme
  4. Önbellek Hücumu, Geçersiz Kılma ve Tutarlılık
← Spring Boot 4 Complete Guide Sayfasına Dön