← Kursa Dön
📄 Text · 25 min

Redis ile Distributed Caching

Redis (Remote Dictionary Server), in-memory data store olarak hem cache hem message broker hem de veritabanı olarak kullanılır. Microservice mimarisinde birden fazla instance aynı cache'i paylaşabilir. Bu derste Redis'in veri tiplerini, TTL stratejilerini, caching pattern'larını, connection pooling seçeneklerini ve Spring Boot entegrasyonunu derinlemesine öğreneceğiz.

Spring Data Redis Kurulumu

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

Spring Boot Redis Auto-Configuration

Spring Boot, Redis bağlantısını otomatik yapılandırır. Tek yapmanız gereken property'leri ayarlamaktır:

# Temel bağlantı
spring.data.redis.host=localhost
spring.data.redis.port=6379
spring.data.redis.password=secretpassword
spring.data.redis.database=0

# Connection Pool (Lettuce default)
spring.data.redis.lettuce.pool.enabled=true
spring.data.redis.lettuce.pool.max-active=16
spring.data.redis.lettuce.pool.max-idle=8
spring.data.redis.lettuce.pool.min-idle=2
spring.data.redis.lettuce.pool.max-wait=2000ms

# Timeout
spring.data.redis.timeout=5000ms
spring.data.redis.connect-timeout=3000ms

# SSL (production için)
spring.data.redis.ssl.enabled=true

# Spring Cache entegrasyonu
spring.cache.type=redis
spring.cache.redis.time-to-live=1800000
spring.cache.redis.key-prefix=myapp::
spring.cache.redis.use-key-prefix=true
spring.cache.redis.cache-null-values=false

Auto-configuration ne yapar:

  1. RedisConnectionFactory bean'i oluşturur (Lettuce varsayılan)

  2. RedisTemplate<Object, Object> bean'i oluşturur

  3. StringRedisTemplate bean'i oluşturur

  4. spring.cache.type=redis ise RedisCacheManager oluşturur

Redis Veri Tipleri ve Kullanım Alanları

Redis sadece key-value store değildir. 5 temel veri tipi vardır ve her birinin farklı kullanım alanları vardır:

1. String — En temel tip

// Tek bir değer saklamak: JSON, sayı, metin
ValueOperations<String, String> ops = redisTemplate.opsForValue();

// Ürün cache'leme
ops.set("product:42", objectMapper.writeValueAsString(product),
    Duration.ofMinutes(30));

// Sayaç (atomic increment)
ops.increment("page:views:/products");     // +1
ops.increment("api:rateLimit:user:123");   // API rate limiting

Ne zaman kullanılır: Basit cache, sayaçlar, session token, rate limiter.

2. Hash — Obje alanları ayrı ayrı

// Bir objenin alanlarına ayrı ayrı erişmek
HashOperations<String, String, Object> ops = redisTemplate.opsForHash();

// Kullanıcı bilgileri (tüm alanları tek seferde)
Map<String, Object> userMap = Map.of(
    "name", "Ahmet",
    "email", "ahmet@example.com",
    "loginCount", 42
);
ops.putAll("user:123", userMap);

// Tek bir alan güncelle (tüm objeyi yeniden yazmadan!)
ops.put("user:123", "loginCount", 43);

// Tek bir alan oku
String email = (String) ops.get("user:123", "email");

Ne zaman kullanılır: Objenin bir kısmını okuyacak/güncelleyecekseniz. String'e göre avantajı: tüm objeyi deserialize etmeden tek alan okunabilir.

3. List — Sıralı koleksiyon

ListOperations<String, String> ops = redisTemplate.opsForList();

// Son aktiviteler (en yeni başta)
ops.leftPush("user:123:activities", "Ürün görüntüledi: iPhone");
ops.leftPush("user:123:activities", "Sepete ekledi: iPhone");

// Son 10 aktiviteyi getir
List<String> recentActivities = ops.range("user:123:activities", 0, 9);

// Liste boyutunu sınırla (son 100 aktivite)
ops.trim("user:123:activities", 0, 99);

Ne zaman kullanılır: Mesaj kuyruğu, son aktiviteler, timeline.

4. Set — Benzersiz elemanlar

SetOperations<String, String> ops = redisTemplate.opsForSet();

// Online kullanıcılar
ops.add("online:users", "user:123", "user:456", "user:789");
ops.remove("online:users", "user:123");

// Kaç kişi online?
Long count = ops.size("online:users");

// Kullanıcı online mı?
Boolean isOnline = ops.isMember("online:users", "user:123");

// İki kümenin kesişimi (ortak takipçiler)
Set<String> commonFollowers = ops.intersect(
    "followers:user:123", "followers:user:456");

Ne zaman kullanılır: Etiketler, online kullanıcılar, unique visitor sayma, küme işlemleri.

5. Sorted Set — Skorlu sıralama

ZSetOperations<String, String> ops = redisTemplate.opsForZSet();

// Liderlik tablosu
ops.add("leaderboard:game1", "player:ahmet", 1500);
ops.add("leaderboard:game1", "player:mehmet", 2100);
ops.add("leaderboard:game1", "player:ayse", 1800);

// Skor güncelle
ops.incrementScore("leaderboard:game1", "player:ahmet", 100);

// Top 10 oyuncu (en yüksek skor)
Set<ZSetOperations.TypedTuple<String>> top10 =
    ops.reverseRangeWithScores("leaderboard:game1", 0, 9);

// Oyuncunun sıralaması
Long rank = ops.reverseRank("leaderboard:game1", "player:ahmet");

Ne zaman kullanılır: Liderlik tablosu, trending konular, zamana dayalı sıralama.

RedisTemplate Konfigürasyonu

Default RedisTemplate JDK serialization kullanır (okunaksız binary). JSON serialization kullanmak için:

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(
            RedisConnectionFactory connectionFactory) {

        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);

        // Key'ler String olarak
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());

        // Value'lar JSON olarak (okunabilir + language-agnostic)
        GenericJackson2JsonRedisSerializer jsonSerializer =
            new GenericJackson2JsonRedisSerializer();
        template.setValueSerializer(jsonSerializer);
        template.setHashValueSerializer(jsonSerializer);

        template.afterPropertiesSet();
        return template;
    }
}

TTL Stratejileri

1. Fixed TTL — Sabit süre:

// Her entry aynı süre yaşar
redisTemplate.opsForValue().set("product:42", product,
    Duration.ofMinutes(30));

2. Sliding TTL — Her erişimde yenilenen süre:

// Her okumada TTL sıfırlanır (session benzeri)
public Object getWithSlidingTtl(String key, Duration ttl) {
    Object value = redisTemplate.opsForValue().get(key);
    if (value != null) {
        redisTemplate.expire(key, ttl);  // TTL'yi yenile
    }
    return value;
}

3. Adaptive TTL — Erişim sıklığına göre dinamik süre:

// Sık erişilen veri daha uzun yaşar
public <T> T getWithAdaptiveTtl(String key, Duration baseTtl) {
    T value = (T) redisTemplate.opsForValue().get(key);
    if (value != null) {
        // Hit count'a göre TTL uzat
        String hitKey = "hits:" + key;
        Long hits = redisTemplate.opsForValue().increment(hitKey);

        // Her 10 hit'te TTL'yi 2x uzat (max 4x)
        long multiplier = Math.min(1 + hits / 10, 4);
        redisTemplate.expire(key, baseTtl.multipliedBy(multiplier));
    }
    return value;
}

Caching Pattern'ları (Redis ile)

Cache-Aside (en yaygın):

@Service
@RequiredArgsConstructor
public class ProductCacheService {
    private final RedisTemplate<String, Object> redisTemplate;
    private final ProductRepository repository;
    private static final Duration TTL = Duration.ofMinutes(30);

    public Product getProduct(Long id) {
        String key = "product:" + id;

        // 1. Redis'e bak
        Product cached = (Product) redisTemplate.opsForValue().get(key);
        if (cached != null) return cached;

        // 2. DB'den oku
        Product product = repository.findById(id).orElseThrow();

        // 3. Redis'e yaz
        redisTemplate.opsForValue().set(key, product, TTL);
        return product;
    }
}

Write-Through:

public Product saveProduct(Product product) {
    // 1. DB'ye yaz
    Product saved = repository.save(product);
    // 2. Redis'e yaz (senkron)
    redisTemplate.opsForValue().set("product:" + saved.getId(), saved, TTL);
    return saved;
}

Write-Behind (Async):

public Product saveProductAsync(Product product) {
    // 1. Redis'e hemen yaz (hızlı)
    redisTemplate.opsForValue().set("product:" + product.getId(), product, TTL);

    // 2. DB'ye async yaz
    CompletableFuture.runAsync(() -> repository.save(product));
    return product;
}

Lettuce vs Jedis — Connection Client Seçimi

Spring Boot 3 varsayılan olarak Lettuce kullanır:

ÖzellikLettuceJedis
ThreadingNon-blocking, Netty tabanlıBlocking, thread-per-connection
Thread SafetyThread-safe (tek connection paylaşılır)Thread-safe değil (pool gerekli)
ReactiveDestekler (WebFlux ile uyumlu)Desteklemez
Connection Poolİsteğe bağlı (genellikle gerekmez)Zorunlu
PerformansYüksek concurrency'de daha iyiDüşük concurrency'de benzer
ClusterTam destekTam destek
DefaultSpring Boot 3 defaultManuel eklenmeli
<!-- Jedis kullanmak isterseniz: -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <exclusions>
        <exclusion>
            <groupId>io.lettuce</groupId>
            <artifactId>lettuce-core</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
</dependency>

Tavsiye: Lettuce'i kullanın. Reactive desteği, daha iyi thread safety ve Spring Boot default olması büyük avantaj.

Redis Sentinel ve Cluster

Redis Sentinel — High Availability (HA): Master çökerse otomatik failover sağlar.

# Sentinel konfigürasyonu
spring.data.redis.sentinel.master=mymaster
spring.data.redis.sentinel.nodes=sentinel1:26379,sentinel2:26379,sentinel3:26379
spring.data.redis.sentinel.password=sentinelpass
spring.data.redis.password=redispass
Sentinel yapısı:
┌─────────┐     ┌─────────┐     ┌─────────┐
│Sentinel1│     │Sentinel2│     │Sentinel3│
└────┬────┘     └────┬────┘     └────┬────┘
     │               │               │
     └───────┬───────┴───────┬───────┘
             │               │
        ┌────┴────┐    ┌─────┴────┐
        │  Master │───→│ Replica  │
        └─────────┘    └──────────┘

Master çökerse → Sentinel'ler Replica'yı Master'a promote eder

Redis Cluster — Horizontal Scaling: Veriyi birden fazla node'a dağıtır (sharding).

# Cluster konfigürasyonu
spring.data.redis.cluster.nodes=redis1:6379,redis2:6379,redis3:6379,redis4:6379,redis5:6379,redis6:6379
spring.data.redis.cluster.max-redirects=3
Cluster yapısı (6 node, 3 master + 3 replica):
┌──────────┐  ┌──────────┐  ┌──────────┐
│ Master 1 │  │ Master 2 │  │ Master 3 │
│ Slot 0-  │  │ Slot     │  │ Slot     │
│ 5460     │  │ 5461-    │  │ 10923-   │
│          │  │ 10922    │  │ 16383    │
└────┬─────┘  └────┬─────┘  └────┬─────┘
     │             │             │
┌────┴─────┐  ┌────┴─────┐  ┌────┴─────┐
│Replica 1 │  │Replica 2 │  │Replica 3 │
└──────────┘  └──────────┘  └──────────┘
ÖzellikSentinelCluster
AmaçHigh AvailabilityHigh Availability + Scaling
ShardingYok (tek master)Var (16384 hash slot)
Max veriTek node RAM'iToplam cluster RAM'i
KullanımKüçük-orta veri setiBüyük veri seti, yüksek throughput

@RedisHash — Redis'te Entity Saklama

@RedisHash(value = "sessions", timeToLive = 3600)
public class UserSession {
    @Id
    private String id;
    private String username;
    private String ipAddress;
    private LocalDateTime loginTime;

    @TimeToLive   // Dinamik TTL
    public long getTimeToLive() {
        // VIP kullanıcılara daha uzun session
        return isVip() ? 7200 : 3600;
    }
}

public interface UserSessionRepository extends CrudRepository<UserSession, String> {
    // Spring Data Redis otomatik implementasyon sağlar
}

Yaygın Hatalar

  • `keys *` pattern kullanmak: Production'da Redis'i bloklar (O(N)). SCAN komutunu kullanın.

  • Büyük value'lar saklamak: 1MB+ value'lar network ve serialization maliyeti artırır. Value'ları 100KB altında tutun.

  • TTL koymamak: Redis belleği dolar, eviction başlar ve beklenmedik veri kaybı olur.

  • JDK serialization kullanmak: Debug edilemez, Java dışı okuyamaz. JSON serializer kullanın.

  • Connection pool'suz Jedis: Thread safety sorunu. Lettuce kullanın veya pool yapılandırın.

// ❌ YANLIŞ: keys * kullanmak
Set<String> allKeys = redisTemplate.keys("product:*");  // Production'da KULLANMAYIN!

// ✅ DOĞRU: SCAN kullanmak
ScanOptions options = ScanOptions.scanOptions().match("product:*").count(100).build();
try (Cursor<byte[]> cursor = redisTemplate.getConnectionFactory()
        .getConnection().scan(options)) {
    while (cursor.hasNext()) {
        String key = new String(cursor.next());
        // key'i işle
    }
}

💡 Özet: Redis, distributed cache için standart çözümdür. 5 veri tipi farklı senaryolara uygundur. Lettuce (default) non-blocking ve thread-safe'tir. Sentinel HA, Cluster scaling sağlar. TTL stratejinizi verinin doğasına göre belirleyin. JSON serializer kullanın.