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=falseAuto-configuration ne yapar:
RedisConnectionFactorybean'i oluşturur (Lettuce varsayılan)RedisTemplate<Object, Object>bean'i oluştururStringRedisTemplatebean'i oluştururspring.cache.type=redisiseRedisCacheManageroluş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 limitingNe 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:
| Özellik | Lettuce | Jedis |
|---|---|---|
| Threading | Non-blocking, Netty tabanlı | Blocking, thread-per-connection |
| Thread Safety | Thread-safe (tek connection paylaşılır) | Thread-safe değil (pool gerekli) |
| Reactive | Destekler (WebFlux ile uyumlu) | Desteklemez |
| Connection Pool | İsteğe bağlı (genellikle gerekmez) | Zorunlu |
| Performans | Yüksek concurrency'de daha iyi | Düşük concurrency'de benzer |
| Cluster | Tam destek | Tam destek |
| Default | Spring Boot 3 default | Manuel 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=redispassSentinel yapısı:
┌─────────┐ ┌─────────┐ ┌─────────┐
│Sentinel1│ │Sentinel2│ │Sentinel3│
└────┬────┘ └────┬────┘ └────┬────┘
│ │ │
└───────┬───────┴───────┬───────┘
│ │
┌────┴────┐ ┌─────┴────┐
│ Master │───→│ Replica │
└─────────┘ └──────────┘
Master çökerse → Sentinel'ler Replica'yı Master'a promote ederRedis 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=3Cluster 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 │
└──────────┘ └──────────┘ └──────────┘| Özellik | Sentinel | Cluster |
|---|---|---|
| Amaç | High Availability | High Availability + Scaling |
| Sharding | Yok (tek master) | Var (16384 hash slot) |
| Max veri | Tek node RAM'i | Toplam cluster RAM'i |
| Kullanım | Küçük-orta veri seti | Bü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)).
SCANkomutunu 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.
AI Asistan
Sorularını yanıtlamaya hazır