← Назад к вопросам

Как контролируешь инвалидацию в Redis

2.3 Middle🔥 181 комментариев
#Spring Boot и Spring Data#Кэширование и NoSQL

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

# Контроль инвалидации в Redis

Инвалидация кэша в Redis — критичный аспект работы с памятью. Существует несколько стратегий для контроля того, когда и как удалять устаревшие данные.

1. TTL (Time To Live)

Самый распространённый способ — установка времени жизни ключа:

@Service
public class CacheService {
    private final RedisTemplate<String, Object> redisTemplate;
    
    public void setCacheWithTTL(String key, Object value, long ttlSeconds) {
        redisTemplate.opsForValue().set(key, value, Duration.ofSeconds(ttlSeconds));
    }
    
    public void setCacheWithTTL(String key, Object value, long timeout, TimeUnit unit) {
        redisTemplate.opsForValue().set(key, value, timeout, unit);
    }
}

Redis автоматически удалит ключ по истечении TTL.

2. Явная инвалидация (Active Invalidation)

Удаление кэша при изменении данных в БД:

@Service
public class UserService {
    private final RedisTemplate<String, User> redisTemplate;
    private final UserRepository userRepository;
    
    public User updateUser(Long id, UserRequest request) {
        User user = userRepository.save(new User(id, request.getName()));
        // Инвалидировать кэш
        String cacheKey = "user:" + id;
        redisTemplate.delete(cacheKey);
        return user;
    }
    
    public User getUser(Long id) {
        String cacheKey = "user:" + id;
        User cached = redisTemplate.opsForValue().get(cacheKey);
        if (cached != null) {
            return cached;
        }
        User user = userRepository.findById(id).orElse(null);
        if (user != null) {
            redisTemplate.opsForValue().set(cacheKey, user, Duration.ofHours(1));
        }
        return user;
    }
}

3. Pattern-based инвалидация

Удаление группы кэшей по паттерну:

@Service
public class CacheManager {
    private final RedisTemplate<String, Object> redisTemplate;
    
    public void invalidateUserCache(Long userId) {
        // Удалить все ключи, начинающиеся с user:123:
        Set<String> keys = redisTemplate.keys("user:" + userId + ":*");
        if (!keys.isEmpty()) {
            redisTemplate.delete(keys);
        }
    }
    
    public void invalidateAllCache() {
        Set<String> keys = redisTemplate.keys("*");
        if (!keys.isEmpty()) {
            redisTemplate.delete(keys);
        }
    }
}

Внимание: операция keys() дорогая для больших наборов данных.

4. Cache-aside паттерн с Lua скриптом

Атомарное получение и обновление TTL:

@Service
public class CacheService {
    private final RedisTemplate<String, Object> redisTemplate;
    
    public Object getWithRefresh(String key, long newTTL, TimeUnit unit) {
        String luaScript = 
            "if redis.call('exists', KEYS[1]) == 1 then " +
            "  redis.call('expire', KEYS[1], ARGV[1]) " +
            "  return redis.call('get', KEYS[1]) " +
            "else " +
            "  return nil " +
            "end";
        
        return redisTemplate.execute(
            new DefaultRedisScript<>(luaScript, Object.class),
            List.of(key),
            unit.toSeconds(newTTL)
        );
    }
}

5. Использование Spring Cache Annotations

Декларативный подход с автоматической инвалидацией:

@Service
@CacheConfig(cacheNames = "users")
public class UserService {
    
    @Cacheable(key = "#id")
    public User getUserById(Long id) {
        return userRepository.findById(id).orElse(null);
    }
    
    @CacheEvict(key = "#id")
    public void deleteUser(Long id) {
        userRepository.deleteById(id);
    }
    
    @CachePut(key = "#result.id")
    public User updateUser(User user) {
        return userRepository.save(user);
    }
    
    @CacheEvict(allEntries = true)
    public void clearAllUserCache() {
        // Очистить весь кэш
    }
}

6. Write-through и Write-behind стратегии

Управление синхронизацией между кэшем и БД:

@Service
public class WriteThruService {
    private final RedisTemplate<String, Object> redisTemplate;
    private final Database database;
    
    // Write-through: сначала в БД, потом в кэш
    public void saveWithCache(String key, Object value) {
        database.save(value);  // В БД
        redisTemplate.opsForValue().set(key, value, Duration.ofHours(1));  // В кэш
    }
    
    // Write-behind: сначала в кэш, потом в БД (асинхронно)
    public void saveWithCacheBehind(String key, Object value) {
        redisTemplate.opsForValue().set(key, value, Duration.ofHours(1));
        asyncService.saveToDatabase(value);
    }
}

7. Контроль памяти (Eviction Policies)

Когда Redis кончается память, нужно установить политику вытеснения в redis.conf:

maxmemory 256mb
maxmemory-policy allkeys-lru  # LRU для всех ключей

Политики:

  • noeviction — не удалять, возвращать ошибку
  • allkeys-lru — удалять least recently used
  • allkeys-lfu — удалять least frequently used
  • volatile-lru — LRU только для ключей с TTL
  • volatile-ttl — удалять ключи с наименьшим TTL

Лучшие практики

  1. Используй TTL для автоматической очистки
  2. Инвалидируй явно при критичных изменениях
  3. Используй Spring Cache для декларативного подхода
  4. Избегай keys() в продакшене — используй SCAN
  5. Мониторь память и настраивай maxmemory-policy
  6. Тестируй race conditions между кэшем и БД
  7. Документируй стратегию инвалидации для каждого кэша