Redis ile Senior Seviye Mülakat Soruları

50 adet ileri seviye Redis sorusu ve cevapla hazırlanmış kapsamlı arşiv

Redis Temelleri

Redis'in temel özellikleri ve veri yapıları

Spring Boot & Redis

Spring Boot ile Redis entegrasyonu

Redis Caching

Redis ile caching stratejileri

Redis Performans

Redis performans optimizasyonu

Redis Cluster

Redis cluster ve yüksek erişilebilirlik

Redis Güvenlik

Redis güvenlik ve en iyi uygulamalar

Redis Temelleri

Redis'in temel özellikleri ve veri yapıları ile ilgili mülakat soruları.

❓ Soru 1: Redis nedir ve ne için kullanılır?

Cevap: Redis, açık kaynaklı, bellek içi (in-memory) veri yapısı deposudur. Verileri anahtar-değer (key-value) çiftleri olarak saklar ve çeşitli veri yapılarını destekler. Genellikle veritabanı, önbellek (cache), mesaj kuyruğu ve broker olarak kullanılır.

Özellikleri:

  • Çok yüksek performanslı (1M+ saniyede işlem)
  • Verileri bellekte saklar
  • Dayanıklılık için diske yazabilir
  • Atomik işlemler destekler
  • Çeşitli veri yapıları (string, hash, list, set, sorted set) destekler

❓ Soru 2: Redis hangi veri yapılarını destekler?

Cevap: Redis 5 ana veri yapısını destekler:

  • Strings: Metinler, sayılar, binary veriler
  • Hashes: Alan-değer çiftleri koleksiyonu
  • Lists: Sıralı değerler koleksiyonu
  • Sets: Sırasız benzersiz değerler koleksiyonu
  • Sorted Sets (ZSets): Sıralı benzersiz değerler koleksiyonu

Örnek kullanımlar:

# String
SET user:1:name "Ahmet"
GET user:1:name

# Hash
HSET user:1 name "Ahmet" age 30
HGET user:1 name

# List
LPUSH queue:jobs "job1"
RPUSH queue:jobs "job2"
LPOP queue:jobs

# Set
SADD user:1:followers "user2" "user3"
SMEMBERS user:1:followers

# Sorted Set
ZADD leaderboard 100 "player1" 200 "player2"
ZRANGE leaderboard 0 -1

❓ Soru 3: Redis'in RDB ve AOF dayanıklılık mekanizmaları arasındaki fark nedir?

Cevap: Redis verilerin kaybolmaması için iki farklı dayanıklılık mekanizması sunar:

  • RDB (Redis Database File): Belirli zaman aralıklarında veri setlerinin anlık görüntüsünü alır ve diske yazar. Daha hızlı başlangıç süresi ve daha küçük dosya boyutu sağlar. Ancak son anlık görüntüden sonraki veri kayıpları olabilir.
  • AOF (Append Only File): Her yazma işlemini log dosyasına ekler. Veri kaybı riski daha düşüktür ancak dosya boyutu büyüktür ve başlangıç süresi daha uzundur.

Yapılandırma örnekleri:

# redis.conf
# RDB ayarları
save 900 1      # 900 saniye içinde en az 1 değişiklik olursa kaydet
save 300 10     # 300 saniye içinde en az 10 değişiklik olursa kaydet
save 60 10000   # 60 saniye içinde en az 10000 değişiklik olursa kaydet

# AOF ayarları
appendonly yes  # AOF'yi etkinleştir
appendfsync everysec  # Her saniye diske yaz

❓ Soru 4: Redis'te pub/sub nasıl çalışır?

Cevap: Redis pub/sub, mesajlaşma modelinde yayıncı (publisher) ve abone (subscriber) arasında bir iletişim mekanizmasıdır. Yayıncılar belirli kanallara mesaj gönderir, aboneler ise bu kanalları dinler.

Örnek:

# Terminal 1: Kanala abone ol
SUBSCRIBE notifications

# Terminal 2: Kanala mesaj gönder
PUBLISH notifications "Yeni bildirim: Siparişiniz hazır"

# Terminal 3: Pattern ile abone ol
PSUBSCRIBE notification.*

Bu örnekte, "notifications" kanalına gönderilen mesajlar bu kanala abone olan tüm istemcilere iletilir. PSUBSCRIBE ile belirli bir desene uyan kanallara abone olunabilir.

❓ Soru 5: Redis'te transaction'lar nasıl çalışır?

Cevap: Redis transaction'ları MULTI, EXEC, DISCARD ve WATCH komutları ile yönetilir. Transaction'lar atomik bir birim olarak çalışır: ya tüm komutlar uygulanır ya da hiçbiri uygulanmaz.

Örnek:

# Transaction başlat
MULTI

# Komutları sırayla ekle
SET user:1:name "Ahmet"
SET user:1:age 30
INCR counter:users

# Transaction'ı uygula
EXEC

# Veya iptal et
DISCARD

WATCH komutu, belirli bir anahtarın değişiklik izlenmesini sağlar. Eğer transaction çalışırken izlenen anahtar değişirse, transaction başarısız olur.

❓ Soru 6: Redis'te TTL (Time To Live) nedir ve nasıl kullanılır?

Cevap: TTL, bir anahtarın ne kadar süre sonra otomatik olarak silineceğini belirten saniye cinsinden bir değerdir. Önbellek verilerinin otomatik olarak temizlenmesi için kullanılır.

Örnek:

# Anahtara 60 saniyelik TTL ekle
SET session:abc123 "user_data"
EXPIRE session:abc123 60

# Veya SET ile birlikte TTL belirle
SET session:abc123 "user_data" EX 60

# TTL'yi kontrol et
TTL session:abc123

# TTL'yi kaldır
PERSIST session:abc123

TTL, önbellek yönetimi, oturum yönetimi ve geçici veri depolama için önemlidir. TTL'si biten anahtarlar Redis tarafından otomatik olarak silinir.

❓ Soru 7: Redis'te pipelining nedir ve avantajları nelerdir?

Cevap: Pipelining, birden fazla komutu tek bir istekte göndermek ve tüm yanıtları tek bir seferde almak için kullanılan bir tekniktir. Bu, ağ gecikmesini azaltır ve performansı artırır.

Örnek:

// Java ile Redis pipelining örneği
Jedis jedis = new Jedis("localhost");
Pipeline pipeline = jedis.pipelined();

// Komutları sırayla ekle
pipeline.set("key1", "value1");
pipeline.get("key1");
pipeline.incr("counter");

// Tüm komutları gönder ve yanıtları al
Response<String> response1 = (Response<String>) pipeline.get("key1");
Response<Long> response2 = (Response<Long>) pipeline.incr("counter");

pipeline.sync();

// Yanıtları kullan
String value = response1.get();
long count = response2.get();

Pipelining, özellikle çok sayıda komutun bir arada gönderilmesi gereken durumlarda (örneğin, toplu veri işleme) performansı önemli ölçüde artırır.

❓ Soru 8: Redis'te Lua scripting ne işe yarar?

Cevap: Redis'te Lua scripting, sunucu tarafında karmaşık işlemleri atomik olarak çalıştırmak için kullanılır. EVAL ve EVALSHA komutları ile Lua betikleri çalıştırılabilir.

Örnek:

 tonumber(ARGV[2]) then
    return 0  -- Limit aşıldı
else
    redis.call('SET', KEYS[1], current)
    return 1  -- Başarılı
end
-- Betiği çalıştır
EVAL "local current = redis.call('GET', KEYS[1])..." 1 counter 5 10

Lua scripting, birden fazla Redis komutunun atomik olarak çalıştırılması gerektiğinde (örneğin, banka hesap transferi) çok kullanışlıdır.

❓ Soru 9: Redis'te bitmap nedir ve nasıl kullanılır?

Cevap: Bitmap, string veri yapısının özel bir kullanımıdır. Her bit 0 veya 1 değerini alabilir ve genellikle varlık-yokluk durumlarını temsil etmek için kullanılır. Bellek verimliliği sağlar ve bit düzeyinde işlemlere izin verir.

Örnek:

-- Bitmap oluşturma ve bit ayarlama
SETBIT user:100:notifications 1 1  -- 1. gün bildirim açıldı
SETBIT user:100:notifications 7 1  -- 7. gün bildirim açıldı

-- Bit değerini okuma
GETBIT user:100:notifications 1  -- 1 döner (bildirim açık)

-- Belirli bir aralıktaki 1'leri sayma
BITCOUNT user:100:notifications

-- İki bitmap arasındaki farkı bulma
BITOP DIFFERENCE result user:100:notifications user:101:notifications

Bitmap'ler genellikle kullanıcı aktivitelerini takip etmek, özellik bayraklarını saklamak veya istatistiksel verileri depolamak için kullanılır.

❓ Soru 10: Redis'te HyperLogLog nedir ve hangi durumlarda kullanılır?

Cevap: HyperLogLog, bir kümenin kardinalitesini (benzersiz eleman sayısını) tahmin etmek için kullanılan bir probalistik veri yapısıdır. Bellek kullanımını optimize eder ve büyük veri setleri için yaklaşık sonuçlar sunar.

Örnek:

-- HyperLogLog'a eleman ekleme
PFADD visitors:2023-05-15 "user1" "user2" "user3"

-- Benzersiz ziyaretçi sayısını tahmin etme
PFCOUNT visitors:2023-05-15

-- İki HyperLogLog birleştirme
PFMERGE visitors:2023-05-15:total visitors:2023-05-15:day1 visitors:2023-05-15:day2

HyperLogLog, benzersiz sayım işlemlerinde (örneğin, web sitesine gelen benzersiz ziyaretçi sayısı) kullanılır. Bellek kullanımı küçük olsa da (12 KB) milyarlarca elemanı işleyebilir. %0.81 hata payı ile çalışır.

Spring Boot & Redis

Spring Boot ile Redis entegrasyonu ve kullanımı ile ilgili mülakat soruları.

❓ Soru 1: Spring Boot uygulamasına Redis nasıl entegre edilir?

Cevap: Spring Boot uygulamasına Redis entegre etmek için aşağıdaki adımlar izlenir:

  1. Redis bağımlılığını pom.xml'e ekle
  2. application.properties veya application.yml'de Redis bağlantı bilgilerini yapılandır
  3. RedisTemplate veya StringRedisTemplate kullanarak Redis işlemlerini gerçekleştir

pom.xml bağımlılığı:

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

application.yml yapılandırması:

spring:
  redis:
    host: localhost
    port: 6379
    password: 
    database: 0
    timeout: 2000ms
    lettuce:
      pool:
        max-active: 8
        max-idle: 8
        min-idle: 0
        max-wait: -1ms

❓ Soru 2: Spring Boot'ta RedisTemplate ve StringRedisTemplate arasındaki fark nedir?

Cevap: İkisi de Redis ile etkileşim kurmak için kullanılır, ancak farklı senaryolar için optimize edilmiştir:

  • RedisTemplate: Genel amaçlı bir şablondur. Herhangi bir Java nesnesini serileştirip Redis'e kaydedebilir. Varsayılan olarak JDK serileştirmesini kullanır.
  • StringRedisTemplate: Özellikle string anahtarlar ve değerler için optimize edilmiştir. StringOperations, ListOperations, SetOperations, HashOperations ve ZSetOperations gibi operasyonları doğrudan sağlar.

Örnek kullanımlar:

@Autowired
private RedisTemplate<String, Object> redisTemplate;

@Autowired
private StringRedisTemplate stringRedisTemplate;

public void saveUser(User user) {
    // RedisTemplate ile nesne kaydetme
    redisTemplate.opsForValue().set("user:" + user.getId(), user);
}

public void saveString(String key, String value) {
    // StringRedisTemplate ile string kaydetme
    stringRedisTemplate.opsForValue().set(key, value);
}

❓ Soru 3: Spring Boot'ta Redis için özel serileştirme nasıl yapılandırılır?

Cevap: Varsayılan JDK serileştirmesi yerine JSON serileştirme gibi daha verimli yöntemler kullanılabilir. Bu, hem bellek kullanımını optimize eder hem de farklı platformlar arasında uyumluluk sağlar.

Örnek yapılandırma:

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
        
        // JSON serileştirici kullan
        template.setDefaultSerializer(new GenericJackson2JsonRedisSerializer());
        
        // Anahtarlar için String serileştirici
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        
        template.afterPropertiesSet();
        return template;
    }
}

Bu yapılandırmada, Jackson kütüphanesi kullanılarak nesneler JSON formatına dönüştürülür. Bu, hem insan tarafından okunabilir hem de daha verimli bir serileştirme sağlar.

❓ Soru 4: Spring Boot'ta @Cacheable, @CachePut ve @CacheEvict anotasyonları nasıl kullanılır?

Cevap: Spring'in caching anotasyonları, metod seviyesinde önbellek işlemlerini kolaylaştırır. Redis bu önbellek işlemleri için backend olarak kullanılabilir.

  • @Cacheable: Metodu çalıştırmadan önce önbellekte sonuç varsa onu döndürür, yoksa metodu çalıştırır ve sonucu önbelleğe kaydeder.
  • @CachePut: Metodu her zaman çalıştırır ve sonucu önbelleğe kaydeder.
  • @CacheEvict: Önbellekten belirli bir veya tüm girdileri siler.

Örnek kullanımlar:

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    // Önbellekte varsa getir, yoksa veritabanından al ve önbelleğe kaydet
    @Cacheable(value = "users", key = "#id")
    public User getUserById(Long id) {
        return userRepository.findById(id).orElse(null);
    }

    // Metodu çalıştır ve sonucu önbelleğe kaydet
    @CachePut(value = "users", key = "#user.id")
    public User updateUser(User user) {
        return userRepository.save(user);
    }

    // Önbellekten belirli bir kullanıcıyı sil
    @CacheEvict(value = "users", key = "#id")
    public void deleteUser(Long id) {
        userRepository.deleteById(id);
    }

    // Önbellekteki tüm kullanıcıları sil
    @CacheEvict(value = "users", allEntries = true)
    public void clearUserCache() {
        // Önbelleği temizle
    }
}

❓ Soru 5: Spring Boot'ta Redis ile mesajlaşma (pub/sub) nasıl yapılır?

Cevap: Spring Data Redis, Redis pub/sub mekanizmasını kullanarak mesajlaşma işlemlerini destekler. Bu, uygulamalar arasında asenkron iletişim sağlar.

Örnek yapılandırma ve kullanım:

@Configuration
@EnableRedisMessageListener
public class RedisConfig {

    @Bean
    public RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,
                                                MessageListenerAdapter listenerAdapter) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        container.addMessageListener(listenerAdapter, new PatternTopic("notifications.*"));
        return container;
    }

    @Bean
    public MessageListenerAdapter listenerAdapter(Receiver receiver) {
        return new MessageListenerAdapter(receiver, "receiveMessage");
    }

    @Bean
    public Receiver receiver() {
        return new Receiver();
    }

    @Bean
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory connectionFactory) {
        return new StringRedisTemplate(connectionFactory);
    }
}

public class Receiver {
    public void receiveMessage(String message) {
        System.out.println("Mesaj alındı: " + message);
    }
}

@Service
public class PublisherService {

    @Autowired
    private StringRedisTemplate redisTemplate;

    public void publishMessage(String channel, String message) {
        redisTemplate.convertAndSend(channel, message);
    }
}

Bu örnekte, "notifications.*" kanalına gönderilen tüm mesajlar Receiver sınıfı tarafından dinlenir. PublisherService ise belirli kanallara mesaj göndermek için kullanılır.

❓ Soru 6: Spring Boot'ta Redis ile oturum yönetimi nasıl yapılır?

Cevap: Spring Session, HTTP oturum verilerini Redis'te saklamak için kullanılır. Bu, birden fazla uygulama örneği arasında oturum paylaşımını sağlar ve ölçeklenebilirliği artırır.

Yapılandırma adımları:

  1. Gerekli bağımlılıkları ekle
  2. Redis bağlantı bilgilerini yapılandır
  3. Oturum yönetimini etkinleştir

pom.xml bağımlılıkları:

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

application.yml yapılandırması:

spring:
  redis:
    host: localhost
    port: 6379
  session:
    store-type: redis
    timeout: 1800  # 30 dakika (saniye cinsinden)
    redis:
      namespace: myapp:sessions

Bu yapılandırma ile HTTP oturumları Redis'te saklanır ve birden fazla uygulama örneği arasında paylaşılabilir. Kullanıcı bir örnekte oturum açtığında, diğer örnekler de bu oturumu tanır.

❓ Soru 7: Spring Boot'ta Redis ile distributed locking nasıl yapılır?

Cevap: Dağıtık sistemlerde kaynaklara aynı anda erişimi önlemek için distributed locking kullanılır. Spring Integration Redis veya RedisTemplate ile bu işlevsellik sağlanabilir.

Örnek bir distributed lock sınıfı:

@Service
public class RedisLockService {

    @Autowired
    private StringRedisTemplate redisTemplate;

    private static final String LOCK_PREFIX = "lock:";
    private static final long LOCK_EXPIRE = 30; // 30 saniye

    public boolean acquireLock(String lockKey) {
        String key = LOCK_PREFIX + lockKey;
        Boolean acquired = redisTemplate.opsForValue().setIfAbsent(key, "locked", LOCK_EXPIRE, TimeUnit.SECONDS);
        return acquired != null && acquired;
    }

    public void releaseLock(String lockKey) {
        String key = LOCK_PREFIX + lockKey;
        redisTemplate.delete(key);
    }

    public boolean isLocked(String lockKey) {
        String key = LOCK_PREFIX + lockKey;
        return Boolean.TRUE.equals(redisTemplate.hasKey(key));
    }
}

Kullanım örneği:

@Service
public class OrderService {

    @Autowired
    private RedisLockService lockService;

    public void processOrder(Long orderId) {
        String lockKey = "order:" + orderId;
        
        try {
            // Lock al
            if (lockService.acquireLock(lockKey)) {
                // Sipariş işleme
                System.out.println("Sipariş işleniyor: " + orderId);
                
                // İşlem tamamlandığında lock'u serbest bırak
                lockService.releaseLock(lockKey);
            } else {
                System.out.println("Sipariş şu anda işleniyor: " + orderId);
            }
        } catch (Exception e) {
            // Hata durumunda lock'u serbest bırak
            lockService.releaseLock(lockKey);
            throw e;
        }
    }
}

Bu örnekte, her sipariş işlemi için bir lock alınır. Bu, aynı siparişin aynı anda birden fazla kez işlenmesini önler.

❓ Soru 8: Spring Boot'ta Redis ile rate limiting nasıl yapılır?

Cevap: Rate limiting, belirli bir zaman diliminde bir kaynağın veya servisin kullanımını sınırlamak için kullanılır. Redis, bu işlev için hızlı ve verimli bir çözüm sunar.

Örnek bir rate limiting sınıfı:

@Service
public class RateLimiterService {

    @Autowired
    private StringRedisTemplate redisTemplate;

    public boolean isAllowed(String key, int limit, int period) {
        // Zaman damgası ile birlikte anahtar oluştur
        String currentKey = key + ":" + System.currentTimeMillis() / (period * 1000);
        
        // Mevcut sayıyı al veya 0 olarak ayarla
        Long current = redisTemplate.opsForValue().increment(currentKey);
        
        // İlk artırma ise TTL ayarla
        if (current != null && current == 1) {
            redisTemplate.expire(currentKey, period, TimeUnit.SECONDS);
        }
        
        // Limiti aştı mı kontrol et
        return current != null && current <= limit;
    }
}

Kullanım örneği:

@RestController
@RequestMapping("/api")
public class ApiController {

    @Autowired
    private RateLimiterService rateLimiterService;

    @GetMapping("/data")
    public ResponseEntity<String> getData(HttpServletRequest request) {
        String clientIp = request.getRemoteAddr();
        
        // Dakikada 10 istek limiti
        if (!rateLimiterService.isAllowed(clientIp, 10, 60)) {
            return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS)
                                .body("İstek limiti aşıldı");
        }
        
        return ResponseEntity.ok("İstek başarılı");
    }
}

Bu örnekte, her IP adresi için dakikada 10 istek limiti belirlenmiştir. Bu limit aşıldığında, 429 Too Many Requests yanıtı döndürülür.

❓ Soru 9: Spring Boot'ta Redis için connection pooling nasıl yapılandırılır?

Cevap: Redis ile etkili bir şekilde çalışmak için connection pooling kullanmak önemlidir. Spring Boot, varsayılan olarak Lettuce client'ını kullanır ve connection pooling için yapılandırma sağlar.

Örnek yapılandırma:

spring:
  redis:
    host: localhost
    port: 6379
    password: 
    database: 0
    timeout: 2000ms
    lettuce:
      pool:
        max-active: 100   # Maksimum aktif bağlantı sayısı
        max-idle: 50      # Boştaki maksimum bağlantı sayısı
        min-idle: 10      # Boştaki minimum bağlantı sayısı
        max-wait: 5000ms  # Bağlantı havuzunda bağlantı yoksa bekleme süresi

Özel bir RedisClientConfiguration sınıfı ile daha detaylı yapılandırma:

@Configuration
public class RedisConfig {

    @Bean
    public LettuceConnectionFactory redisConnectionFactory() {
        RedisStandaloneConfiguration redisConfig = new RedisStandaloneConfiguration();
        redisConfig.setHostName("localhost");
        redisConfig.setPort(6379);
        
        LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
                .clientOptions(ClientOptions.builder()
                        .autoReconnect(true)
                        .build())
                .build();
        
        return new LettuceConnectionFactory(redisConfig, clientConfig);
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        return template;
    }
}

Bu yapılandırmalar, Redis ile daha verimli ve güvenilir bir bağlantı yönetimi sağlar.

❓ Soru 10: Spring Boot'ta Redis health check nasıl yapılır?

Cevap: Spring Boot Actuator, Redis bağlantısının sağlık durumunu kontrol etmek için health endpoint'leri sağlar. Bu, Redis sunucusunun çalışıp çalışmadığını ve uygulamanın Redis'e bağlanıp bağlanamadığını kontrol etmemizi sağlar.

Öncelikle gerekli bağımlılığı ekle:

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

application.yml'de health endpoint'ini yapılandır:

management:
  endpoints:
    web:
      exposure:
        include: health,info
  endpoint:
    health:
      show-details: always

Özel bir Redis health indicator oluşturmak için:

@Component
public class CustomRedisHealthIndicator implements HealthIndicator {

    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    @Override
    public Health health() {
        try {
            RedisConnection connection = redisConnectionFactory.getConnection();
            connection.ping();
            connection.close();
            return Health.up()
                    .withDetail("message", "Redis connection successful")
                    .build();
        } catch (Exception e) {
            return Health.down()
                    .withDetail("error", e.getMessage())
                    .build();
        }
    }
}

Bu yapılandırmadan sonra, /actuator/health endpoint'ine yapılan istek, Redis bağlantısının sağlık durumunu içerecektir. Bu, monitoring sistemlerinde ve container health check'lerinde kullanılabilir.

Redis Caching

Redis ile caching stratejileri ve en iyi uygulamalar ile ilgili mülakat soruları.

❓ Soru 1: Cache-Aside (Look Aside) pattern nedir ve Redis ile nasıl uygulanır?

Cevap: Cache-Aside pattern, en yaygın caching stratejilerinden biridir. Uygulama öncelikle veriyi önbellekte arar, bulamazsa veritabanından çeker ve önbelleğe ekler.

Redis ile Cache-Aside pattern uygulaması:

@Service
public class ProductService {

    @Autowired
    private ProductRepository productRepository;
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    private static final String PRODUCT_CACHE_PREFIX = "product:";

    public Product getProductById(Long id) {
        String cacheKey = PRODUCT_CACHE_PREFIX + id;
        
        // 1. Önbellekte ara
        Product product = (Product) redisTemplate.opsForValue().get(cacheKey);
        
        if (product == null) {
            // 2. Önbellekte yoksa veritabanından al
            product = productRepository.findById(id)
                    .orElseThrow(() -> new ResourceNotFoundException("Product not found"));
            
            // 3. Önbelleğe ekle
            redisTemplate.opsForValue().set(cacheKey, product);
            redisTemplate.expire(cacheKey, 10, TimeUnit.MINUTES); // 10 dakika TTL
        }
        
        return product;
    }

    public void updateProduct(Product product) {
        // Veritabanını güncelle
        productRepository.save(product);
        
        // Önbelleği güncelle
        String cacheKey = PRODUCT_CACHE_PREFIX + product.getId();
        redisTemplate.opsForValue().set(cacheKey, product);
        redisTemplate.expire(cacheKey, 10, TimeUnit.MINUTES);
    }

    public void deleteProduct(Long id) {
        // Veritabanından sil
        productRepository.deleteById(id);
        
        // Önbellekten sil
        String cacheKey = PRODUCT_CACHE_PREFIX + id;
        redisTemplate.delete(cacheKey);
    }
}

Bu pattern, veritabanı yükünü azaltır ve uygulama performansını artırır. Ancak, önbellek ve veritabanı arasındaki tutarsızlıkları yönetmek için dikkatli olunmalıdır.

❓ Soru 2: Write-Through ve Write-Behind caching stratejileri arasındaki fark nedir?

Cevap: İkisi de yazma işlemlerinde kullanılan caching stratejileridir, ancak farklı yaklaşımlar sunarlar:

  • Write-Through: Veri hem önbelleğe hem de veritabanına aynı anda yazılır. Bu, veri tutarlılığını sağlar ancak yazma işlemlerini yavaşlatabilir.
  • Write-Behind: Veri önce önbelleğe yazılır ve daha sonra arka planda veritabanına zamanlanmış olarak yazılır. Bu, yazma işlemlerini hızlandırır ancak veri kaybı riski taşır.

Write-Through örneği:

@Service
public class ProductService {

    @Autowired
    private ProductRepository productRepository;
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    private static final String PRODUCT_CACHE_PREFIX = "product:";

    public Product saveProduct(Product product) {
        // 1. Veritabanına kaydet
        Product savedProduct = productRepository.save(product);
        
        // 2. Önbelleğe kaydet
        String cacheKey = PRODUCT_CACHE_PREFIX + savedProduct.getId();
        redisTemplate.opsForValue().set(cacheKey, savedProduct);
        redisTemplate.expire(cacheKey, 10, TimeUnit.MINUTES);
        
        return savedProduct;
    }
}

Write-Behind örneği (basit uygulama):

@Service
public class ProductService {

    @Autowired
    private ProductRepository productRepository;
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    @Autowired
    private TaskExecutor taskExecutor;

    private static final String PRODUCT_CACHE_PREFIX = "product:";

    public Product saveProductAsync(Product product) {
        // 1. Önce önbelleğe kaydet
        String cacheKey = PRODUCT_CACHE_PREFIX + product.getId();
        redisTemplate.opsForValue().set(cacheKey, product);
        redisTemplate.expire(cacheKey, 10, TimeUnit.MINUTES);
        
        // 2. Asenkron olarak veritabanına kaydet
        taskExecutor.execute(() -> {
            productRepository.save(product);
        });
        
        return product;
    }
}

Write-Through, veri tutarlılığı kritik olan durumlarda tercih edilirken, Write-Behind performansın önemli olduğu ve veri kaybının tolere edilebildiği durumlarda kullanılır.

❓ Soru 3: Redis ile cache stampede (cache thundering herd) problemi nasıl çözülür?

Cevap: Cache stampede, popüler bir önbellek anahtarının süresi dolduğunda, çok sayıda istemcinin aynı anda veritabanına gitmesi ve sistemi aşırı yüklemesi durumudur. Bu problemi çözmek için çeşitli stratejiler kullanılır:

  • Locking: İlk istek lock alır, diğerleri bekler.
  • Probabilistic Early Expiration: Önbellek anahtarının süresi dolmadan önce rastgele bir süreyle yenilenmesi.
  • Blank Value Caching: Veritabanında olmayan veriler için boş değer önbelleğe alınır.

Locking çözümü:

@Service
public class ProductService {

    @Autowired
    private ProductRepository productRepository;
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    private static final String PRODUCT_CACHE_PREFIX = "product:";
    private static final String PRODUCT_LOCK_PREFIX = "product_lock:";

    public Product getProductById(Long id) {
        String cacheKey = PRODUCT_CACHE_PREFIX + id;
        String lockKey = PRODUCT_LOCK_PREFIX + id;
        
        // 1. Önbellekte ara
        Product product = (Product) redisTemplate.opsForValue().get(cacheKey);
        
        if (product == null) {
            // 2. Lock almayı dene
            Boolean acquired = redisTemplate.opsForValue().setIfAbsent(lockKey, "locked", 10, TimeUnit.SECONDS);
            
            if (Boolean.TRUE.equals(acquired)) {
                try {
                    // 3. Veritabanından al
                    product = productRepository.findById(id)
                            .orElseThrow(() -> new ResourceNotFoundException("Product not found"));
                    
                    // 4. Önbelleğe ekle
                    redisTemplate.opsForValue().set(cacheKey, product);
                    redisTemplate.expire(cacheKey, 10, TimeUnit.MINUTES);
                } finally {
                    // 5. Lock'u serbest bırak
                    redisTemplate.delete(lockKey);
                }
            } else {
                // 6. Başka biri işlem yapıyor, biraz bekle ve tekrar dene
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
                return getProductById(id); // Recursive call
            }
        }
        
        return product;
    }
}

Bu çözüm, aynı anda gelen çoklu isteklerin veritabanına gitmesini engeller ve sadece bir istek veriyi getirip önbelleğe koyar. Diğer istekler önbellekten okur.

❓ Soru 4: Redis ile cache warming (ön ısıtma) nasıl yapılır?

Cevap: Cache warming, uygulama başladığında veya kullanılmadan önce önbelleği popüler verilerle doldurma işlemidir. Bu, ilk isteklerde önbellek miss'lerini azaltır ve performansı artırır.

Örnek bir cache warming implementasyonu:

@Service
public class CacheWarmupService {

    @Autowired
    private ProductRepository productRepository;
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    @Autowired
    private ProductService productService;
    
    private static final String PRODUCT_CACHE_PREFIX = "product:";
    private static final String POPULAR_PRODUCTS_KEY = "popular:products";

    @PostConstruct
    public void warmupCache() {
        // Popüler ürünleri önbelleğe yükle
        List<Product> popularProducts = productRepository.findTop10ByOrderBySalesDesc();
        
        for (Product product : popularProducts) {
            String cacheKey = PRODUCT_CACHE_PREFIX + product.getId();
            redisTemplate.opsForValue().set(cacheKey, product);
            redisTemplate.expire(cacheKey, 1, TimeUnit.HOURS);
        }
        
        // Popüler ürün ID'lerini set olarak sakla
        Set<String> popularProductIds = popularProducts.stream()
                .map(product -> product.getId().toString())
                .collect(Collectors.toSet());
                
        redisTemplate.opsForSet().add(POPULAR_PRODUCTS_KEY, popularProductIds.toArray());
        redisTemplate.expire(POPULAR_PRODUCTS_KEY, 1, TimeUnit.HOURS);
    }
    
    public void warmupProduct(Long productId) {
        try {
            Product product = productService.getProductById(productId);
            String cacheKey = PRODUCT_CACHE_PREFIX + productId;
            redisTemplate.opsForValue().set(cacheKey, product);
            redisTemplate.expire(cacheKey, 1, TimeUnit.HOURS);
        } catch (Exception e) {
            // Hata durumunda logla
            System.err.println("Cache warming failed for product " + productId + ": " + e.getMessage());
        }
    }
}

Zamanlanmış cache warming için:

@Service
public class ScheduledCacheWarmupService {

    @Autowired
    private CacheWarmupService cacheWarmupService;
    
    // Her saat başı çalışır
    @Scheduled(cron = "0 0 * * * *")
    public void scheduledCacheWarmup() {
        cacheWarmupService.warmupCache();
    }
    
    // Her 5 dakikada bir çalışır
    @Scheduled(fixedRate = 5 * 60 * 1000)
    public void frequentCacheWarmup() {
        // Sık erişilen verileri önbelleğe al
        cacheWarmupService.warmupFrequentlyAccessedData();
    }
}

Cache warming, özellikle trafik artışları öncesi veya planlı bakım sonrası kullanışlıdır. Bu, kullanıcıların ilk isteklerinde gecikme yaşamamasını sağlar.

❓ Soru 5: Redis ile cache eviction (önbellek temizleme) stratejileri nelerdir?

Cevap: Cache eviction, önbellekteki verilerin temizlenmesi için kullanılan stratejilerdir. Redis çeşitli eviction stratejileri sunar:

  • noeviction: Bellek dolduğunda yeni veri eklenmez, hata döner.
  • allkeys-lru: En az kullanılan (Least Recently Used) anahtarları siler.
  • allkeys-lfu: En az kullanılan sıklığa sahip (Least Frequently Used) anahtarları siler.
  • volatile-lru: TTL ayarlanmış anahtarlardan en az kullanılanları siler.
  • volatile-lfu: TTL ayarlanmış anahtarlardan en az kullanılan sıklığa sahip olanları siler.
  • allkeys-random: Rastgele anahtarları siler.
  • volatile-random: TTL ayarlanmış anahtarlardan rastgele olanları siler.
  • volatile-ttl: En kısa TTL'ye sahip anahtarları siler.

Redis yapılandırmasında eviction ayarlama:

# redis.conf
maxmemory 1gb
maxmemory-policy allkeys-lru

Spring Boot uygulamasında eviction yapılandırması:

spring:
  redis:
    host: localhost
    port: 6379
    jedis:
      pool:
        max-active: 10
    lettuce:
      pool:
        max-active: 10
  cache:
    type: redis
    redis:
      time-to-live: 60000  # 1 dakika
      cache-null-values: false
      key-prefix: myapp_
      use-key-prefix: true

Doğru eviction stratejisi seçimi, uygulamanın veri erişim pattern'lerine bağlıdır. Örneğin, sosyal medya uygulamalarında LFU daha uygun olabilirken, e-ticaret uygulamalarında LRU daha iyi performans sağlayabilir.

❓ Soru 6: Redis ile multi-level caching nasıl yapılır?

Cevap: Multi-level caching, birden fazla önbellek katmanını birleştirerek performansı optimize etme stratejisidir. Genellikle yerel (in-memory) önbellek ve dağıtık önbellek (Redis) birlikte kullanılır.

Örnek bir multi-level caching implementasyonu:

@Service
public class ProductService {

    @Autowired
    private ProductRepository productRepository;
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    // Yerel önbellek (Caffeine)
    private final Cache<Long, Product> localCache = Caffeine.newBuilder()
            .expireAfterWrite(5, TimeUnit.MINUTES)
            .maximumSize(1000)
            .build();

    private static final String PRODUCT_CACHE_PREFIX = "product:";

    public Product getProductById(Long id) {
        // 1. Yerel önbellekte ara
        Product product = localCache.getIfPresent(id);
        
        if (product == null) {
            // 2. Redis'te ara
            String cacheKey = PRODUCT_CACHE_PREFIX + id;
            product = (Product) redisTemplate.opsForValue().get(cacheKey);
            
            if (product == null) {
                // 3. Veritabanından al
                product = productRepository.findById(id)
                        .orElseThrow(() -> new ResourceNotFoundException("Product not found"));
                
                // 4. Redis'e ekle
                redisTemplate.opsForValue().set(cacheKey, product);
                redisTemplate.expire(cacheKey, 1, TimeUnit.HOURS);
            }
            
            // 5. Yerel önbelleğe ekle
            localCache.put(id, product);
        }
        
        return product;
    }

    public void updateProduct(Product product) {
        // Veritabanını güncelle
        productRepository.save(product);
        
        // Redis'i güncelle
        String cacheKey = PRODUCT_CACHE_PREFIX + product.getId();
        redisTemplate.opsForValue().set(cacheKey, product);
        redisTemplate.expire(cacheKey, 1, TimeUnit.HOURS);
        
        // Yerel önbelleği güncelle
        localCache.put(product.getId(), product);
    }

    public void deleteProduct(Long id) {
        // Veritabanından sil
        productRepository.deleteById(id);
        
        // Redis'ten sil
        String cacheKey = PRODUCT_CACHE_PREFIX + id;
        redisTemplate.delete(cacheKey);
        
        // Yerel önbellekten sil
        localCache.invalidate(id);
    }
}

Bu strateji, en sık kullanılan veriler için çok düşük gecikme süresi (yerel önbellek) ve daha az kullanılan veriler için yüksek erişilebilirlik (Redis) sağlar. Özellikle yüksek trafikli uygulamalarda performansı önemli ölçüde artırır.

❓ Soru 7: Redis ile cache invalidation (önbellek geçersiz kılma) nasıl yönetilir?

Cevap: Cache invalidation, önbellekteki verilerin güncelliğini sağlamak için kullanılır. Veritabanındaki veriler değiştiğinde, ilgili önbellek girdilerinin güncellenmesi veya silinmesi gerekir.

Örnek bir cache invalidation implementasyonu:

@Service
public class ProductService {

    @Autowired
    private ProductRepository productRepository;
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    @Autowired
    private ApplicationEventPublisher eventPublisher;

    private static final String PRODUCT_CACHE_PREFIX = "product:";
    private static final String PRODUCT_LIST_CACHE_KEY = "products:list";

    @Cacheable(value = "products", key = "#id")
    public Product getProductById(Long id) {
        return productRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("Product not found"));
    }

    @CachePut(value = "products", key = "#product.id")
    public Product updateProduct(Product product) {
        Product updatedProduct = productRepository.save(product);
        
        // Ürün güncellendiğinde ürün listesi önbelleğini geçersiz kıl
        redisTemplate.delete(PRODUCT_LIST_CACHE_KEY);
        
        // Ürün güncelleme olayını yayınla
        eventPublisher.publishEvent(new ProductUpdatedEvent(updatedProduct));
        
        return updatedProduct;
    }

    @CacheEvict(value = "products", key = "#id")
    public void deleteProduct(Long id) {
        productRepository.deleteById(id);
        
        // Ürün silindiğinde ürün listesi önbelleğini geçersiz kıl
        redisTemplate.delete(PRODUCT_LIST_CACHE_KEY);
        
        // Ürün silme olayını yayınla
        eventPublisher.publishEvent(new ProductDeletedEvent(id));
    }

    @Cacheable(value = "productLists", key = "'all'")
    public List<Product> getAllProducts() {
        return productRepository.findAll();
    }
}

Olay tabanlı cache invalidation:

@Component
public class CacheInvalidationListener {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @EventListener
    public void handleProductUpdatedEvent(ProductUpdatedEvent event) {
        // İlgili önbellek anahtarlarını geçersiz kıl
        Long productId = event.getProduct().getId();
        
        // Ürün detay önbelleği zaten @CachePut ile güncellendi
        
        // Diğer ilgili önbellekleri geçersiz kıl
        redisTemplate.delete("products:category:" + event.getProduct().getCategory());
        redisTemplate.delete("products:related:" + productId);
    }

    @EventListener
    public void handleProductDeletedEvent(ProductDeletedEvent event) {
        Long productId = event.getProductId();
        
        // İlgili tüm önbellekleri geçersiz kıl
        redisTemplate.delete("products:category:*");
        redisTemplate.delete("products:related:" + productId);
        redisTemplate.delete("products:search:*");
    }
}

Bu yaklaşım, veri tutarlılığını sağlar ve eski verilerin önbellekte kalmasını önler. Özellikle dağıtık sistemlerde, olay tabanlı invalidasyon farklı servisler arasında tutarlılığı sağlamak için önemlidir.

❓ Soru 8: Redis ile cache coherency (tutarlılık) nasıl sağlanır?

Cevap: Cache coherency, önbellekteki verilerin veritabanındaki verilerle tutarlı olmasını sağlama işlemidir. Dağıtık sistemlerde bu özellikle zordur ve çeşitli stratejilerle yönetilir.

  • Write-through cache: Veri hem önbelleğe hem de veritabanına yazılır.
  • Cache invalidation: Veri değiştiğinde önbellek geçersiz kılınır.
  • TTL (Time To Live): Önbellek verilerinin süresi belirli bir süre sonra dolar.
  • Read-repair: Eski veri okunduğunda veritabanından güncel veri çekilir.
  • Versioning: Verilere versiyon numarası eklenir ve güncellik kontrol edilir.

Versioning ile cache coherency örneği:

@Service
public class ProductService {

    @Autowired
    private ProductRepository productRepository;
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    private static final String PRODUCT_CACHE_PREFIX = "product:";

    public Product getProductById(Long id) {
        String cacheKey = PRODUCT_CACHE_PREFIX + id;
        
        // Önbellekten ürün ve versiyon bilgisini al
        Map<String, Object> cachedData = (Map<String, Object>) redisTemplate.opsForValue().get(cacheKey);
        
        if (cachedData != null) {
            Long cachedVersion = (Long) cachedData.get("version");
            Product cachedProduct = (Product) cachedData.get("product");
            
            // Veritabanından güncel versiyonu kontrol et
            Product dbProduct = productRepository.findById(id).orElse(null);
            
            if (dbProduct != null && dbProduct.getVersion() > cachedVersion) {
                // Veritabanı daha güncel, önbelleği güncelle
                Map<String, Object> updatedData = new HashMap<>();
                updatedData.put("product", dbProduct);
                updatedData.put("version", dbProduct.getVersion());
                
                redisTemplate.opsForValue().set(cacheKey, updatedData);
                redisTemplate.expire(cacheKey, 10, TimeUnit.MINUTES);
                
                return dbProduct;
            } else {
                // Önbellek güncel, önbellekten döndür
                return cachedProduct;
            }
        } else {
            // Önbellekte yok, veritabanından al
            Product product = productRepository.findById(id)
                    .orElseThrow(() -> new ResourceNotFoundException("Product not found"));
            
            // Önbelleğe ekle
            Map<String, Object> data = new HashMap<>();
            data.put("product", product);
            data.put("version", product.getVersion());
            
            redisTemplate.opsForValue().set(cacheKey, data);
            redisTemplate.expire(cacheKey, 10, TimeUnit.MINUTES);
            
            return product;
        }
    }

    public Product updateProduct(Product product) {
        // Versiyonu artır
        product.setVersion(product.getVersion() + 1);
        
        // Veritabanını güncelle
        Product updatedProduct = productRepository.save(product);
        
        // Önbelleği güncelle
        String cacheKey = PRODUCT_CACHE_PREFIX + updatedProduct.getId();
        Map<String, Object> data = new HashMap<>();
        data.put("product", updatedProduct);
        data.put("version", updatedProduct.getVersion());
        
        redisTemplate.opsForValue().set(cacheKey, data);
        redisTemplate.expire(cacheKey, 10, TimeUnit.MINUTES);
        
        return updatedProduct;
    }
}

Bu yaklaşım, önbellekteki verilerin güncelliğini kontrol eder ve eski verilerin kullanılmasını önler. Özellikle birden fazla uygulama örneğinin aynı veriyi güncellediği durumlarda önemlidir.

❓ Soru 9: Redis ile cache partitioning (bölümlendirme) nasıl yapılır?

Cevap: Cache partitioning, büyük önbellekleri daha küçük parçalara bölerek yönetme işlemidir. Bu, önbellek performansını artırır ve ölçeklenebilirliği sağlar.

  • Veriye dayalı bölümlendirme: Verilerin özelliklerine göre farklı önbellek bölümlerine kaydedilmesi.
  • Hash tabanlı bölümlendirme: Anahtarların hash değerlerine göre farklı önbellek sunucularına yönlendirilmesi.
  • Redis Cluster: Redis'in yerleşik cluster özelliğinin kullanılması.

Veriye dayalı bölümlendirme örneği:

@Service
public class PartitionedCacheService {

    @Autowired
    private RedisTemplate<String, Object> userRedisTemplate;
    
    @Autowired
    private RedisTemplate<String, Object> productRedisTemplate;
    
    @Autowired
    private RedisTemplate<String, Object> orderRedisTemplate;

    public void cacheUser(User user) {
        String cacheKey = "user:" + user.getId();
        userRedisTemplate.opsForValue().set(cacheKey, user);
        userRedisTemplate.expire(cacheKey, 1, TimeUnit.HOURS);
    }

    public User getUser(Long userId) {
        String cacheKey = "user:" + userId;
        return (User) userRedisTemplate.opsForValue().get(cacheKey);
    }

    public void cacheProduct(Product product) {
        String cacheKey = "product:" + product.getId();
        productRedisTemplate.opsForValue().set(cacheKey, product);
        productRedisTemplate.expire(cacheKey, 1, TimeUnit.HOURS);
    }

    public Product getProduct(Long productId) {
        String cacheKey = "product:" + productId;
        return (Product) productRedisTemplate.opsForValue().get(cacheKey);
    }

    public void cacheOrder(Order order) {
        String cacheKey = "order:" + order.getId();
        orderRedisTemplate.opsForValue().set(cacheKey, order);
        orderRedisTemplate.expire(cacheKey, 1, TimeUnit.HOURS);
    }

    public Order getOrder(Long orderId) {
        String cacheKey = "order:" + orderId;
        return (Order) orderRedisTemplate.opsForValue().get(cacheKey);
    }
}

Hash tabanlı bölümlendirme örneği:

@Service
public class HashPartitionedCacheService {

    private final List<RedisTemplate<String, Object>> redisTemplates;
    private final int numberOfShards;

    public HashPartitionedCacheService(List<RedisTemplate<String, Object>> redisTemplates) {
        this.redisTemplates = redisTemplates;
        this.numberOfShards = redisTemplates.size();
    }

    private RedisTemplate<String, Object> getShard(String key) {
        int shardIndex = Math.abs(key.hashCode()) % numberOfShards;
        return redisTemplates.get(shardIndex);
    }

    public void cacheData(String key, Object value) {
        RedisTemplate<String, Object> template = getShard(key);
        template.opsForValue().set(key, value);
        template.expire(key, 1, TimeUnit.HOURS);
    }

    public Object getData(String key) {
        RedisTemplate<String, Object> template = getShard(key);
        return template.opsForValue().get(key);
    }
}

Cache partitioning, büyük ölçekli uygulamalarda önbellek performansını artırır ve tek bir önbellek sunucusuna olan bağımlılığı azaltır. Redis Cluster, bu işlemi otomatik olarak yapar ve veri dağıtımını yönetir.

❓ Soru 10: Redis ile cache monitoring (izleme) nasıl yapılır?

Cevap: Cache monitoring, önbelleğin performansını, kullanımını ve sağlık durumunu izleme işlemidir. Redis çeşitli metrikler ve komutlar aracılığıyla monitoring imkanları sunar.

  • Redis INFO komutu: Sunucu istatistiklerini ve metriklerini gösterir.
  • Redis MONITOR komutu: Tüm Redis komutlarını gerçek zamanlı olarak gösterir.
  • Redis SLOWLOG komutu: Yavaş sorguları gösterir.
  • Spring Boot Actuator: Redis metriklerini ve sağlık durumunu izler.
  • Prometheus + Grafana: Redis metriklerini görselleştirir.

Redis INFO komutu çıktısı:

$ redis-cli INFO

# Server
redis_version:6.2.6
os:Linux 5.4.0-80-generic
...

# Memory
used_memory:1048576
used_memory_human:1.00M
used_memory_peak:2097152
used_memory_peak_human:2.00M
...

# Stats
total_connections_received:10
total_commands_processed:100
instantaneous_ops_per_sec:0
total_net_input_bytes:5000
total_net_output_bytes:10000
...

# KeySpace
db0:keys=5,expires=0,avg_ttl=0

Spring Boot ile Redis monitoring:

@Configuration
public class RedisMetricsConfig {

    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
        RedisCacheConfiguration cacheConfig = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofMinutes(10))
                .disableCachingNullValues()
                .serializeValuesWith(RedisSerializationContext.SerializationPair
                        .fromSerializer(new GenericJackson2JsonRedisSerializer()));

        return RedisCacheManager.builder(connectionFactory)
                .cacheDefaults(cacheConfig)
                .transactionAware()
                .build();
    }

    @Bean
    public MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
        return registry -> registry.config().commonTags("application", "myapp");
    }

    @Bean
    public RedisMetrics redisMetrics(RedisConnectionFactory connectionFactory) {
        return new RedisMetrics(connectionFactory, "redis", Collections.emptyList());
    }
}

Prometheus ile Redis metriklerini toplama:

# prometheus.yml
scrape_configs:
  - job_name: 'redis'
    static_configs:
      - targets: ['localhost:6379']

Redis monitoring, önbellek performansını izlemek, sorunları teşhis etmek ve kapasite planlaması yapmak için önemlidir. Özellikle üretim ortamlarında düzenli monitoring yapılması, önbellek ile ilgili sorunların önceden tespit edilmesine yardımcı olur.

Redis Performans

Redis performans optimizasyonu ve tünelleme ile ilgili mülakat soruları.

❓ Soru 1: Redis performansını etkileyen faktörler nelerdir?

Cevap: Redis performansı çeşitli faktörlerden etkilenir. Bu faktörleri anlamak, Redis'in en iyi şekilde çalışmasını sağlamak için önemlidir:

  • Ağ gecikmesi: Redis istemcisi ile sunucu arasındaki ağ gecikmesi, özellikle yüksek hacimli işlemlerde performansı etkiler.
  • Bellek kullanımı: Yetersiz bellek, sık sık disk yazmalarına neden olur ve performansı düşürür.
  • Veri yapısı seçimi: Doğru veri yapısını seçmek, bellek verimliliği ve işlem hızı açısından önemlidir.
  • Pipelining kullanımı: Komutları toplu halde göndermek, ağ gecikmesini azaltır ve performansı artırır.
  • Bağlantı havuzu: Bağlantı havuzu, bağlantı kurma maliyetini azaltır.
  • Persistans stratejisi: RDB veya AOF kullanımı, performansı etkileyebilir.
  • İstemci yapılandırması: Timeout, serileştirme ve diğer istemci ayarları performansı etkiler.
  • Donanım: CPU, RAM ve disk hızı performansı doğrudan etkiler.

Redis performansını ölçmek için kullanılan komutlar:

# Redis gecikmesini ölçme
redis-cli --latency

# Yavaş sorguları görme
SLOWLOG GET

# Bellek kullanımını görme
INFO memory

# İşlem sayısını görme
INFO stats

Bu faktörleri optimize ederek Redis performansını önemli ölçüde artırabilirsiniz.

❓ Soru 2: Redis'te pipelining nasıl performansı artırır?

Cevap: Pipelining, birden fazla komutu tek bir istekte göndermek ve tüm yanıtları tek bir seferde almak için kullanılan bir tekniktir. Bu, ağ gecikmesini azaltır ve performansı önemli ölçüde artırır.

Pipelining olmadan:

# Her komut için ayrı bir ağ turu gerekir
redis-cli SET key1 value1
redis-cli SET key2 value2
redis-cli SET key3 value3
redis-cli GET key1
redis-cli GET key2
redis-cli GET key3

Pipelining ile:

# Tüm komutlar tek bir ağ turunda gönderilir
redis-cli --pipe
SET key1 value1
SET key2 value2
SET key3 value3
GET key1
GET key2
GET key3
EOF

Java ile Redis pipelining örneği:

@Service
public class ProductService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    public void batchUpdateProducts(List<Product> products) {
        // Pipeline oluştur
        redisTemplate.executePipelined(new RedisCallback<Object>() {
            @Override
            public Object doInRedis(RedisConnection connection) throws DataAccessException {
                StringRedisConnection stringRedisConn = (StringRedisConnection) connection;
                
                for (Product product : products) {
                    String key = "product:" + product.getId();
                    stringRedisConn.set(key, product.toString());
                }
                
                return null;
            }
        });
    }

    public List<Product> batchGetProducts(List<Long> productIds) {
        // Pipeline ile çoklu okuma
        List<Object> results = redisTemplate.executePipelined(new RedisCallback<Object>() {
            @Override
            public Object doInRedis(RedisConnection connection) throws DataAccessException {
                StringRedisConnection stringRedisConn = (StringRedisConnection) connection;
                
                for (Long id : productIds) {
                    String key = "product:" + id;
                    stringRedisConn.get(key);
                }
                
                return null;
            }
        });
        
        // Sonuçları işle
        List<Product> products = new ArrayList<>();
        for (Object result : results) {
            if (result != null) {
                products.add(Product.fromString(result.toString()));
            }
        }
        
        return products;
    }
}

Pipelining, özellikle çok sayıda komutun bir arada gönderilmesi gereken durumlarda (örneğin, toplu veri işleme) performansı önemli ölçüde artırır. Ağ gecikmesi yüksek olduğunda bu fark daha belirgin hale gelir.

❓ Soru 3: Redis bellek optimizasyonu nasıl yapılır?

Cevap: Redis bellek optimizasyonu, verileri daha verimli bir şekilde saklayarak bellek kullanımını azaltma işlemidir. Bu, daha fazla veriyi aynı bellekte saklamayı mümkün kılar ve performansı artırır.

  • Veri yapılarını doğru seçme: Her kullanım durumu için en uygun veri yapısını seçmek bellek kullanımını optimize eder.
  • Kısa anahtarlar kullanma: Anahtarları mümkün olduğunca kısa ve anlamlı tutmak.
  • Hash'lar kullanma: İlgili alanları tek bir anahtar altında gruplamak.
  • ZipList kullanımı: Küçük listeler ve sorted set'ler için ZipList kullanmak.
  • IntSet kullanımı: Sadece tamsayı içeren set'ler için IntSet kullanmak.
  • Veri sıkıştırma: Büyük verileri sıkıştırmak.
  • Veri parçalama: Büyük verileri daha küçük parçalara bölmek.

Hash kullanımı örneği:

# Verimsiz kullanım (çoklu anahtar)
SET user:1:name "Ahmet"
SET user:1:surname "Yılmaz"
SET user:1:email "ahmet@example.com"
SET user:1:age "30"

# Verimli kullanım (tek hash)
HSET user:1 name "Ahmet" surname "Yılmaz" email "ahmet@example.com" age "30"

ZipList yapılandırması:

# redis.conf
# Küçük listeler için ZipList kullanımı
list-max-ziplist-size -2  # 8KB
list-compress-depth 0

# Küçük set'ler için ZipList kullanımı
set-max-intset-entries 512

Java ile bellek optimizasyonu:

@Service
public class OptimizedUserService {

    @Autowired
    private StringRedisTemplate redisTemplate;

    public void saveUserOptimized(User user) {
        String key = "user:" + user.getId();
        
        // Hash olarak kaydet (daha verimli)
        Map<String, String> userMap = new HashMap<>();
        userMap.put("name", user.getName());
        userMap.put("surname", user.getSurname());
        userMap.put("email", user.getEmail());
        userMap.put("age", String.valueOf(user.getAge()));
        
        redisTemplate.opsForHash().putAll(key, userMap);
        redisTemplate.expire(key, 1, TimeUnit.HOURS);
    }

    public User getUserOptimized(Long userId) {
        String key = "user:" + userId;
        
        // Hash olarak oku
        Map<Object, Object> userMap = redisTemplate.opsForHash().entries(key);
        
        if (userMap.isEmpty()) {
            return null;
        }
        
        User user = new User();
        user.setId(userId);
        user.setName((String) userMap.get("name"));
        user.setSurname((String) userMap.get("surname"));
        user.setEmail((String) userMap.get("email"));
        user.setAge(Integer.parseInt((String) userMap.get("age")));
        
        return user;
    }
}

Bu optimizasyonlar, Redis bellek kullanımını önemli ölçüde azaltabilir ve aynı fiziksel bellekte daha fazla veri saklanmasını sağlayabilir.

❓ Soru 4: Redis'te yavaş sorgular nasıl tespit edilir ve optimize edilir?

Cevap: Redis'te yavaş sorgular, performansı etkileyen önemli faktörlerdir. Bu sorguları tespit edip optimize etmek, Redis performansını artırır.

Yavaş sorguları tespit etme:

# Yavaş sorgu eşiğini ayarla (mikrosaniye cinsinden)
CONFIG SET slowlog-log-slower-than 10000

# Yavaş sorgu log uzunluğunu ayarla
CONFIG SET slowlog-max-len 128

# Yavaş sorguları görüntüle
SLOWLOG GET

# Son 10 yavaş sorguyu görüntüle
SLOWLOG GET 10

Yaygın yavaş sorgular ve optimizasyonları:

  • KEYS komutu: Büyük veri setlerinde tüm anahtarları listelemek yavaştır. Bunun yerine SCAN komutu kullanılmalı.
  • Büyük LIST/SET işlemleri: Büyük listeler veya set'ler üzerinde işlemler yavaş olabilir. Bunun yerine veri parçalama veya farklı veri yapıları kullanılmalı.
  • FLUSHDB/FLUSHALL: Tüm veritabanını silmek yavaştır. Üretim ortamında dikkatli kullanılmalı.
  • Büyük HASH işlemleri: Büyük hash'ler üzerinde HGETALL gibi işlemler yavaş olabilir. Bunun yerine HSCAN veya alan bazında okuma yapılmalı.

KEYS yerine SCAN kullanımı:

# Yavaş olan KEYS komutu
KEYS user:*

# Optimize edilmiş SCAN komutu
SCAN 0 MATCH user:* COUNT 100

Java ile SCAN kullanımı:

@Service
public class OptimizedUserService {

    @Autowired
    private StringRedisTemplate redisTemplate;

    public List<String> getAllUserKeys() {
        List<String> keys = new ArrayList<>();
        ScanOptions options = ScanOptions.scanOptions().match("user:*").count(100).build();
        
        // Cursor ile verimli tarama
        try (Cursor<String> cursor = redisTemplate.scan(options)) {
            while (cursor.hasNext()) {
                keys.add(cursor.next());
            }
        } catch (Exception e) {
            // Hata yönetimi
        }
        
        return keys;
    }
}

Büyük veri setlerini parçalama:

@Service
public class OptimizedAnalyticsService {

    @Autowired
    private StringRedisTemplate redisTemplate;

    public void recordPageView(String userId, String pageId) {
        // Tüm sayfa görüntülemelerini tek bir anahtarda saklamak yerine
        // Kullanıcı ve sayfa bazında parçala
        String key = "pageviews:" + userId + ":" + pageId;
        redisTemplate.opsForValue().increment(key);
        redisTemplate.expire(key, 7, TimeUnit.DAYS);
    }

    public Long getPageViews(String userId, String pageId) {
        String key = "pageviews:" + userId + ":" + pageId;
        String value = redisTemplate.opsForValue().get(key);
        return value != null ? Long.parseLong(value) : 0L;
    }
}

Yavaş sorguları düzenli olarak izlemek ve optimize etmek, Redis performansını yüksek tutmak için önemlidir.

❓ Soru 5: Redis connection pooling nasıl performansı artırır?

Cevap: Redis connection pooling, istemciler ile Redis sunucusu arasındaki bağlantıları yeniden kullanarak performansı artırır. Her istek için yeni bir bağlantı oluşturmak yerine, havuzdaki mevcut bağlantıları kullanır.

Connection pooling avantajları:

  • Bağlantı kurma maliyetini azaltır
  • Ağ kaynaklarını verimli kullanır
  • Yüksek eşzamanlı istekleri daha iyi yönetir
  • Bağlantı sayısını sınırlayarak Redis sunucusunu korur

Lettuce connection pool yapılandırması:

spring:
  redis:
    host: localhost
    port: 6379
    lettuce:
      pool:
        max-active: 100   # Maksimum aktif bağlantı sayısı
        max-idle: 50      # Boştaki maksimum bağlantı sayısı
        min-idle: 10      # Boştaki minimum bağlantı sayısı
        max-wait: 5000ms  # Bağlantı havuzunda bağlantı yoksa bekleme süresi

Özel connection pool yapılandırması:

@Configuration
public class RedisConfig {

    @Bean
    public LettuceConnectionFactory redisConnectionFactory() {
        RedisStandaloneConfiguration redisConfig = new RedisStandaloneConfiguration();
        redisConfig.setHostName("localhost");
        redisConfig.setPort(6379);
        
        // Connection pool yapılandırması
        LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
                .clientOptions(ClientOptions.builder()
                        .autoReconnect(true)
                        .build())
                .poolConfig(GenericObjectPoolConfig<LettuceConnection>().builder()
                        .maxTotal(100)
                        .maxIdle(50)
                        .minIdle(10)
                        .maxWaitMillis(5000)
                        .build())
                .build();
        
        return new LettuceConnectionFactory(redisConfig, clientConfig);
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        return template;
    }
}

Jedis connection pool yapılandırması:

@Configuration
public class RedisConfig {

    @Bean
    public JedisConnectionFactory redisConnectionFactory() {
        RedisStandaloneConfiguration redisConfig = new RedisStandaloneConfiguration();
        redisConfig.setHostName("localhost");
        redisConfig.setPort(6379);
        
        JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(redisConfig);
        
        // Jedis connection pool yapılandırması
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        poolConfig.setMaxTotal(100);
        poolConfig.setMaxIdle(50);
        poolConfig.setMinIdle(10);
        poolConfig.setMaxWaitMillis(5000);
        poolConfig.setTestOnBorrow(true);
        poolConfig.setTestWhileIdle(true);
        
        jedisConnectionFactory.setUsePool(true);
        jedisConnectionFactory.setPoolConfig(poolConfig);
        
        return jedisConnectionFactory;
    }
}

Doğru yapılandırılmış bir connection pool, Redis performansını önemli ölçüde artırabilir. Özellikle yüksek trafikli uygulamalarda, bağlantı havuzu optimizasyonu kritik öneme sahiptir.

❓ Soru 6: Redis'te komut optimizasyonu nasıl yapılır?

Cevap: Redis'te komut optimizasyonu, verimli komutlar kullanarak performansı artırma işlemidir. Doğru komutları seçmek ve kullanmak, Redis'in daha hızlı çalışmasını sağlar.

  • Atomik komutlar kullanma: INCR, DECR, HINCRBY gibi atomik komutlar, birden fazla komutun işlevini tek bir komutta yapar.
  • Toplu işlemler kullanma: MSET, MGET gibi komutlar, çoklu işlemleri tek bir komutta yapar.
  • Doğru veri yapılarını seçme: Her kullanım durumu için en uygun veri yapısını seçmek.
  • Bit işlemleri kullanma: Bit işlemleri, bellek verimliliği sağlar.
  • LUA scripting kullanma: Karmaşık işlemleri sunucu tarafında çalıştırarak ağ gecikmesini azaltır.

Atomik komutlar örneği:

# Verimsiz kullanım
GET counter
SET counter [hesaplanmış değer]

# Optimize edilmiş kullanım
INCR counter

Toplu işlemler örneği:

# Verimsiz kullanım
SET key1 value1
SET key2 value2
SET key3 value3

# Optimize edilmiş kullanım
MSET key1 value1 key2 value2 key3 value3

Java ile toplu işlemler:

@Service
public class OptimizedProductService {

    @Autowired
    private StringRedisTemplate redisTemplate;

    public void batchUpdateProducts(Map<Long, String> productUpdates) {
        // Toplu güncelleme için pipeline kullan
        redisTemplate.executePipelined(new RedisCallback<Object>() {
            @Override
            public Object doInRedis(RedisConnection connection) throws DataAccessException {
                StringRedisConnection stringRedisConn = (StringRedisConnection) connection;
                
                for (Map.Entry<Long, String> entry : productUpdates.entrySet()) {
                    String key = "product:" + entry.getKey();
                    stringRedisConn.set(key, entry.getValue());
                }
                
                return null;
            }
        });
    }

    public Map<Long, String> batchGetProducts(List<Long> productIds) {
        // Çoklu okuma için pipeline kullan
        List<Object> results = redisTemplate.executePipelined(new RedisCallback<Object>() {
            @Override
            public Object doInRedis(RedisConnection connection) throws DataAccessException {
                StringRedisConnection stringRedisConn = (StringRedisConnection) connection;
                
                for (Long id : productIds) {
                    String key = "product:" + id;
                    stringRedisConn.get(key);
                }
                
                return null;
            }
        });
        
        // Sonuçları işle
        Map<Long, String> products = new HashMap<>();
        for (int i = 0; i < productIds.size(); i++) {
            Long id = productIds.get(i);
            Object result = results.get(i);
            if (result != null) {
                products.put(id, result.toString());
            }
        }
        
        return products;
    }
}

LUA scripting ile komut optimizasyonu:

 tonumber(ARGV[2]) then
    return 0  -- Limit aşıldı
else
    redis.call('SET', KEYS[1], current)
    return 1  -- Başarılı
end

Komut optimizasyonu, Redis performansını önemli ölçüde artırabilir. Özellikle yüksek trafikli uygulamalarda, doğru komutları kullanmak kritik öneme sahiptir.

❓ Soru 7: Redis'te veri sıkıştırma nasıl yapılır ve performansa etkisi nedir?

Cevap: Redis'te veri sıkıştırma, büyük verileri daha az bellekte saklamak için kullanılan bir tekniktir. Bu, bellek kullanımını azaltır ancak CPU kullanımını artırır.

  • Avantajları:
    • Daha az bellek kullanımı
    • Daha fazla verinin aynı bellekte saklanması
    • Daha az disk alanı kullanımı (persistans durumunda)
  • Dezavantajları:
    • Daha yüksek CPU kullanımı
    • Sıkıştırma/açma gecikmesi
    • Karmaşık implementasyon

Java ile veri sıkıştırma örneği:

@Service
public class CompressedDataService {

    @Autowired
    private RedisTemplate<String, byte[]> redisTemplate;

    private static final String COMPRESSION_PREFIX = "compressed:";

    public void saveCompressedData(String key, String data) {
        try {
            // Veriyi sıkıştır
            byte[] compressed = compress(data);
            
            // Redis'e kaydet
            redisTemplate.opsForValue().set(COMPRESSION_PREFIX + key, compressed);
            redisTemplate.expire(COMPRESSION_PREFIX + key, 1, TimeUnit.HOURS);
        } catch (IOException e) {
            throw new RuntimeException("Sıkıştırma hatası", e);
        }
    }

    public String getCompressedData(String key) {
        try {
            // Redis'ten oku
            byte[] compressed = redisTemplate.opsForValue().get(COMPRESSION_PREFIX + key);
            
            if (compressed == null) {
                return null;
            }
            
            // Sıkıştırmayı aç
            return decompress(compressed);
        } catch (IOException e) {
            throw new RuntimeException("Sıkıştırma açma hatası", e);
        }
    }

    private byte[] compress(String data) throws IOException {
        ByteArrayOutputStream bos = new ByteArrayOutputStream(data.length());
        Deflater compressor = new Deflater();
        compressor.setLevel(Deflater.BEST_COMPRESSION);
        compressor.setInput(data.getBytes());
        compressor.finish();
        
        byte[] buf = new byte[1024];
        while (!compressor.finished()) {
            int count = compressor.deflate(buf);
            bos.write(buf, 0, count);
        }
        
        compressor.end();
        bos.close();
        return bos.toByteArray();
    }

    private String decompress(byte[] compressed) throws IOException {
        ByteArrayInputStream bis = new ByteArrayInputStream(compressed);
        Inflater decompressor = new Inflater();
        decompressor.setInput(compressed);
        
        ByteArrayOutputStream bos = new ByteArrayOutputStream(compressed.length);
        byte[] buf = new byte[1024];
        
        while (!decompressor.finished()) {
            int count = decompressor.inflate(buf);
            bos.write(buf, 0, count);
        }
        
        decompressor.end();
        bos.close();
        bis.close();
        
        return bos.toString();
    }
}

Redis'in yerleşik sıkıştırma özellikleri:

# redis.conf
# List/set/zset/hash için sıkıştırma etkinleştirme
list-compress-depth 0
list-max-ziplist-size -2
set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
hash-max-ziplist-entries 512
hash-max-ziplist-value 64

Veri sıkıştırma, özellikle büyük metinler, JSON verileri veya serileştirilmiş nesneler için kullanışlıdır. Ancak, sıkıştırma/açma işlemi CPU kullanımını artıracağından, sık sık erişilen veriler için dikkatli kullanılmalıdır.

❓ Soru 8: Redis'te sharding (parçalama) nasıl performansı artırır?

Cevap: Redis sharding, verileri birden fazla Redis sunucusuna dağıtarak performansı ve ölçeklenebilirliği artırma tekniğidir. Bu, tek bir Redis sunucusunun sınırlamalarını aşar.

  • Avantajları:
    • Daha yüksek okuma/yazma kapasitesi
    • Daha fazla bellek kapasitesi
    • Daha iyi ölçeklenebilirlik
    • Hata toleransı
  • Sharding yöntemleri:
    • Client-side sharding (İstemci tarafı parçalama)
    • Proxy-based sharding (Proxy tabanlı parçalama)
    • Redis Cluster

Client-side sharding örneği:

@Service
public class ShardedRedisService {

    private final List<JedisPool> jedisPools;
    private final Hashing hashing = Hashing.MURMUR_HASH;

    public ShardedRedisService(List<String> redisHosts) {
        this.jedisPools = new ArrayList<>();
        for (String host : redisHosts) {
            String[] parts = host.split(":");
            String hostname = parts[0];
            int port = Integer.parseInt(parts[1]);
            this.jedisPools.add(new JedisPool(hostname, port));
        }
    }

    private Jedis getShard(String key) {
        int shardIndex = Math.abs(hashing.hash(key)) % jedisPools.size();
        return jedisPools.get(shardIndex).getResource();
    }

    public void set(String key, String value) {
        try (Jedis jedis = getShard(key)) {
            jedis.set(key, value);
        }
    }

    public String get(String key) {
        try (Jedis jedis = getShard(key)) {
            return jedis.get(key);
        }
    }

    public void close() {
        for (JedisPool pool : jedisPools) {
            pool.close();
        }
    }
}

Spring Data Redis ile sharding:

@Configuration
public class ShardedRedisConfig {

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        List<RedisNode> shards = new ArrayList<>();
        shards.add(new RedisNode("redis1.example.com", 6379));
        shards.add(new RedisNode("redis2.example.com", 6379));
        shards.add(new RedisNode("redis3.example.com", 6379));
        
        return new LettuceConnectionFactory(shards);
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        return template;
    }
}

Redis Cluster yapılandırması:

# redis.conf
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
appendonly yes

Redis sharding, büyük ölçekli uygulamalarda performansı önemli ölçüde artırır. Redis Cluster, sharding işlemini otomatik olarak yönetir ve veri dağıtımını, yük dengelemesini ve hata toleransını sağlar.

❓ Soru 9: Redis'te persistans stratejileri performansı nasıl etkiler?

Cevap: Redis persistans stratejileri, verilerin bellekten diske nasıl yazılacağını belirler. Bu stratejiler, performansı ve veri güvenliğini doğrudan etkiler.

  • RDB (Redis Database File):
    • Belirli zaman aralıklarında veri setlerinin anlık görüntüsünü alır
    • Daha hızlı başlangıç süresi ve daha küçük dosya boyutu
    • Son anlık görüntüden sonraki veri kayıpları olabilir
    • Daha yüksek performans (daha az disk I/O)
  • AOF (Append Only File):
    • Her yazma işlemini log dosyasına ekler
    • Daha iyi veri güvenliği (daha az veri kaybı)
    • Daha büyük dosya boyutu ve daha yavaş başlangıç süresi
    • Daha düşük performans (daha fazla disk I/O)
  • Hibrit persistans:
    • Hem RDB hem de AOF kullanımı
    • Hem performans hem de veri güvenliği
    • Daha fazla disk alanı kullanımı

RDB yapılandırması:

# redis.conf
# 15 dakikada en az 1 değişiklik olursa kaydet
save 900 1
# 5 dakikada en az 10 değişiklik olursa kaydet
save 300 10
# 1 dakikada en az 10000 değişiklik olursa kaydet
save 60 10000

# RDB dosyasının adı
dbfilename dump.rdb

# RDB dosyasının dizini
dir /var/lib/redis

AOF yapılandırması:

# redis.conf
# AOF'yi etkinleştir
appendonly yes

# AOF dosyasının adı
appendfilename "appendonly.aof"

# AOF senkronizasyon stratejisi
# always: Her komuttan sonra diske yaz (en güvenli, en yavaş)
# everysec: Her saniyede diske yaz (dengeli)
# no: İşletim sistemine bırak (en hızlı, en riskli)
appendfsync everysec

# AOF yeniden yazma etkinleştir
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

Hibrit persistans yapılandırması:

# redis.conf
# Hem RDB hem de AOF etkinleştir
appendonly yes

# AOF yeniden yazma sırasında RDB kullan
aof-use-rdb-preamble yes

Persistans stratejisi seçimi, uygulamanın performans ve veri güvenliği gereksinimlerine göre yapılmalıdır. Yüksek performanslı okuma/yazma işlemleri için RDB, veri güvenliğinin kritik olduğu durumlarda AOF tercih edilebilir. Hibrit yaklaşım ise her ikisini de dengelemek için kullanılır.

❓ Soru 10: Redis'te yüksek erişilebilirlik (high availability) nasıl sağlanır ve performansa etkisi nedir?

Cevap: Redis'te yüksek erişilebilirlik, bir Redis sunucusu çöktüğünde sistemin çalışmaya devam etmesini sağlar. Bu, genellikle replikasyon ve sentinel/cluster mekanizmaları ile sağlanır.

  • Master-Slave Replikasyon:
    • Bir master sunucu ve birden fazla slave sunucu
    • Master'daki veriler slave'lere kopyalanır
    • Okuma işlemleri slave'ler üzerinden dağıtılabilir
    • Yazma işlemleri sadece master üzerinden yapılır
  • Redis Sentinel:
    • Master sunucuyu izler
    • Master çöktüğünde otomatik olarak yeni master seçer
    • İstemcilere doğru master adresini bildirir
  • Redis Cluster:
    • Verileri birden fazla master sunucusuna dağıtır
    • Otomatik parçalama ve yük dengeleme
    • Bir sunucu çöktüğünde veri kaybı olmadan çalışmaya devam eder

Master-Slave yapılandırması:

# Master redis.conf
bind 192.168.1.100
port 6379

# Slave redis.conf
bind 192.168.1.101
port 6379
slaveof 192.168.1.100 6379

Redis Sentinel yapılandırması:

# sentinel.conf
port 26379
sentinel monitor mymaster 192.168.1.100 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 10000
sentinel parallel-syncs mymaster 1

Redis Cluster yapılandırması:

# redis.conf (tüm sunucularda)
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
appendonly yes

# Cluster oluşturma
redis-cli --cluster create 192.168.1.100:6379 192.168.1.101:6379 192.168.1.102:6379 \
192.168.1.103:6379 192.168.1.104:6379 192.168.1.105:6379 \
--cluster-replicas 1

Spring Boot ile Redis Sentinel yapılandırması:

@Configuration
public class RedisSentinelConfig {

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration()
                .master("mymaster")
                .sentinel("192.168.1.200", 26379)
                .sentinel("192.168.1.201", 26379)
                .sentinel("192.168.1.202", 26379);
        
        sentinelConfig.setPassword("yourpassword");
        
        return new LettuceConnectionFactory(sentinelConfig);
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        return template;
    }
}

Yüksek erişilebilirlik, performansı çeşitli şekillerde etkiler. Master-Slave replikasyon, okuma performansını artırır ancak yazma performansını değiştirmez. Redis Cluster ise hem okuma hem de yazma performansını artırır. Ancak, bu yapılandırmalar daha karmaşık olabilir ve ağ gecikmesini artırabilir.

Redis Cluster

Redis cluster ve yüksek erişilebilirlik ile ilgili mülakat soruları.

❓ Soru 1: Redis Cluster nedir ve ne zaman kullanılır?

Cevap: Redis Cluster, Redis'in dağıtık ve yüksek erişilebilirlikli bir sürümüdür. Verileri birden fazla Redis sunucusuna otomatik olarak dağıtır ve bu sunucular arasında yük dengelemesi sağlar.

Redis Cluster'ın özellikleri:

  • Verileri 16384 hash slot'a böler
  • Her hash slot bir master sunucuya atanır
  • Master sunucular slave'ler sahip olabilir
  • Otomatik failover (yük devretme) sağlar
  • Otomatik yeniden dengeleme yapar
  • Birden fazla anahtar için atomik işlemler destekler

Redis Cluster'ın kullanıldığı durumlar:

  • Büyük veri setleri (tek bir sunucunun RAM kapasitesini aşan)
  • Yüksek okuma/yazma hacmi (tek bir sunucunun işlem kapasitesini aşan)
  • Yüksek erişilebilirlik gereksinimi (sürekli kullanılabilirlik)
  • Ölçeklenebilirlik gereksinimi (gelecekteki büyüme ihtiyacı)

Redis Cluster oluşturma:

# 6 sunucu (3 master, 3 slave) ile cluster oluşturma
redis-cli --cluster create 192.168.1.100:6379 192.168.1.101:6379 192.168.1.102:6379 \
192.168.1.103:6379 192.168.1.104:6379 192.168.1.105:6379 \
--cluster-replicas 1

Redis Cluster, büyük ölçekli uygulamalarda performansı ve erişilebilirliği artırır. Ancak, küçük ölçekli uygulamalarda karmaşıklık ve yönetim maliyeti nedeniyle uygun olmayabilir.

❓ Soru 2: Redis Cluster'ta veri nasıl dağıtılır?

Cevap: Redis Cluster, verileri hash slot'lar aracılığıyla dağıtır. Her anahtar bir hash slot'a atanır ve bu slot bir master sunucuda bulunur.

Hash slot mekanizması:

  • Redis Cluster, 16384 hash slot kullanır (0-16383)
  • Her anahtar için hash slot şu şekilde hesaplanır: slot = CRC16(key) % 16384
  • Her master sunucu, belirli bir aralıktaki slot'lardan sorumludur
  • İstemciler, hangi anahtarın hangi sunucuda olduğunu bilir

Hash slot dağılımı:

# 3 master sunuculu cluster'da slot dağılımı
# Master 1: 0-5460
# Master 2: 5461-10922
# Master 3: 10923-16383

Java ile hash slot hesaplama:

public class RedisClusterUtils {
    
    private static final int SLOT_COUNT = 16384;
    
    public static int calculateSlot(String key) {
        // Hash tag kontrolü ({} içindeki kısım)
        int start = key.indexOf('{');
        if (start != -1) {
            int end = key.indexOf('}', start + 1);
            if (end != -1 && end > start + 1) {
                key = key.substring(start + 1, end);
            }
        }
        
        // CRC16 hesapla
        int crc = CRC16.crc16(key.getBytes());
        return crc % SLOT_COUNT;
    }
}

Redis Cluster'ta slot görüntüleme:

# Cluster slot bilgilerini görme
redis-cli -c cluster slots

# Belirli bir anahtarın hangi slot'ta olduğunu görme
redis-cli -c cluster keyslot mykey

Bu dağıtım mekanizması, verilerin Redis sunucular arasında dengeli bir şekilde dağıtılmasını sağlar. Ayrıca, aynı slot'taki anahtarlar için atomik işlemler mümkündür.

❓ Soru 3: Redis Cluster'ta failover (yük devretme) nasıl çalışır?

Cevap: Redis Cluster'ta failover, bir master sunucu çöktüğünde otomatik olarak slave sunuculardan birinin master rolünü üstlenmesi işlemidir. Bu, sistemin kesintisiz çalışmaya devam etmesini sağlar.

Failover mekanizması:

  • Her master sunucu, slave'ler tarafından düzenli olarak ping'lenir
  • Master sunucuya belirli bir süre içinde ping cevabı gelmezse, master çökmüş sayılır
  • Slave'ler arasında seçim yapılır ve bir slave yeni master olarak atanır
  • Diğer sunucular bu değişikliği kabul eder ve cluster güncellenir
  • İstemciler, yeni master adresini öğrenir ve işlemlerine devam eder

Failover yapılandırması:

# redis.conf
# Master çökmeden önce beklenecek süre (milisaniye)
cluster-node-timeout 5000

# Slave'in master olabilmesi için gereken en az slave sayısı
min-slaves-to-write 1
min-slaves-max-lag 10

Failover test etme:

# Master sunucuyu durdur
redis-cli -h 192.168.1.100 -p 6379 DEBUG SLEEP 30

# Cluster durumunu kontrol et
redis-cli -c cluster nodes

# Logları kontrol et
tail -f /var/log/redis/redis-server.log

Spring Boot ile Redis Cluster failover:

@Configuration
public class RedisClusterConfig {

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        RedisClusterConfiguration clusterConfig = new RedisClusterConfiguration()
                .clusterNode("192.168.1.100", 6379)
                .clusterNode("192.168.1.101", 6379)
                .clusterNode("192.168.1.102", 6379)
                .clusterNode("192.168.1.103", 6379)
                .clusterNode("192.168.1.104", 6379)
                .clusterNode("192.168.1.105", 6379);
        
        clusterConfig.setPassword("yourpassword");
        clusterConfig.setMaxRedirects(3);
        
        return new LettuceConnectionFactory(clusterConfig);
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        return template;
    }
}

Failover mekanizması, Redis Cluster'ın yüksek erişilebilirlik sağlamasının temel bir parçasıdır. Doğru yapılandırıldığında, bir sunucu çöktüğünde sistem otomatik olarak çalışmaya devam eder.

❓ Soru 4: Redis Cluster'ta resharding (yeniden dağıtma) nasıl yapılır?

Cevap: Redis Cluster'ta resharding, hash slot'ları sunucular arasında yeniden dağıtarak cluster'ı dengeleme işlemidir. Bu, yeni sunucu ekleme veya mevcut sunucuları kaldırma sırasında yapılır.

Resharding adımları:

  1. Yeni Redis sunucusunu cluster'a ekle
  2. Redis-cli resharding komutunu çalıştır
  3. Hangi slot'ların taşınacağını ve nereye taşınacağını belirt
  4. Redis, verileri otomatik olarak taşır
  5. Taşıma tamamlandığında cluster güncellenir

Resharding komutu:

# Yeni sunucu ekleme
redis-cli --cluster add-node 192.168.1.106:6379 192.168.1.100:6379

# Resharding yapma
redis-cli --cluster reshard 192.168.1.100:6379

# Komut çıktısı:
# How many slots do you want to move (from 1 to 16384)? 1000
# What is the receiving node ID? [node-id-of-new-server]
# Please enter all the source node IDs.
# Type 'all' to use all the nodes as source nodes, type 'done' once you entered all the source nodes IDs.
# Source node #1: [node-id-of-source-server-1]
# Source node #2: [node-id-of-source-server-2]
# Source node #3: done

Otomatik yeniden dengeleme:

# Cluster'ı otomatik olarak dengeleme
redis-cli --cluster rebalance 192.168.1.100:6379

# Sadece belirli bir ağırlık eşiğini aşan sunucuları dengeleme
redis-cli --cluster rebalance 192.168.1.100:6379 --threshold 1.0

Java ile resharding durumu kontrol etme:

@Service
public class RedisClusterService {

    @Autowired
    private RedisConnectionFactory connectionFactory;

    public void checkClusterBalance() {
        LettuceConnectionFactory lettuceConnectionFactory = (LettuceConnectionFactory) connectionFactory;
        RedisClusterClient clusterClient = (RedisClusterClient) lettuceConnectionFactory.getNativeClient();
        
        Partitions partitions = clusterClient.getPartitions();
        
        Map<String, Integer> nodeSlotCounts = new HashMap<>();
        
        for (RedisClusterNode node : partitions) {
            int slotCount = node.getSlots().size();
            String nodeId = node.getNodeId();
            nodeSlotCounts.put(nodeId, slotCount);
            
            System.out.println("Node: " + node.getUri() + ", Slots: " + slotCount);
        }
        
        // Dengeyi kontrol et
        Optional<Integer> minSlots = nodeSlotCounts.values().stream().min(Integer::compare);
        Optional<Integer> maxSlots = nodeSlotCounts.values().stream().max(Integer::compare);
        
        if (minSlots.isPresent() && maxSlots.isPresent()) {
            int difference = maxSlots.get() - minSlots.get();
            System.out.println("Slot dağılımı farkı: " + difference);
            
            if (difference > 100) {
                System.out.println("Cluster dengesiz, yeniden dağıtma önerilir.");
            }
        }
    }
}

Resharding, Redis Cluster'ın ölçeklenebilirliğini sağlayan önemli bir özelliktir. Bu sayede, sistem büyüdükçe yeni sunucular eklenebilir ve yük dengeli bir şekilde dağıtılabilir.

❓ Soru 5: Redis Cluster'ta multi-key işlemlerinin sınırlamaları nelerdir?

Cevap: Redis Cluster'ta multi-key işlemleri, anahtarların aynı hash slot'ta olması durumunda çalışır. Farklı slot'lardaki anahtarlar için multi-key işlemleri desteklenmez.

Multi-key işlem sınırlamaları:

  • MSET, MGET gibi komutlar sadece aynı slot'taki anahtarlar için çalışır
  • Transaction'lar (MULTI/EXEC) sadece aynı slot'taki anahtarlar için çalışır
  • LUA script'leri sadece aynı slot'taki anahtarlar için çalışır
  • Keys komutu tüm cluster'da çalışır ancak önerilmez

Hash tag kullanımı:

# Aynı slot'ta olması gereken anahtarlar için hash tag kullanımı
# {user:1000}:profile ve {user:1000}:settings aynı slot'ta olacak
SET {user:1000}:profile "profile data"
SET {user:1000}:settings "settings data"

# Multi-key işlemi
MGET {user:1000}:profile {user:1000}:settings

Java ile hash tag kullanımı:

@Service
public class RedisClusterMultiKeyService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    public void saveUserData(String userId, Map<String, String> data) {
        // Hash tag kullanarak aynı slot'ta olacak anahtarlar oluştur
        String profileKey = "{" + userId + "}:profile";
        String settingsKey = "{" + userId + "}:settings";
        
        // Pipeline ile çoklu işlemler
        redisTemplate.executePipelined(new RedisCallback<Object>() {
            @Override
            public Object doInRedis(RedisConnection connection) throws DataAccessException {
                StringRedisConnection stringRedisConn = (StringRedisConnection) connection;
                
                stringRedisConn.set(profileKey, data.get("profile"));
                stringRedisConn.set(settingsKey, data.get("settings"));
                
                return null;
            }
        });
    }

    public Map<String, String> getUserData(String userId) {
        // Hash tag kullanarak aynı slot'taki anahtarları oku
        String profileKey = "{" + userId + "}:profile";
        String settingsKey = "{" + userId + "}:settings";
        
        // Pipeline ile çoklu okuma
        List<Object> results = redisTemplate.executePipelined(new RedisCallback<Object>() {
            @Override
            public Object doInRedis(RedisConnection connection) throws DataAccessException {
                StringRedisConnection stringRedisConn = (StringRedisConnection) connection;
                
                stringRedisConn.get(profileKey);
                stringRedisConn.get(settingsKey);
                
                return null;
            }
        });
        
        Map<String, String> data = new HashMap<>();
        if (results.size() >= 2) {
            data.put("profile", results.get(0) != null ? results.get(0).toString() : null);
            data.put("settings", results.get(1) != null ? results.get(1).toString() : null);
        }
        
        return data;
    }
}

Cluster'da desteklenen komutlar:

# Desteklenen komutlar (aynı slot'taki anahtarlar için)
DEL key1 key2
MGET key1 key2
MSET key1 value1 key2 value2
SUNION key1 key2
SINTER key1 key2
SDIFF key1 key2

Bu sınırlamalar, Redis Cluster'ın dağıtık doğasından kaynaklanır. İlgili verileri aynı slot'ta tutmak için hash tag kullanımı, bu sınırlamaları aşmak için bir çözüm sunar.

❓ Soru 6: Redis Cluster ile Redis Sentinel arasındaki farklar nelerdir?

Cevap: Redis Cluster ve Redis Sentinel, yüksek erişilebilirlik sağlamak için kullanılan iki farklı çözümdür. Ancak, mimari ve özellik olarak farklılıklar gösterirler.

Temel farklar:

Özellik Redis Sentinel Redis Cluster
Mimari Master-Slave replikasyon Sharding (parçalama)
Veri dağıtımı Tüm veri master'da, slave'ler kopya Veri sunucular arasında dağıtık
Ölçeklenebilirlik Okuma ölçeklenebilir, yazma tek master Hem okuma hem yazma ölçeklenebilir
Maksimum bellek Tek sunucunun RAM kapasitesi Tüm sunucuların toplam RAM kapasitesi
Multi-key işlemler Tüm anahtarlar için desteklenir Sadece aynı slot'taki anahtarlar için
Yönetim karmaşıklığı Daha az karmaşık Daha karmaşık

Redis Sentinel yapılandırması:

# sentinel.conf
port 26379
sentinel monitor mymaster 192.168.1.100 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 10000
sentinel parallel-syncs mymaster 1

Redis Cluster yapılandırması:

# redis.conf
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
appendonly yes

Spring Boot ile Redis Sentinel:

@Configuration
public class RedisSentinelConfig {

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration()
                .master("mymaster")
                .sentinel("192.168.1.200", 26379)
                .sentinel("192.168.1.201", 26379)
                .sentinel("192.168.1.202", 26379);
        
        return new LettuceConnectionFactory(sentinelConfig);
    }
}

Spring Boot ile Redis Cluster:

@Configuration
public class RedisClusterConfig {

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        RedisClusterConfiguration clusterConfig = new RedisClusterConfiguration()
                .clusterNode("192.168.1.100", 6379)
                .clusterNode("192.168.1.101", 6379)
                .clusterNode("192.168.1.102", 6379);
        
        return new LettuceConnectionFactory(clusterConfig);
    }
}

Redis Sentinel, daha basit bir yüksek erişilebilirlik çözümü sunarken, Redis Cluster daha karmaşık ancak daha yüksek ölçeklenebilirlik ve performans sağlar. Uygulamanın gereksinimlerine göre doğru çözüm seçilmelidir.

❓ Soru 7: Redis Cluster'ta istemci (client) nasıl çalışır?

Cevap: Redis Cluster istemcileri, cluster topolojisini anlayan ve komutları doğru sunucuya yönlendiren özel istemcilerdir. İstemciler, hangi anahtarın hangi sunucuda olduğunu bilmeli ve yönlendirmeleri doğru şekilde yapmalıdır.

Redis Cluster istemcisinin özellikleri:

  • Cluster topolojisini öğrenir ve günceller
  • Hash slot dağılımını bilir
  • Komutları doğru sunucuya yönlendirir
  • Yönlendirmeleri (redirects) otomatik olarak işler
  • Bağlantı havuzunu yönetir
  • Hata durumunda yeniden dener

İstemci çalışma mantığı:

  1. İstemci, cluster topolojisini öğrenir (hangi sunucunun hangi slot'lardan sorumlu olduğu)
  2. Komut geldiğinde, anahtarın hash slot'unu hesaplar
  3. Komutu doğru sunucuya gönderir
  4. Eğer sunucuMOVED hatası dönerse, istemci topolojiyi günceller ve komutu yeniden gönderir
  5. Eğer sunucuASK hatası dönerse, istemci komutu belirtilen sunucuya gönderir

Java ile Redis Cluster istemcisi:

@Configuration
public class RedisClusterConfig {

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        RedisClusterConfiguration clusterConfig = new RedisClusterConfiguration()
                .clusterNode("192.168.1.100", 6379)
                .clusterNode("192.168.1.101", 6379)
                .clusterNode("192.168.1.102", 6379);
        
        clusterConfig.setPassword("yourpassword");
        clusterConfig.setMaxRedirects(3);
        
        return new LettuceConnectionFactory(clusterConfig);
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        return template;
    }
}

Özel Redis Cluster istemcisi:

@Service
public class CustomRedisClusterClient {

    private final Map<RedisURI, RedisConnection> connections = new ConcurrentHashMap<>();
    private final Map<Integer, RedisURI> slotMap = new ConcurrentHashMap<>();
    
    @PostConstruct
    public void init() {
        // Cluster sunucularını ekle
        addClusterNode(RedisURI.create("redis://192.168.1.100:6379"));
        addClusterNode(RedisURI.create("redis://192.168.1.101:6379"));
        addClusterNode(RedisURI.create("redis://192.168.1.102:6379"));
        
        // Cluster topolojisini öğren
        updateClusterTopology();
    }
    
    private void addClusterNode(RedisURI uri) {
        RedisConnection connection = RedisConnection.connect(uri);
        connections.put(uri, connection);
    }
    
    private void updateClusterTopology() {
        for (Map.Entry<RedisURI, RedisConnection> entry : connections.entrySet()) {
            RedisConnection connection = entry.getValue();
            
            // CLUSTER SLOTS komutunu çalıştır
            RedisClusterSlots clusterSlots = connection.clusterSlots();
            
            for (RedisClusterSlots.NodeSlotRange range : clusterSlots) {
                for (RedisClusterSlots.Node node : range.getNodes()) {
                    if (node.is(RedisClusterSlots.Node.Flag.MASTER)) {
                        // Slot aralığını ve master sunucuyu eşleştir
                        for (int slot = range.getStart(); slot <= range.getEnd(); slot++) {
                            slotMap.put(slot, node.getUri());
                        }
                    }
                }
            }
        }
    }
    
    public String get(String key) {
        int slot = calculateSlot(key);
        RedisURI uri = slotMap.get(slot);
        
        RedisConnection connection = connections.get(uri);
        return connection.sync().get(key);
    }
    
    public void set(String key, String value) {
        int slot = calculateSlot(key);
        RedisURI uri = slotMap.get(slot);
        
        RedisConnection connection = connections.get(uri);
        connection.sync().set(key, value);
    }
    
    private int calculateSlot(String key) {
        // Hash tag kontrolü
        int start = key.indexOf('{');
        if (start != -1) {
            int end = key.indexOf('}', start + 1);
            if (end != -1 && end > start + 1) {
                key = key.substring(start + 1, end);
            }
        }
        
        // CRC16 hesapla
        int crc = CRC16.crc16(key.getBytes());
        return crc % 16384;
    }
}

Redis Cluster istemcileri, cluster'ın dağıtık doğasını yönetmek için önemlidir. Doğru istemci seçimi ve yapılandırması, uygulamanın performansını ve güvenilirliğini önemli ölçüde etkiler.

❓ Soru 8: Redis Cluster'ta veri kaybı nasıl önlenir?

Cevap: Redis Cluster'ta veri kaybını önlemek için çeşitli stratejiler ve yapılandırmalar kullanılır. Bu stratejiler, veri güvenliğini artırır ve sunucu arızalarında veri kaybını en aza indirir.

Veri kaybını önleme stratejileri:

  • Replication (Replikasyon): Her master sunucu için en az bir slave sunucu kullanmak
  • AOF persistansı: Her yazma işlemini log dosyasına kaydetmek
  • Wait komutu: Yazma işlemlerinin slave'lere kopyalandığını doğrulamak
  • Cluster node timeout ayarı: Sunucu çöküşünü doğru tespit etmek
  • Min-slaves-to-write: Yazma için minimum slave sayısı belirlemek

Redis yapılandırması:

# redis.conf
# AOF'yi etkinleştir
appendonly yes

# AOF senkronizasyon stratejisi
appendfsync everysec

# Yazma için minimum slave sayısı
min-slaves-to-write 1
min-slaves-max-lag 10

# Cluster node timeout
cluster-node-timeout 5000

# Cluster slave geçikme faktörü
cluster-slave-validity-factor 10

WAIT komutu kullanımı:

# Yazma işlemini slave'lere kopyala ve bekle
SET mykey myvalue
WAIT 1 5000  # 1 slave'e kopyalanana kadar bekle, maksimum 5 saniye

Java ile WAIT komutu:

@Service
public class SafeRedisClusterService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    public void safeSet(String key, String value) {
        // Veriyi kaydet
        redisTemplate.opsForValue().set(key, value);
        
        // Slave'lere kopyalandığını doğrula
        RedisClusterConnection clusterConnection = (RedisClusterConnection) redisTemplate.getConnectionFactory().getConnection();
        Long result = clusterConnection.wait(ReplicationRequirement.of(1), Duration.ofSeconds(5));
        
        if (result == null || result < 1) {
            // Slave'lere kopyalanamadı, logla veya uyar
            System.err.println("Uyarı: Veri slave'lere kopyalanamadı: " + key);
        }
    }
}

AOF yeniden yazma ve optimizasyon:

# AOF dosyasını manuel olarak yeniden yaz
BGREWRITEAOF

# AOF dosyasını kontrol et
redis-cli --latency -s /var/lib/redis/appendonly.aof

Redis Cluster'ta veri kaybını önlemek, özellikle finansal veya kritik veriler için önemlidir. Bu stratejiler, veri bütünlüğünü ve güvenliğini sağlar.

❓ Soru 9: Redis Cluster'ta monitoring ve bakım nasıl yapılır?

Cevap: Redis Cluster'ta monitoring ve bakım, cluster'ın sağlık durumunu izlemek, performansı optimize etmek ve sorunları çözmek için önemlidir. Bu işlem için çeşitli komutlar ve araçlar kullanılır.

Monitoring komutları:

  • CLUSTER INFO: Cluster genel bilgilerini gösterir
  • CLUSTER NODES: Cluster'daki tüm sunucuları ve durumlarını gösterir
  • CLUSTER SLOTS: Slot dağılımını gösterir
  • INFO: Sunucu istatistiklerini gösterir
  • INFO REPLICATION: Replikasyon bilgilerini gösterir

Monitoring komut örnekleri:

# Cluster genel bilgilerini görme
redis-cli -c cluster info

# Cluster sunucularını görme
redis-cli -c cluster nodes

# Slot dağılımını görme
redis-cli -c cluster slots

# Sunucu istatistiklerini görme
redis-cli -c info

# Replikasyon bilgilerini görme
redis-cli -c info replication

Java ile monitoring:

@Service
public class RedisClusterMonitoringService {

    @Autowired
    private RedisConnectionFactory connectionFactory;

    public Map<String, Object> getClusterInfo() {
        RedisClusterConnection clusterConnection = (RedisClusterConnection) connectionFactory.getConnection();
        
        Map<String, Object> clusterInfo = new HashMap<>();
        
        // Cluster genel bilgileri
        Properties clusterProperties = clusterConnection.clusterGetClusterInfo();
        clusterInfo.put("clusterInfo", clusterProperties);
        
        // Cluster sunucuları
        Iterable<RedisClusterNode> clusterNodes = clusterConnection.clusterGetNodes();
        List<Map<String, Object>> nodes = new ArrayList<>();
        
        for (RedisClusterNode node : clusterNodes) {
            Map<String, Object> nodeInfo = new HashMap<>();
            nodeInfo.put("id", node.getNodeId());
            nodeInfo.put("host", node.getHost());
            nodeInfo.put("port", node.getPort());
            nodeInfo.put("master", node.is(RedisClusterNode.Node.Flag.MASTER));
            nodeInfo.put("slave", node.is(RedisClusterNode.Node.Flag.SLAVE));
            nodeInfo.put("slots", node.getSlots());
            
            nodes.add(nodeInfo);
        }
        
        clusterInfo.put("nodes", nodes);
        
        return clusterInfo;
    }

    public Map<String, Object> getServerStats() {
        RedisClusterConnection clusterConnection = (RedisClusterConnection) connectionFactory.getConnection();
        
        Map<String, Object> stats = new HashMap<>();
        
        // Her sunucu için istatistikleri topla
        Iterable<RedisClusterNode> clusterNodes = clusterConnection.clusterGetNodes();
        
        for (RedisClusterNode node : clusterNodes) {
            Properties serverProperties = clusterConnection.serverCommands().info(node);
            stats.put(node.getHost() + ":" + node.getPort(), serverProperties);
        }
        
        return stats;
    }
}

Prometheus ile monitoring:

# prometheus.yml
global:
  scrape_interval: 15s

scrape_configs:
  - job_name: 'redis-cluster'
    static_configs:
      - targets: 
        - 'redis1:9121'
        - 'redis2:9121'
        - 'redis3:9121'

Redis Cluster bakım görevleri:

  • Düzenli olarak cluster durumunu kontrol etme
  • Slot dağılımını dengeleme
  • AOF dosyalarını optimize etme
  • Logları izleme ve analiz etme
  • Yedekleme ve kurtarma stratejileri oluşturma
  • Güvenlik güncellemelerini uygulama

Redis Cluster monitoring ve bakımı, cluster'ın sağlıklı ve performanslı çalışmasını sağlar. Düzenli monitoring, sorunları önceden tespit etmeye yardımcı olur.

❓ Soru 10: Redis Cluster'ta ölçeklendirme stratejileri nelerdir?

Cevap: Redis Cluster'ta ölçeklendirme, sistemin büyüyen ihtiyaçlara uyum sağlaması için önemlidir. Doğru ölçeklendirme stratejileri, performansı ve erişilebilirliği artırır.

Ölçeklendirme stratejileri:

  • Horizontal scaling (Yatay ölçeklendirme): Yeni sunucular ekleyerek kapasiteyi artırma
  • Vertical scaling (Dikey ölçeklendirme): Mevcut sunucuların kaynaklarını (CPU, RAM, Disk) artırma
  • Read scaling (Okuma ölçeklendirme): Slave sunucular ekleyerek okuma kapasitesini artırma
  • Shard optimization (Parça optimizasyonu): Veri dağılımını optimize etme

Yatay ölçeklendirme adımları:

  1. Yeni Redis sunucularını ekle
  2. Cluster'a yeni sunucuları ekle
  3. Slot'ları yeniden dağıt (resharding)
  4. Yeni sunucular için slave'ler ekle
  5. Uygulama istemcilerini güncelle

Yeni sunucu ekleme komutları:

# Yeni sunucuyu cluster'a ekle
redis-cli --cluster add-node 192.168.1.106:6379 192.168.1.100:6379

# Slot'ları yeniden dağıt
redis-cli --cluster reshard 192.168.1.100:6379

# Cluster'ı dengele
redis-cli --cluster rebalance 192.168.1.100:6379

Java ile otomatik ölçeklendirme:

@Service
public class RedisClusterScalingService {

    @Autowired
    private RedisConnectionFactory connectionFactory;
    
    @Scheduled(fixedRate = 60000)  // Her dakika çalıştır
    public void checkAndScaleCluster() {
        RedisClusterConnection clusterConnection = (RedisClusterConnection) connectionFactory.getConnection();
        
        // Cluster durumunu kontrol et
        Properties clusterProperties = clusterConnection.clusterGetClusterInfo();
        String clusterState = clusterProperties.getProperty("cluster_state");
        
        if (!"ok".equals(clusterState)) {
            // Cluster'da sorun var, ölçeklendirme yapma
            return;
        }
        
        // Bellek kullanımını kontrol et
        Iterable<RedisClusterNode> clusterNodes = clusterConnection.clusterGetNodes();
        
        for (RedisClusterNode node : clusterNodes) {
            if (node.is(RedisClusterNode.Node.Flag.MASTER)) {
                Properties serverProperties = clusterConnection.serverCommands().info(node, "memory");
                String usedMemory = serverProperties.getProperty("used_memory");
                String maxMemory = serverProperties.getProperty("maxmemory");
                
                if (usedMemory != null && maxMemory != null) {
                    double used = Double.parseDouble(usedMemory);
                    double max = Double.parseDouble(maxMemory);
                    double usagePercentage = (used / max) * 100;
                    
                    if (usagePercentage > 80) {
                        // Bellek kullanımı %80'den fazla, ölçeklendirme öner
                        System.out.println("Ölçeklendirme önerisi: " + node.getHost() + ":" + node.getPort() + 
                                          " bellek kullanımı %" + usagePercentage);
                        
                        // Yeni sunucu ekleme işlemi başlat
                        scaleUpCluster();
                    }
                }
            }
        }
    }
    
    private void scaleUpCluster() {
        // Yeni sunucu ekleme işlemleri
        // Bu kısım, otomasyon betikleri veya manuel müdahale ile yapılabilir
        System.out.println("Cluster ölçeklendirme başlatılıyor...");
    }
}

Bulut ortamlarında otomatik ölçeklendirme:

# Kubernetes HorizontalPodAutoscaler örneği
apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
  name: redis-cluster-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: StatefulSet
    name: redis-cluster
  minReplicas: 3
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: memory
      target:
        type: Utilization
        averageUtilization: 80

Redis Cluster ölçeklendirme, sistemin büyüyen ihtiyaçlara uyum sağlamasını sağlar. Doğru stratejiler ve otomasyon ile, cluster performansı ve erişilebilirliği yüksek tutulabilir.

Redis Güvenlik

Redis güvenlik ve en iyi uygulamalar ile ilgili mülakat soruları.

❓ Soru 1: Redis'te temel güvenlik önlemleri nelerdir?

Cevap: Redis, varsayılan olarak güvenlik önlemleri olmayan bir veritabanıdır, bu nedenle güvenlik için ek yapılandırmalar yapmak gerekir. Temel güvenlik önlemleri şunlardır:

  • Parola koruması: Redis sunucusuna erişim için parola belirlemek
  • Ağ güvenliği: Güvenlik duvarı ve ağ erişim kontrolleri
  • Komut yeniden adlandırma: Tehlikeli komutları yeniden adlandırmak veya devre dışı bırakmak
  • SSL/TLS şifreleme: İstemci-sunucu arasındaki iletişimi şifrelemek
  • Veri şifreleme: Hassas verileri şifreleyerek saklamak

Redis güvenlik yapılandırması:

# redis.conf
# Parola koruması
requirepass your-strong-password

# Tehlikeli komutları yeniden adlandırma veya devre dışı bırakma
rename-command CONFIG ""
rename-command FLUSHDB ""
rename-command FLUSHALL ""
rename-command SHUTDOWN ""

# Yalnızca belirli IP adreslerinden bağlantıya izin verme
bind 127.0.0.1 192.168.1.100

# Korumalı mod (yalnızca yerel bağlantılar)
protected-mode yes

Redis sunucusuna bağlanırken parola kullanımı:

# Parola ile bağlanma
redis-cli -h your-redis-host -p 6379 -a your-password

# veya
redis-cli -h your-redis-host -p 6379
AUTH your-password

Spring Boot ile güvenli Redis bağlantısı:

@Configuration
public class RedisSecurityConfig {

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        RedisStandaloneConfiguration redisConfig = new RedisStandaloneConfiguration();
        redisConfig.setHostName("your-redis-host");
        redisConfig.setPort(6379);
        redisConfig.setPassword("your-password");
        
        return new LettuceConnectionFactory(redisConfig);
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        return template;
    }
}

Bu temel güvenlik önlemleri, Redis sunucusunu yetkisiz erişimlere karşı korur ve veri güvenliğini artırır.

❓ Soru 2: Redis'te SSL/TLS şifreleme nasıl yapılandırılır?

Cevap: Redis'te SSL/TLS şifreleme, istemci ve sunucu arasındaki iletişimin şifrelenmesini sağlar. Bu, özellikle internet üzerinden veya güvenli olmayan ağlarda veri güvenliği için önemlidir.

SSL/TLS yapılandırma adımları:

  1. Sertifika yetkilisi (CA) veya otomatik olarak imzalanan sertifikalar oluştur
  2. Redis sunucusunu SSL/TLS için yapılandır
  3. İstemcileri SSL/TLS kullanacak şekilde yapılandır

Sertifika oluşturma:

# CA ve sunucu sertifikası oluşturma
openssl genrsa -out ca.key 2048
openssl req -new -x509 -days 3650 -key ca.key -out ca.crt -subj "/CN=RedisCA"

# Sunucu anahtarı ve CSR oluşturma
openssl genrsa -out redis-server.key 2048
openssl req -new -key redis-server.key -out redis-server.csr -subj "/CN=redis-server"

# Sunucu sertifikasını imzalama
openssl x509 -req -days 3650 -in redis-server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out redis-server.crt

# İstemci sertifikası oluşturma
openssl genrsa -out redis-client.key 2048
openssl req -new -key redis-client.key -out redis-client.csr -subj "/CN=redis-client"
openssl x509 -req -days 3650 -in redis-client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out redis-client.crt

Redis sunucusu SSL yapılandırması:

# redis.conf
# TLS/SSL etkinleştirme
tls-cert-file /etc/redis/redis-server.crt
tls-key-file /etc/redis/redis-server.key
tls-ca-cert-file /etc/redis/ca.crt

# İstemci sertifikası doğrulama
tls-auth-clients yes
tls-replication yes
tls-cluster yes

# TLS portu kullanma
port 0
tls-port 6379

Spring Boot ile SSL/TLS yapılandırması:

@Configuration
public class RedisSSLConfig {

    @Bean
    public RedisConnectionFactory redisConnectionFactory() throws Exception {
        RedisStandaloneConfiguration redisConfig = new RedisStandaloneConfiguration();
        redisConfig.setHostName("your-redis-host");
        redisConfig.setPort(6379);
        redisConfig.setPassword("your-password");
        
        // SSL yapılandırması
        LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
                .useSsl()
                .and()
                .clientOptions(ClientOptions.builder()
                        .sslOptions(SslOptions.builder()
                                .jdkSslProvider()
                                .truststore(new ClassPathResource("ca.jks"), "password")
                                .keystore(new ClassPathResource("redis-client.jks"), "password")
                                .build())
                        .build())
                .build();
        
        return new LettuceConnectionFactory(redisConfig, clientConfig);
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        return template;
    }
}

SSL/TLS şifreleme, verilerin ağda taşınırken güvenliğini sağlar. Özellikle bulut ortamlarında veya internet üzerinden Redis erişimi gerektiğinde önemlidir.

❓ Soru 3: Redis'te erişim kontrolü nasıl sağlanır?

Cevap: Redis'te erişim kontrolü, belirli kullanıcıların veya uygulamaların belirli komutları veya verileri çalıştırmasını kısıtlamak için kullanılır. Redis, rol tabanlı erişim kontrolü (RBAC) ve komut seviyesinde erişim kontrolü imkanları sunar.

Redis ACL (Access Control List) özellikleri:

  • Kullanıcı adı ve parola ile kimlik doğrulama
  • Rol tabanlı yetkilendirme
  • Komut seviyesinde erişim kontrolü
  • Anahtar (key) seviyesinde erişim kontrolü
  • Kanal (channel) seviyesinde erişim kontrolü

Redis ACL komutları:

# Kullanıcı oluşturma
ACL SETUSER user1 on >password ~* &* -@dangerous +@read

# Kullanıcı listeleme
ACL LIST

# Kullanıcı bilgilerini görme
ACL GETUSER user1

# Rol oluşturma
ACL SETROLE readonly +@read -@write

# Kullanıcıya rol atama
ACL SETUSER user1 >password +@readonly

# Komut izinlerini düzenleme
ACL SETUSER user1 +@string -@list

Java ile Redis ACL kullanımı:

@Service
public class RedisACLService {

    @Autowired
    private RedisConnectionFactory connectionFactory;

    public void createUserWithACL(String username, String password, List<String> allowedCommands) {
        RedisConnection connection = connectionFactory.getConnection();
        
        // ACL komutunu oluştur
        StringBuilder aclCommand = new StringBuilder("ACL SETUSER ")
                .append(username)
                .append(" on >")
                .append(password);
        
        // İzin verilen komutları ekle
        for (String command : allowedCommands) {
            aclCommand.append(" +").append(command);
        }
        
        // Tehlikeli komutları engelle
        aclCommand.append(" -@dangerous");
        
        // Komutu çalıştır
        connection.execute(aclCommand.toString());
    }

    public Map<String, Object> getUserACL(String username) {
        RedisConnection connection = connectionFactory.getConnection();
        
        // Kullanıcı ACL bilgilerini al
        byte[] aclInfo = connection.execute("ACL GETUSER " + username);
        
        // Sonucu işle
        Map<String, Object> aclMap = new HashMap<>();
        if (aclInfo != null) {
            String aclString = new String(aclInfo);
            // ACL bilgilerini ayrıştır
            // ...
        }
        
        return aclMap;
    }
}

Spring Security ile Redis entegrasyonu:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/public/**").permitAll()
                .anyRequest().authenticated()
            .and()
            .formLogin()
                .loginPage("/login")
                .permitAll()
            .and()
            .logout()
                .permitAll();
    }

    @Bean
    public UserDetailsService userDetailsService() {
        return new RedisUserDetailsService(redisTemplate);
    }
}

public class RedisUserDetailsService implements UserDetailsService {

    private final RedisTemplate<String, Object> redisTemplate;

    public RedisUserDetailsService(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // Redis'ten kullanıcı bilgilerini al
        String userKey = "user:" + username;
        Map<Object, Object> userData = redisTemplate.opsForHash().entries(userKey);
        
        if (userData.isEmpty()) {
            throw new UsernameNotFoundException("Kullanıcı bulunamadı: " + username);
        }
        
        String password = (String) userData.get("password");
        Collection<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList("ROLE_USER");
        
        return new User(username, password, authorities);
    }
}

Redis ACL, özellikle çok kullanıcılı ortamlarda ve farklı uygulamaların aynı Redis örneğini paylaştığı durumlarda önemlidir. Bu, yetkisiz erişimleri önler ve veri güvenliğini artırır.

❓ Soru 4: Redis'te veri şifreleme nasıl yapılır?

Cevap: Redis'te veri şifreleme, hassas verilerin güvenli bir şekilde saklanması için kullanılır. Bu, özellikle kişisel bilgiler, finansal veriler veya diğer hassas bilgiler için önemlidir.

Veri şifreleme stratejileri:

  • Uygulama katmanında şifreleme: Veriler Redis'e kaydedilmeden önce şifrelenir
  • Redis modülleri ile şifreleme: Redis'e şifreleme modülleri eklemek
  • Şifrelenmiş Redis (Redis-Ecrypt): Şifreleme özellikli Redis sürümleri kullanmak

Java ile veri şifreleme:

@Service
public class EncryptedRedisService {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    
    private final SecretKey secretKey;
    private final Cipher cipher;
    
    public EncryptedRedisService() throws Exception {
        // Anahtar oluştur (gerçek uygulamada güvenli bir şekilde saklanmalı)
        KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
        keyGenerator.init(256);
        secretKey = keyGenerator.generateKey();
        
        cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    }
    
    public void saveEncryptedData(String key, String value) {
        try {
            // Veriyi şifrele
            byte[] encryptedValue = encrypt(value);
            
            // Base64 ile kodla ve Redis'e kaydet
            String encodedValue = Base64.getEncoder().encodeToString(encryptedValue);
            redisTemplate.opsForValue().set(key, encodedValue);
        } catch (Exception e) {
            throw new RuntimeException("Şifreleme hatası", e);
        }
    }
    
    public String getEncryptedData(String key) {
        try {
            // Redis'ten şifrelenmiş veriyi al
            String encodedValue = redisTemplate.opsForValue().get(key);
            
            if (encodedValue == null) {
                return null;
            }
            
            // Base64'den çöz ve şifreyi çöz
            byte[] encryptedValue = Base64.getDecoder().decode(encodedValue);
            return decrypt(encryptedValue);
        } catch (Exception e) {
            throw new RuntimeException("Şifre çözme hatası", e);
        }
    }
    
    private byte[] encrypt(String value) throws Exception {
        byte[] iv = new byte[16];
        new SecureRandom().nextBytes(iv);
        
        cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv));
        
        byte[] encrypted = cipher.doFinal(value.getBytes());
        
        // IV ve şifrelenmiş veriyi birleştir
        byte[] combined = new byte[iv.length + encrypted.length];
        System.arraycopy(iv, 0, combined, 0, iv.length);
        System.arraycopy(encrypted, 0, combined, iv.length, encrypted.length);
        
        return combined;
    }
    
    private String decrypt(byte[] encryptedValue) throws Exception {
        // IV ve şifrelenmiş veriyi ayır
        byte[] iv = new byte[16];
        System.arraycopy(encryptedValue, 0, iv, 0, iv.length);
        
        byte[] encrypted = new byte[encryptedValue.length - 16];
        System.arraycopy(encryptedValue, 16, encrypted, 0, encrypted.length);
        
        cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
        
        byte[] decrypted = cipher.doFinal(encrypted);
        return new String(decrypted);
    }
}

Spring Security ile veri şifreleme:

@Service
public class SecureUserService {

    @Autowired
    private EncryptedRedisService encryptedRedisService;
    
    public void saveUser(User user) {
        // Hassas verileri şifrele
        String encryptedSSN = encryptSSN(user.getSsn());
        String encryptedCreditCard = encryptCreditCard(user.getCreditCard());
        
        // Şifrelenmiş verileri Redis'e kaydet
        encryptedRedisService.saveEncryptedData("user:" + user.getId() + ":ssn", encryptedSSN);
        encryptedRedisService.saveEncryptedData("user:" + user.getId() + ":creditCard", encryptedCreditCard);
        
        // Diğer kullanıcı bilgilerini kaydet
        // ...
    }
    
    public User getUser(Long userId) {
        // Kullanıcı bilgilerini Redis'ten al
        String encryptedSSN = encryptedRedisService.getEncryptedData("user:" + userId + ":ssn");
        String encryptedCreditCard = encryptedRedisService.getEncryptedData("user:" + userId + ":creditCard");
        
        // Şifrelenmiş verileri çöz
        String ssn = decryptSSN(encryptedSSN);
        String creditCard = decryptCreditCard(encryptedCreditCard);
        
        // Kullanıcı nesnesini oluştur ve döndür
        User user = new User();
        user.setId(userId);
        user.setSsn(ssn);
        user.setCreditCard(creditCard);
        
        return user;
    }
    
    private String encryptSSN(String ssn) {
        // SSN şifreleme mantığı
        return ssn; // Basitleştirilmiş
    }
    
    private String decryptSSN(String encryptedSSN) {
        // SSN şifre çözme mantığı
        return encryptedSSN; // Basitleştirilmiş
    }
    
    private String encryptCreditCard(String creditCard) {
        // Kredi kartı şifreleme mantığı
        return creditCard; // Basitleştirilmiş
    }
    
    private String decryptCreditCard(String encryptedCreditCard) {
        // Kredi kartı şifre çözme mantığı
        return encryptedCreditCard; // Basitleştirilmiş
    }
}

Veri şifreleme, özellikle düzenlemelere uyum sağlamak (GDPR, PCI DSS vb.) ve hassas verileri korumak için önemlidir. Doğru şifreleme stratejileri ve anahtar yönetimi ile veri güvenliği artırılabilir.

❓ Soru 5: Redis'te güvenlik denetimi ve loglama nasıl yapılır?

Cevap: Redis'te güvenlik denetimi ve loglama, güvenlik olaylarını izlemek, yetkisiz erişimleri tespit etmek ve güvenlik ihlallerini analiz etmek için önemlidir. Bu, Redis güvenliğini proaktif olarak yönetmeye yardımcı olur.

Güvenlik denetimi ve loglama stratejileri:

  • Komut loglama: Tüm Redis komutlarını loglama
  • Erişim loglama: Bağlantı ve kimlik doğrulama logları
  • Güvenlik olayları: Başarısız giriş denemeleri, yetkisiz erişimler
  • Audit trail: Veri değişikliklerinin kaydedilmesi
  • SIEM entegrasyonu: Güvenlik bilgilerini SIEM sistemlerine gönderme

Redis güvenlik log yapılandırması:

# redis.conf
# Log seviyesi
loglevel verbose

# Log dosyası
logfile /var/log/redis/redis-server.log

# Yavaş sorgu logu
slowlog-log-slower-than 10000
slowlog-max-len 128

# İstemci logları
# Redis 6.0 ve sonrası için
acllog-max-len 128

Java ile Redis güvenlik loglama:

@Service
public class RedisSecurityAuditService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    private static final Logger securityLogger = LoggerFactory.getLogger("REDIS_SECURITY_AUDIT");
    
    @PostConstruct
    public void initSecurityAudit() {
        // Güvenlik loglarını yapılandır
        configureRedisLogging();
    }
    
    private void configureRedisLogging() {
        // RedisTemplate için özel loglama
        ((LettuceConnectionFactory) redisTemplate.getConnectionFactory())
                .setShareNativeConnection(false);
        
        // Güvenlik olaylarını dinle
        securityLogger.info("Redis güvenlik denetimi başlatıldı");
    }
    
    public void logSecurityEvent(String eventType, String username, String details) {
        // Güvenlik olayını logla
        String logMessage = String.format("Güvenlik olayı: %s, Kullanıcı: %s, Detaylar: %s", 
                eventType, username, details);
        
        securityLogger.warn(logMessage);
        
        // Güvenlik olayını Redis'e kaydet
        String eventKey = "security:events:" + System.currentTimeMillis();
        Map<String, Object> eventData = new HashMap<>();
        eventData.put("eventType", eventType);
        eventData.put("username", username);
        eventData.put("details", details);
        eventData.put("timestamp", System.currentTimeMillis());
        
        redisTemplate.opsForHash().putAll(eventKey, eventData);
        redisTemplate.expire(eventKey, 30, TimeUnit.DAYS);
    }
    
    public List<Map<String, Object>> getSecurityEvents() {
        // Tüm güvenlik olaylarını getir
        Set<String> eventKeys = redisTemplate.keys("security:events:*");
        List<Map<String, Object>> events = new ArrayList<>();
        
        for (String eventKey : eventKeys) {
            Map<Object, Object> eventData = redisTemplate.opsForHash().entries(eventKey);
            
            @SuppressWarnings("unchecked")
            Map<String, Object> event = (Map<String, Object>) (Object) eventData;
            events.add(event);
        }
        
        // Tarihe göre sırala
        events.sort((e1, e2) -> {
            long timestamp1 = (long) e1.get("timestamp");
            long timestamp2 = (long) e2.get("timestamp");
            return Long.compare(timestamp2, timestamp1);
        });
        
        return events;
    }
}

Redis komutlarını loglama:

@Aspect
@Component
public class RedisCommandLoggingAspect {

    private static final Logger commandLogger = LoggerFactory.getLogger("REDIS_COMMANDS");
    
    @Around("execution(* org.springframework.data.redis.core.RedisTemplate.*(..))")
    public Object logRedisCommand(ProceedingJoinPoint joinPoint) throws Throwable {
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        
        // Komut bilgilerini logla
        commandLogger.debug("Redis komutu: {}, Argümanlar: {}", methodName, Arrays.toString(args));
        
        try {
            // Komutu çalıştır
            Object result = joinPoint.proceed();
            
            // Sonucu logla
            commandLogger.debug("Redis komutu sonucu: {}", result);
            
            return result;
        } catch (Exception e) {
            // Hatayı logla
            commandLogger.error("Redis komutu hatası: {}, Hata: {}", methodName, e.getMessage(), e);
            throw e;
        }
    }
}

SIEM entegrasyonu:

@Service
public class SiemIntegrationService {

    @Autowired
    private RedisSecurityAuditService securityAuditService;
    
    @Scheduled(fixedRate = 60000)  // Her dakika çalıştır
    public void sendSecurityEventsToSIEM() {
        // Güvenlik olaylarını getir
        List<Map<String, Object>> events = securityAuditService.getSecurityEvents();
        
        // SIEM sistemine gönder
        for (Map<String, Object> event : events) {
            sendToSIEM(event);
        }
    }
    
    private void sendToSIEM(Map<String, Object> event) {
        // SIEM sistemine gönderme mantığı
        // Örneğin, Syslog, REST API veya Kafka kullanarak
        
        String siemMessage = String.format(
                "<134>1 %s %s redis-security - - - Redis güvenlik olayı: %s, Kullanıcı: %s, Detaylar: %s",
                new Date((Long) event.get("timestamp")),
                InetAddress.getHostName(),
                event.get("eventType"),
                event.get("username"),
                event.get("details")
        );
        
        // Syslog sunucusuna gönder
        // ...
    }
}

Redis güvenlik denetimi ve loglama, güvenlik olaylarını izlemek ve analiz etmek için önemlidir. Bu, özellikle düzenlemelere uyum sağlamak ve güvenlik ihlallerini tespit etmek için kritik öneme sahiptir.

❓ Soru 6: Redis'te güvenlik açıkları nasıl tespit edilir ve önlenir?

Cevap: Redis'te güvenlik açıkları, yetkisiz erişim, veri sızıntıları veya hizmet reddi saldırıları gibi güvenlik sorunlarıdır. Bu açıkları tespit etmek ve önlemek için çeşitli yöntemler kullanılır.

Yaygın Redis güvenlik açıkları:

  • Varsayılan parola veya parolasız erişim: Redis sunucusunun varsayılan yapılandırmada çalıştırılması
  • Açık portlar: Redis sunucusunun internete açık portları
  • Zayıf şifreleme: SSL/TLS kullanılmaması veya zayıf şifreleme algoritmaları
  • Komut enjeksiyonu: Kullanıcı girdilerinin doğrulanmaması
  • DoS saldırıları: Redis sunucusunu aşırı yükleme
  • Veri sızıntısı: Hassas verilerin korunmaması

Güvenlik açıklarını tespit etme yöntemleri:

# Redis sunucusu güvenlik denetimi
nmap -p 6379 --script redis-info target-ip

# Zayıf parola taraması
nmap -p 6379 --script redis-brute target-ip

# SSL/TLS kontrolü
openssl s_client -connect target-ip:6379

# Redis sürüm ve güvenlik açığı kontrolü
redis-cli -h target-ip -p 6379 INFO server

Java ile güvenlik açığı taraması:

@Service
public class RedisVulnerabilityScanner {

    @Autowired
    private RedisConnectionFactory connectionFactory;
    
    private static final Logger vulnerabilityLogger = LoggerFactory.getLogger("REDIS_VULNERABILITY_SCANNER");
    
    public void scanForVulnerabilities() {
        vulnerabilityLogger.info("Redis güvenlik taraması başlatıldı");
        
        // Varsayılan parola kontrolü
        checkDefaultPassword();
        
        // Açık port kontrolü
        checkOpenPorts();
        
        // SSL/TLS kontrolü
        checkSSLTLS();
        
        // Sürüm ve güvenlik açığı kontrolü
        checkVersionVulnerabilities();
        
        // Komut enjeksiyon kontrolü
        checkCommandInjection();
        
        vulnerabilityLogger.info("Redis güvenlik taraması tamamlandı");
    }
    
    private void checkDefaultPassword() {
        try {
            // Varsayılan parola ile bağlanmayı dene
            RedisStandaloneConfiguration redisConfig = new RedisStandaloneConfiguration();
            redisConfig.setHostName("localhost");
            redisConfig.setPort(6379);
            
            LettuceConnectionFactory connectionFactory = new LettuceConnectionFactory(redisConfig);
            connectionFactory.afterPropertiesSet();
            
            RedisConnection connection = connectionFactory.getConnection();
            
            try {
                // Bilinen varsayılan parolaları dene
                String[] defaultPasswords = {"", "redis", "pass", "123456"};
                
                for (String password : defaultPasswords) {
                    try {
                        connection.auth(password.getBytes());
                        vulnerabilityLogger.warn("Varsayılan parola tespit edildi: {}", password);
                        break;
                    } catch (Exception e) {
                        // Parola yanlış, devam et
                    }
                }
            } finally {
                connection.close();
            }
        } catch (Exception e) {
            vulnerabilityLogger.error("Varsayılan parola kontrolü sırasında hata: {}", e.getMessage());
        }
    }
    
    private void checkOpenPorts() {
        try {
            // Redis portunun açık olup olmadığını kontrol et
            Socket socket = new Socket();
            socket.connect(new InetSocketAddress("localhost", 6379), 1000);
            socket.close();
            
            vulnerabilityLogger.info("Redis portu (6379) açık");
            
            // Diğer olası Redis portlarını kontrol et
            int[] possiblePorts = {6380, 6379, 16379};
            
            for (int port : possiblePorts) {
                try {
                    socket = new Socket();
                    socket.connect(new InetSocketAddress("localhost", port), 1000);
                    socket.close();
                    
                    if (port != 6379) {
                        vulnerabilityLogger.warn("Diğer Redis portu açık: {}", port);
                    }
                } catch (IOException e) {
                    // Port kapalı, devam et
                }
            }
        } catch (Exception e) {
            vulnerabilityLogger.error("Açık port kontrolü sırasında hata: {}", e.getMessage());
        }
    }
    
    private void checkSSLTLS() {
        try {
            // SSL/TLS kullanılıp kullanılmadığını kontrol et
            RedisConnection connection = connectionFactory.getConnection();
            
            // SSL/TLS bilgilerini al
            Properties info = connection.serverCommands().info("tls");
            
            if (info.isEmpty()) {
                vulnerabilityLogger.warn("SSL/TLS kullanılmıyor");
            } else {
                vulnerabilityLogger.info("SSL/TLS kullanılıyor: {}", info);
            }
            
            connection.close();
        } catch (Exception e) {
            vulnerabilityLogger.error("SSL/TLS kontrolü sırasında hata: {}", e.getMessage());
        }
    }
    
    private void checkVersionVulnerabilities() {
        try {
            // Redis sürümünü al
            RedisConnection connection = connectionFactory.getConnection();
            Properties info = connection.serverCommands().info("server");
            
            String version = info.getProperty("redis_version");
            
            // Bilinen güvenlik açıklarını kontrol et
            if (version != null) {
                vulnerabilityLogger.info("Redis sürümü: {}", version);
                
                // Örnek: CVE-2022-0543 (Redis 7.0.0'dan önceki sürümlerde)
                if (version.compareTo("7.0.0") < 0) {
                    vulnerabilityLogger.warn("CVE-2022-0543 güvenlik açığı: Lua sandbox escape");
                }
                
                // Diğer bilinen güvenlik açıklarını burada kontrol et
                // ...
            }
            
            connection.close();
        } catch (Exception e) {
            vulnerabilityLogger.error("Sürüm kontrolü sırasında hata: {}", e.getMessage());
        }
    }
    
    private void checkCommandInjection() {
        // Komut enjeksiyon açıklarını kontrol et
        vulnerabilityLogger.info("Komut enjeksiyon kontrolü: Uygulama katmanında doğrulama yapılmalı");
    }
}

Güvenlik açıklarını önleme yöntemleri:

@Service
public class RedisSecurityHardeningService {

    @Autowired
    private RedisConnectionFactory connectionFactory;
    
    public void hardenRedisSecurity() {
        // Güçlü parola ayarla
        setStrongPassword();
        
        // Tehlikeli komutları devre dışı bırak
        disableDangerousCommands();
        
        // SSL/TLS etkinleştir
        enableSSLTLS();
        
        // Ağ erişimini kısıtla
        restrictNetworkAccess();
        
        // Güvenlik loglarını etkinleştir
        enableSecurityLogging();
    }
    
    private void setStrongPassword() {
        // Güçlü bir parola oluştur ve ayarla
        String strongPassword = generateStrongPassword();
        
        // Redis sunucusuna parolayı ayarla
        RedisConnection connection = connectionFactory.getConnection();
        try {
            connection.execute("CONFIG SET requirepass " + strongPassword);
            connection.execute("CONFIG REWRITE");
        } finally {
            connection.close();
        }
    }
    
    private String generateStrongPassword() {
        // Güçlü bir parola oluştur
        String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()";
        StringBuilder password = new StringBuilder();
        Random random = new Random();
        
        for (int i = 0; i < 16; i++) {
            password.append(chars.charAt(random.nextInt(chars.length())));
        }
        
        return password.toString();
    }
    
    private void disableDangerousCommands() {
        // Tehlikeli komutları yeniden adlandır veya devre dışı bırak
        RedisConnection connection = connectionFactory.getConnection();
        try {
            // FLUSHDB, FLUSHALL, CONFIG, SHUTDOWN komutlarını devre dışı bırak
            connection.execute("RENAME-COMMAND FLUSHDB \"\"");
            connection.execute("RENAME-COMMAND FLUSHALL \"\"");
            connection.execute("RENAME-COMMAND CONFIG \"\"");
            connection.execute("RENAME-COMMAND SHUTDOWN \"\"");
            
            // Değişiklikleri kaydet
            connection.execute("CONFIG REWRITE");
        } finally {
            connection.close();
        }
    }
    
    private void enableSSLTLS() {
        // SSL/TLS etkinleştir
        // Bu, Redis sunucusu yapılandırmasında yapılmalı
        // ...
    }
    
    private void restrictNetworkAccess() {
        // Ağ erişimini kısıtla
        // Bu, güvenlik duvarı veya Redis yapılandırmasında yapılmalı
        // ...
    }
    
    private void enableSecurityLogging() {
        // Güvenlik loglarını etkinleştir
        // ...
    }
}

Redis güvenlik açıklarını tespit etmek ve önlemek, Redis sunucusunun güvenliğini sağlamak için önemlidir. Düzenli güvenlik taramaları ve sertleştirme adımları, güvenlik ihlallerini önlemeye yardımcı olur.

❓ Soru 7: Redis'te güvenlik duvarı ve ağ erişim kontrolleri nasıl yapılandırılır?

Cevap: Redis'te güvenlik duvarı ve ağ erişim kontrolleri, yetkisiz erişimi önlemek ve Redis sunucusunu korumak için kullanılır. Bu, özellikle internet üzerinden erişilen Redis sunucuları için önemlidir.

Güvenlik duvarı ve ağ erişim kontrol stratejileri:

  • İşletim sistemi güvenlik duvarı: Sunucu düzeyinde port erişimini kısıtlama
  • Redis bind ayarı: Redis'in sadece belirli IP adreslerinden bağlantı kabul etmesi
  • Ağ segmentasyon: Redis sunucusunu ayrı bir ağ segmentinde çalıştırma
  • VPN veya SSH tüneli: Redis erişimi için güvenli bağlantılar kullanma
  • Bulut güvenlik grupları: Bulut ortamlarında ağ erişimini kısıtlama

Redis bind yapılandırması:

# redis.conf
# Yerel bağlantılara izin ver
bind 127.0.0.1

# Özel IP adreslerine izin ver
bind 192.168.1.100

# Birden fazla IP adresi
bind 127.0.0.1 192.168.1.100

# Tüm arayüzlerde dinle (güvenli değil)
# bind 0.0.0.0

Linux güvenlik duvarı (iptables) yapılandırması:

# Redis portuna erişimi kısıtla
iptables -A INPUT -p tcp --dport 6379 -s 192.168.1.0/24 -j ACCEPT
iptables -A INPUT -p tcp --dport 6379 -j DROP

# Kuralları kaydet
service iptables save

# veya
iptables-save > /etc/iptables/rules.v4

AWS güvenlik grubu yapılandırması:

{
  "IpPermissions": [
    {
      "FromPort": 6379,
      "ToPort": 6379,
      "IpProtocol": "tcp",
      "IpRanges": [
        {
          "CidrIp": "192.168.1.0/24",
          "Description": "Redis erişimi"
        }
      ]
    }
  ]
}

Kubernetes NetworkPolicy yapılandırması:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: redis-network-policy
spec:
  podSelector:
    matchLabels:
      app: redis
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: backend
    - namespaceSelector:
        matchLabels:
          name: production
  - ports:
    - protocol: TCP
      port: 6379

Docker ağ yapılandırması:

version: '3.8'
services:
  redis:
    image: redis:alpine
    ports:
      - "6379:6379"
    networks:
      - backend
    environment:
      - REDIS_PASSWORD=yourpassword
  
  backend:
    image: your-backend-app
    depends_on:
      - redis
    networks:
      - backend

networks:
  backend:
    driver: bridge

VPN ile Redis erişimi:

# SSH tüneli oluşturma
ssh -L 6379:localhost:6379 user@redis-server

# veya
ssh -N -L 6379:localhost:6379 user@redis-server

Güvenlik duvarı ve ağ erişim kontrolleri, Redis sunucusunu yetkisiz erişimlere karşı korumak için önemlidir. Bu kontroller, özellikle internet üzerinden erişilen Redis sunucuları için kritik öneme sahiptir.

❓ Soru 8: Redis'te güvenlik testi nasıl yapılır?

Cevap: Redis'te güvenlik testi, Redis sunucusunun güvenlik önlemlerini değerlendirmek ve güvenlik açıklarını tespit etmek için kullanılır. Bu testler, manuel veya otomatik araçlarla yapılabilir.

Güvenlik testi yöntemleri:

  • Penetrasyon testi: Redis sunucusuna gerçek saldırıları simüle etme
  • Vulnerability scanning: Otomatik araçlarla güvenlik açıklarını tarama
  • Configuration audit: Redis yapılandırmasının güvenlik açıklarını kontrol etme
  • Access control test: Erişim kontrollerinin etkinliğini test etme
  • Encryption test: Şifrelemenin doğru çalıştığını doğrulama

Redis güvenlik test araçları:

# Nmap ile Redis tarama
nmap -p 6379 --script redis-info target-ip

# Nmap ile Redis zayıf parola taraması
nmap -p 6379 --script redis-brute target-ip

# Redis-trib.rb ile cluster güvenlik testi
redis-trib.rb check target-ip:6379

# Redis-cli ile güvenlik testi
redis-cli -h target-ip -p 6379 -a testpassword ping

Java ile güvenlik testi:

@Service
public class RedisSecurityTestService {

    @Autowired
    private RedisConnectionFactory connectionFactory;
    
    private static final Logger securityTestLogger = LoggerFactory.getLogger("REDIS_SECURITY_TEST");
    
    public void performSecurityTests() {
        securityTestLogger.info("Redis güvenlik testleri başlatıldı");
        
        // Bağlantı güvenlik testi
        testConnectionSecurity();
        
        // Kimlik doğrulama testi
        testAuthentication();
        
        // Yetkilendirme testi
        testAuthorization();
        
        // Şifreleme testi
        testEncryption();
        
        // Komut enjeksiyon testi
        testCommandInjection();
        
        securityTestLogger.info("Redis güvenlik testleri tamamlandı");
    }
    
    private void testConnectionSecurity() {
        securityTestLogger.info("Bağlantı güvenlik testi başlatıldı");
        
        try {
            // Güvenli olmayan bağlantıyı dene
            RedisStandaloneConfiguration insecureConfig = new RedisStandaloneConfiguration();
            insecureConfig.setHostName("localhost");
            insecureConfig.setPort(6379);
            
            LettuceConnectionFactory insecureFactory = new LettuceConnectionFactory(insecureConfig);
            insecureFactory.afterPropertiesSet();
            
            RedisConnection insecureConnection = insecureFactory.getConnection();
            
            // Bağlantı başarılı oldu mu?
            securityTestLogger.warn("Güvenli olmayan bağlantı başarılı: {}", insecureConnection.ping());
            
            insecureConnection.close();
        } catch (Exception e) {
            securityTestLogger.info("Güvenli olmayan bağlantı başarısız: {}", e.getMessage());
        }
        
        try {
            // Güvenli bağlantıyı dene
            RedisStandaloneConfiguration secureConfig = new RedisStandaloneConfiguration();
            secureConfig.setHostName("localhost");
            secureConfig.setPort(6379);
            secureConfig.setPassword("yourpassword");
            
            LettuceConnectionFactory secureFactory = new LettuceConnectionFactory(secureConfig);
            secureFactory.afterPropertiesSet();
            
            RedisConnection secureConnection = secureFactory.getConnection();
            
            // Bağlantı başarılı oldu mu?
            securityTestLogger.info("Güvenli bağlantı başarılı: {}", secureConnection.ping());
            
            secureConnection.close();
        } catch (Exception e) {
            securityTestLogger.error("Güvenli bağlantı başarısız: {}", e.getMessage());
        }
    }
    
    private void testAuthentication() {
        securityTestLogger.info("Kimlik doğrulama testi başlatıldı");
        
        try {
            // Yanlış parola ile bağlanmayı dene
            RedisStandaloneConfiguration wrongConfig = new RedisStandaloneConfiguration();
            wrongConfig.setHostName("localhost");
            wrongConfig.setPort(6379);
            wrongConfig.setPassword("wrongpassword");
            
            LettuceConnectionFactory wrongFactory = new LettuceConnectionFactory(wrongConfig);
            wrongFactory.afterPropertiesSet();
            
            RedisConnection wrongConnection = wrongFactory.getConnection();
            
            // Kimlik doğrulama başarısız olmalı
            securityTestLogger.warn("Yanlış parola ile kimlik doğrulama başarılı: {}", wrongConnection.ping());
            
            wrongConnection.close();
        } catch (Exception e) {
            securityTestLogger.info("Yanlış parola ile kimlik doğrulama başarısız: {}", e.getMessage());
        }
    }
    
    private void testAuthorization() {
        securityTestLogger.info("Yetkilendirme testi başlatıldı");
        
        try {
            // Sınırlı kullanıcı ile bağlan
            RedisStandaloneConfiguration limitedConfig = new RedisStandaloneConfiguration();
            limitedConfig.setHostName("localhost");
            limitedConfig.setPort(6379);
            limitedConfig.setPassword("limiteduser");
            
            LettuceConnectionFactory limitedFactory = new LettuceConnectionFactory(limitedConfig);
            limitedFactory.afterPropertiesSet();
            
            RedisConnection limitedConnection = limitedFactory.getConnection();
            
            // İzin verilen komutu dene
            try {
                limitedConnection.ping();
                securityTestLogger.info("İzin verilen komut başarılı: ping");
            } catch (Exception e) {
                securityTestLogger.error("İzin verilen komut başarısız: {}", e.getMessage());
            }
            
            // İzin verilmeyen komutu dene
            try {
                limitedConnection.execute("FLUSHDB");
                securityTestLogger.warn("İzin verilmeyen komut başarılı: FLUSHDB");
            } catch (Exception e) {
                securityTestLogger.info("İzin verilmeyen komut başarısız: {}", e.getMessage());
            }
            
            limitedConnection.close();
        } catch (Exception e) {
            securityTestLogger.error("Yetkilendirme testi sırasında hata: {}", e.getMessage());
        }
    }
    
    private void testEncryption() {
        securityTestLogger.info("Şifreleme testi başlatıldı");
        
        try {
            // Şifrelenmemiş veri gönder
            RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
            config.setHostName("localhost");
            config.setPort(6379);
            config.setPassword("yourpassword");
            
            LettuceConnectionFactory factory = new LettuceConnectionFactory(config);
            factory.afterPropertiesSet();
            
            RedisConnection connection = factory.getConnection();
            
            // Test verisi gönder
            String testData = "Güvenlik testi verisi";
            connection.set("test:key".getBytes(), testData.getBytes());
            
            // Test verisini al
            byte[] receivedData = connection.get("test:key".getBytes());
            
            if (receivedData != null) {
                String receivedString = new String(receivedData);
                securityTestLogger.info("Şifrelenmemiş veri alındı: {}", receivedString);
            } else {
                securityTestLogger.warn("Şifrelenmemiş veri alınamadı");
            }
            
            connection.close();
        } catch (Exception e) {
            securityTestLogger.error("Şifreleme testi sırasında hata: {}", e.getMessage());
        }
    }
    
    private void testCommandInjection() {
        securityTestLogger.info("Komut enjeksiyon testi başlatıldı");
        
        try {
            // Zararlı komut enjeksiyonunu dene
            RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
            config.setHostName("localhost");
            config.setPort(6379);
            config.setPassword("yourpassword");
            
            LettuceConnectionFactory factory = new LettuceConnectionFactory(config);
            factory.afterPropertiesSet();
            
            RedisConnection connection = factory.getConnection();
            
            // Normal komut
            try {
                connection.set("safe:key".getBytes(), "safe value".getBytes());
                securityTestLogger.info("Normal komut başarılı");
            } catch (Exception e) {
                securityTestLogger.error("Normal komut başarısız: {}", e.getMessage());
            }
            
            // Potansiyel zararlı komut
            try {
                // Anahtarda özel karakterler içeren komut
                connection.set("safe:key;FLUSHDB".getBytes(), "injected value".getBytes());
                securityTestLogger.warn("Potansiyel zararlı komut başarılı");
            } catch (Exception e) {
                securityTestLogger.info("Potansiyel zararlı komut başarısız: {}", e.getMessage());
            }
            
            connection.close();
        } catch (Exception e) {
            securityTestLogger.error("Komut enjeksiyon testi sırasında hata: {}", e.getMessage());
        }
    }
}

Güvenlik testi raporlama:

@Service
public class RedisSecurityReportService {

    @Autowired
    private RedisSecurityTestService securityTestService;
    
    public void generateSecurityReport() {
        // Güvenlik testlerini çalıştır
        securityTestService.performSecurityTests();
        
        // Test sonuçlarını topla
        Map<String, Object> testResults = collectTestResults();
        
        // Güvenlik raporu oluştur
        String report = generateReport(testResults);
        
        // Raporu logla veya kaydet
        System.out.println(report);
    }
    
    private Map<String, Object> collectTestResults() {
        Map<String, Object> results = new HashMap<>();
        
        // Bağlantı güvenlik testi sonuçları
        results.put("connectionSecurity", Map.of(
            "insecureConnection", false,
            "secureConnection", true
        ));
        
        // Kimlik doğrulama testi sonuçları
        results.put("authentication", Map.of(
            "wrongPasswordRejected", true
        ));
        
        // Yetkilendirme testi sonuçları
        results.put("authorization", Map.of(
            "allowedCommandsSuccessful", true,
            "disallowedCommandsRejected", true
        ));
        
        // Şifreleme testi sonuçları
        results.put("encryption", Map.of(
            "dataTransferredSuccessfully", true
        ));
        
        // Komut enjeksiyon testi sonuçları
        results.put("commandInjection", Map.of(
            "normalCommandsSuccessful", true,
            "injectionAttemptsRejected", true
        ));
        
        return results;
    }
    
    private String generateReport(Map<String, Object> testResults) {
        StringBuilder report = new StringBuilder();
        report.append("Redis Güvenlik Test Raporu\n");
        report.append("=========================\n\n");
        
        // Bağlantı güvenliği
        Map<String, Object> connectionResults = (Map<String, Object>) testResults.get("connectionSecurity");
        report.append("Bağlantı Güvenliği:\n");
        report.append(String.format("- Güvenli olmayan bağlantı: %s\n", 
                connectionResults.get("insecureConnection") ? "BAŞARISIZ" : "BAŞARILI"));
        report.append(String.format("- Güvenli bağlantı: %s\n\n", 
                connectionResults.get("secureConnection") ? "BAŞARILI" : "BAŞARISIZ"));
        
        // Kimlik doğrulama
        Map<String, Object> authResults = (Map<String, Object>) testResults.get("authentication");
        report.append("Kimlik Doğrulama:\n");
        report.append(String.format("- Yanlış parola reddedildi: %s\n\n", 
                authResults.get("wrongPasswordRejected") ? "BAŞARILI" : "BAŞARISIZ"));
        
        // Yetkilendirme
        Map<String, Object> authzResults = (Map<String, Object>) testResults.get("authorization");
        report.append("Yetkilendirme:\n");
        report.append(String.format("- İzin verilen komutlar: %s\n", 
                authzResults.get("allowedCommandsSuccessful") ? "BAŞARILI" : "BAŞARISIZ"));
        report.append(String.format("- İzin verilmeyen komutlar: %s\n\n", 
                authzResults.get("disallowedCommandsRejected") ? "BAŞARILI" : "BAŞARISIZ"));
        
        // Şifreleme
        Map<String, Object> encResults = (Map<String, Object>) testResults.get("encryption");
        report.append("Şifreleme:\n");
        report.append(String.format("- Veri transferi: %s\n\n", 
                encResults.get("dataTransferredSuccessfully") ? "BAŞARILI" : "BAŞARISIZ"));
        
        // Komut enjeksiyonu
        Map<String, Object> injResults = (Map<String, Object>) testResults.get("commandInjection");
        report.append("Komut Enjeksiyonu:\n");
        report.append(String.format("- Normal komutlar: %s\n", 
                injResults.get("normalCommandsSuccessful") ? "BAŞARILI" : "BAŞARISIZ"));
        report.append(String.format("- Enjeksiyon denemeleri: %s\n\n", 
                injResults.get("injectionAttemptsRejected") ? "BAŞARILI" : "BAŞARISIZ"));
        
        // Genel değerlendirme
        boolean allTestsPassed = true;
        for (Map.Entry<String, Object> entry : testResults.entrySet()) {
            Map<String, Object> categoryResults = (Map<String, Object>) entry.getValue();
            for (Map.Entry<String, Object> testEntry : categoryResults.entrySet()) {
                if (!Boolean.TRUE.equals(testEntry.getValue())) {
                    allTestsPassed = false;
                    break;
                }
            }
            if (!allTestsPassed) {
                break;
            }
        }
        
        report.append("Genel Değerlendirme: ");
        report.append(allTestsPassed ? "TÜM TESTLER BAŞARILI" : "Bazı testler başarısız");
        
        return report.toString();
    }
}

Redis güvenlik testi, Redis sunucusunun güvenlik önlemlerini değerlendirmek ve güvenlik açıklarını tespit etmek için önemlidir. Düzenli güvenlik testleri, Redis sunucusunun güvenliğini sağlamak için kritik öneme sahiptir.

❓ Soru 9: Redis'te yedekleme ve kurtarma stratejileri nelerdir?

Cevap: Redis'te yedekleme ve kurtarma stratejileri, veri kaybını önlemek ve felaket durumlarında veri kurtarmak için önemlidir. Bu stratejiler, veri güvenliğini ve iş sürekliliğini sağlar.

Yedekleme stratejileri:

  • RDB yedeklemesi: Anlık görüntü dosyalarını yedekleme
  • AOF yedeklemesi: Append-only dosyalarını yedekleme
  • Veri aktarımı: Redis verilerini başka bir sunucuya kopyalama
  • Cluster yedeklemesi: Redis Cluster verilerini yedekleme
  • Bulut yedeklemesi: Verileri bulut depolama hizmetlerine yedekleme

RDB yedeklemesi:

# Manuel RDB yedeklemesi
redis-cli --rdb /path/to/backup/dump.rdb

# veya BGSAVE komutu ile arka planda yedekleme
redis-cli BGSAVE

# Yedekleme dosyasını kontrol et
ls -la /path/to/backup/dump.rdb

AOF yedeklemesi:

# AOF dosyasını yedekleme
cp /var/lib/redis/appendonly.aof /path/to/backup/appendonly-$(date +%Y%m%d).aof

# AOF dosyasını sıkıştır
gzip /path/to/backup/appendonly-$(date +%Y%m%d).aof

Veri aktarımı:

# Kaynak Redis sunucusundan verileri aktar
redis-cli --host source-redis --pipe < data.txt

# veya
redis-cli --host source-redis --rdb /path/to/dump.rdb
redis-cli --host target-redis --pipe < /path/to/dump.rdb

Java ile yedekleme:

@Service
public class RedisBackupService {

    @Autowired
    private RedisConnectionFactory connectionFactory;
    
    private static final Logger backupLogger = LoggerFactory.getLogger("REDIS_BACKUP");
    
    public void createRDBBackup() {
        backupLogger.info("RDB yedeklemesi başlatıldı");
        
        try {
            RedisConnection connection = connectionFactory.getConnection();
            
            // BGSAVE komutunu çalıştır
            String result = connection.execute("BGSAVE").toString();
            backupLogger.info("BGSAVE komutu sonucu: {}", result);
            
            // Yedeklemenin tamamlanmasını bekle
            waitForBackupCompletion(connection);
            
            // Yedekleme dosyasını kopyala
            copyRDBFile();
            
            connection.close();
        } catch (Exception e) {
            backupLogger.error("RDB yedeklemesi sırasında hata: {}", e.getMessage());
        }
    }
    
    private void waitForBackupCompletion(RedisConnection connection) {
        backupLogger.info("Yedekleme tamamlanana kadar bekleniyor...");
        
        while (true) {
            try {
                // Yedekleme durumunu kontrol et
                String info = connection.execute("INFO").toString();
                
                if (info.contains("rdb_bgsave_in_progress:0")) {
                    backupLogger.info("Yedekleme tamamlandı");
                    break;
                }
                
                // 1 saniye bekle
                Thread.sleep(1000);
            } catch (Exception e) {
                backupLogger.error("Yedekleme durumu kontrol edilirken hata: {}", e.getMessage());
                break;
            }
        }
    }
    
    private void copyRDBFile() {
        try {
            // RDB dosyasını kopyala
            Path sourcePath = Paths.get("/var/lib/redis/dump.rdb");
            Path backupDir = Paths.get("/path/to/backup");
            
            if (!Files.exists(backupDir)) {
                Files.createDirectories(backupDir);
            }
            
            String timestamp = new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date());
            Path backupPath = backupDir.resolve("dump-" + timestamp + ".rdb");
            
            Files.copy(sourcePath, backupPath, StandardCopyOption.REPLACE_EXISTING);
            
            backupLogger.info("RDB dosyası yedeklendi: {}", backupPath);
        } catch (Exception e) {
            backupLogger.error("RDB dosyası kopyalanırken hata: {}", e.getMessage());
        }
    }
    
    public void createAOFBackup() {
        backupLogger.info("AOF yedeklemesi başlatıldı");
        
        try {
            // AOF dosyasını kopyala
            Path sourcePath = Paths.get("/var/lib/redis/appendonly.aof");
            Path backupDir = Paths.get("/path/to/backup");
            
            if (!Files.exists(backupDir)) {
                Files.createDirectories(backupDir);
            }
            
            String timestamp = new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date());
            Path backupPath = backupDir.resolve("appendonly-" + timestamp + ".aof");
            
            Files.copy(sourcePath, backupPath, StandardCopyOption.REPLACE_EXISTING);
            
            backupLogger.info("AOF dosyası yedeklendi: {}", backupPath);
        } catch (Exception e) {
            backupLogger.error("AOF yedeklemesi sırasında hata: {}", e.getMessage());
        }
    }
    
    public void createDataBackup() {
        backupLogger.info("Veri yedeklemesi başlatıldı");
        
        try {
            RedisConnection connection = connectionFactory.getConnection();
            
            // Tüm anahtarları al
            Set<byte[]> keys = connection.keys("*".getBytes());
            
            // Yedekleme dizini oluştur
            Path backupDir = Paths.get("/path/to/backup/data");
            if (!Files.exists(backupDir)) {
                Files.createDirectories(backupDir);
            }
            
            String timestamp = new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date());
            Path backupFile = backupDir.resolve("redis-data-" + timestamp + ".txt");
            
            try (BufferedWriter writer = Files.newBufferedWriter(backupFile)) {
                for (byte[] key : keys) {
                    // Anahtar ve değeri al
                    byte[] value = connection.get(key);
                    
                    // Dosyaya yaz
                    writer.write(new String(key) + ":" + new String(value) + "\n");
                }
            }
            
            backupLogger.info("Veri yedeklendi: {}", backupFile);
            
            connection.close();
        } catch (Exception e) {
            backupLogger.error("Veri yedeklemesi sırasında hata: {}", e.getMessage());
        }
    }
}

Kurtarma stratejileri:

@Service
public class RedisRecoveryService {

    @Autowired
    private RedisConnectionFactory connectionFactory;
    
    private static final Logger recoveryLogger = LoggerFactory.getLogger("REDIS_RECOVERY");
    
    public void restoreFromRDB(String backupFilePath) {
        recoveryLogger.info("RDB'den kurtarma başlatıldı: {}", backupFilePath);
        
        try {
            // Redis sunucusunu durdur
            stopRedisServer();
            
            // RDB dosyasını kopyala
            Path sourcePath = Paths.get(backupFilePath);
            Path targetPath = Paths.get("/var/lib/redis/dump.rdb");
            
            Files.copy(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING);
            
            // Redis sunucusunu başlat
            startRedisServer();
            
            recoveryLogger.info("RDB'den kurtarma tamamlandı");
        } catch (Exception e) {
            recoveryLogger.error("RDB'den kurtarma sırasında hata: {}", e.getMessage());
        }
    }
    
    public void restoreFromAOF(String backupFilePath) {
        recoveryLogger.info("AOF'dan kurtarma başlatıldı: {}", backupFilePath);
        
        try {
            // Redis sunucusunu durdur
            stopRedisServer();
            
            // AOF dosyasını kopyala
            Path sourcePath = Paths.get(backupFilePath);
            Path targetPath = Paths.get("/var/lib/redis/appendonly.aof");
            
            Files.copy(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING);
            
            // Redis sunucusunu başlat
            startRedisServer();
            
            recoveryLogger.info("AOF'dan kurtarma tamamlandı");
        } catch (Exception e) {
            recoveryLogger.error("AOF'dan kurtarma sırasında hata: {}", e.getMessage());
        }
    }
    
    public void restoreFromDataBackup(String backupFilePath) {
        recoveryLogger.info("Veri yedeklemesinden kurtarma başlatıldı: {}", backupFilePath);
        
        try {
            RedisConnection connection = connectionFactory.getConnection();
            
            // Yedekleme dosyasını oku
            List<String> lines = Files.readAllLines(Paths.get(backupFilePath));
            
            // Verileri geri yükle
            for (String line : lines) {
                String[] parts = line.split(":", 2);
                if (parts.length == 2) {
                    String key = parts[0];
                    String value = parts[1];
                    
                    connection.set(key.getBytes(), value.getBytes());
                }
            }
            
            recoveryLogger.info("Veri yedeklemesinden kurtarma tamamlandı");
            
            connection.close();
        } catch (Exception e) {
            recoveryLogger.error("Veri yedeklemesinden kurtarma sırasında hata: {}", e.getMessage());
        }
    }
    
    private void stopRedisServer() {
        try {
            // Redis sunucusunu durdur
            ProcessBuilder processBuilder = new ProcessBuilder("sudo", "systemctl", "stop", "redis");
            Process process = processBuilder.start();
            process.waitFor();
            
            recoveryLogger.info("Redis sunucusu durduruldu");
        } catch (Exception e) {
            recoveryLogger.error("Redis sunucusu durdurulurken hata: {}", e.getMessage());
        }
    }
    
    private void startRedisServer() {
        try {
            // Redis sunucusunu başlat
            ProcessBuilder processBuilder = new ProcessBuilder("sudo", "systemctl", "start", "redis");
            Process process = processBuilder.start();
            process.waitFor();
            
            recoveryLogger.info("Redis sunucusu başlatıldı");
        } catch (Exception e) {
            recoveryLogger.error("Redis sunucusu başlatılırken hata: {}", e.getMessage());
        }
    }
}

Otomatik yedekleme:

@Service
public class ScheduledRedisBackupService {

    @Autowired
    private RedisBackupService backupService;
    
    // Her gün gece yarısı yedekle
    @Scheduled(cron = "0 0 0 * * *")
    public void scheduledBackup() {
        backupService.createRDBBackup();
        backupService.createAOFBackup();
        backupService.createDataBackup();
    }
    
    // Her hafta pazar günü yedekle
    @Scheduled(cron = "0 0 0 * * SUN")
    public void weeklyBackup() {
        backupService.createRDBBackup();
        backupService.createAOFBackup();
        backupService.createDataBackup();
    }
}

Redis yedekleme ve kurtarma stratejileri, veri kaybını önlemek ve felaket durumlarında veri kurtarmak için önemlidir. Düzenli yedeklemeler ve test edilmiş kurtarma planları, veri güvenliğini ve iş sürekliliğini sağlar.

❓ Soru 10: Redis'te güvenlik en iyi uygulamaları nelerdir?

Cevap: Redis'te güvenlik en iyi uygulamaları, Redis sunucusunu güvenli bir şekilde yapılandırmak ve yönetmek için önerilen yöntemlerdir. Bu uygulamalar, güvenlik açıklarını önlemeye ve veri güvenliğini artırmaya yardımcı olur.

Redis güvenlik en iyi uygulamaları:

  • Güçlü parola kullanma: Karmaşık ve tahmin edilmesi zor parolalar kullanmak
  • SSL/TLS şifreleme: İstemci-sunucu arasındaki iletişimi şifrelemek
  • Ağ erişimini kısıtlama: Sadece güvenilen IP adreslerinden bağlantıya izin vermek
  • Tehlikeli komutları devre dışı bırakma: FLUSHDB, FLUSHALL gibi tehlikeli komutları yeniden adlandırmak
  • Veri şifreleme: Hassas verileri şifreleyerek saklamak
  • Düzenli güvenlik denetimleri: Güvenlik açıklarını düzenli olarak taramak
  • Güvenlik loglama: Güvenlik olaylarını loglamak ve izlemek
  • Düzenli yedeklemeler: Verileri düzenli olarak yedeklemek
  • Erişim kontrolleri: Kullanıcıları ve rolleri yönetmek
  • Güncel sürümleri kullanma: Redis ve istemcileri güncel tutmak

Güvenlik yapılandırması örneği:

# redis.conf
# Güçlü parola
requirepass S3cur3P@ssw0rd!2023

# Sadece yerel ve özel ağdan bağlantıya izin ver
bind 127.0.0.1 10.0.0.100

# Tehlikeli komutları devre dışı bırak
rename-command CONFIG ""
rename-command FLUSHDB ""
rename-command FLUSHALL ""
rename-command SHUTDOWN ""

# SSL/TLS etkinleştir
tls-cert-file /etc/redis/redis-server.crt
tls-key-file /etc/redis/redis-server.key
tls-ca-cert-file /etc/redis/ca.crt

# AOF etkinleştir
appendonly yes
appendfsync everysec

# Güvenlik logları
loglevel verbose
acllog-max-len 128

Java ile güvenlik yapılandırması:

@Configuration
@EnableRedisRepositories
public class RedisSecurityConfig {

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        RedisStandaloneConfiguration redisConfig = new RedisStandaloneConfiguration();
        redisConfig.setHostName("your-redis-host");
        redisConfig.setPort(6379);
        redisConfig.setPassword("S3cur3P@ssw0rd!2023");
        
        // SSL yapılandırması
        LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
                .useSsl()
                .and()
                .clientOptions(ClientOptions.builder()
                        .sslOptions(SslOptions.builder()
                                .jdkSslProvider()
                                .truststore(new ClassPathResource("ca.jks"), "password")
                                .keystore(new ClassPathResource("redis-client.jks"), "password")
                                .build())
                        .build())
                .build();
        
        return new LettuceConnectionFactory(redisConfig, clientConfig);
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        return template;
    }

    @Bean
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory connectionFactory) {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(connectionFactory);
        return template;
    }
}

Güvenlik denetimi ve loglama:

@Aspect
@Component
public class RedisSecurityAspect {

    private static final Logger securityLogger = LoggerFactory.getLogger("REDIS_SECURITY");
    
    @Around("execution(* org.springframework.data.redis.core.RedisTemplate.*(..))")
    public Object logRedisOperations(ProceedingJoinPoint joinPoint) throws Throwable {
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        
        // Güvenlik logu
        securityLogger.debug("Redis işlemi: {}, Argümanlar: {}", methodName, Arrays.toString(args));
        
        try {
            // İşlemi çalıştır
            Object result = joinPoint.proceed();
            
            // Sonucu logla
            securityLogger.debug("Redis işlemi sonucu: {}", result);
            
            return result;
        } catch (Exception e) {
            // Hataları logla
            securityLogger.error("Redis işlemi hatası: {}, Hata: {}", methodName, e.getMessage(), e);
            throw e;
        }
    }
}

Veri şifreleme:

@Service
public class SecureDataService {

    @Autowired
    private StringRedisTemplate redisTemplate;
    
    private final SecretKey secretKey;
    private final Cipher cipher;
    
    public SecureDataService() throws Exception {
        // Anahtar oluştur (gerçek uygulamada güvenli bir şekilde saklanmalı)
        KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
        keyGenerator.init(256);
        secretKey = keyGenerator.generateKey();
        
        cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    }
    
    public void saveSecureData(String key, String value) {
        try {
            // Veriyi şifrele
            byte[] encryptedValue = encrypt(value);
            
            // Base64 ile kodla ve Redis'e kaydet
            String encodedValue = Base64.getEncoder().encodeToString(encryptedValue);
            redisTemplate.opsForValue().set("secure:" + key, encodedValue);
            
            // TTL ayarla
            redisTemplate.expire("secure:" + key, 1, TimeUnit.HOURS);
        } catch (Exception e) {
            throw new RuntimeException("Veri şifreleme hatası", e);
        }
    }
    
    public String getSecureData(String key) {
        try {
            // Redis'ten şifrelenmiş veriyi al
            String encodedValue = redisTemplate.opsForValue().get("secure:" + key);
            
            if (encodedValue == null) {
                return null;
            }
            
            // Base64'den çöz ve şifreyi çöz
            byte[] encryptedValue = Base64.getDecoder().decode(encodedValue);
            return decrypt(encryptedValue);
        } catch (Exception e) {
            throw new RuntimeException("Veri şifre çözme hatası", e);
        }
    }
    
    private byte[] encrypt(String value) throws Exception {
        byte[] iv = new byte[16];
        new SecureRandom().nextBytes(iv);
        
        cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv));
        
        byte[] encrypted = cipher.doFinal(value.getBytes());
        
        // IV ve şifrelenmiş veriyi birleştir
        byte[] combined = new byte[iv.length + encrypted.length];
        System.arraycopy(iv, 0, combined, 0, iv.length);
        System.arraycopy(encrypted, 0, combined, iv.length, encrypted.length);
        
        return combined;
    }
    
    private String decrypt(byte[] encryptedValue) throws Exception {
        // IV ve şifrelenmiş veriyi ayır
        byte[] iv = new byte[16];
        System.arraycopy(encryptedValue, 0, iv, 0, iv.length);
        
        byte[] encrypted = new byte[encryptedValue.length - 16];
        System.arraycopy(encryptedValue, 16, encrypted, 0, encrypted.length);
        
        cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
        
        byte[] decrypted = cipher.doFinal(encrypted);
        return new String(decrypted);
    }
}

Redis güvenlik en iyi uygulamaları, Redis sunucusunu güvenli bir şekilde yapılandırmak ve yönetmek için önemlidir. Bu uygulamalar, güvenlik açıklarını önlemeye ve veri güvenliğini artırmaya yardımcı olur.