← Назад к вопросам
Как контролируешь инвалидацию в 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 usedallkeys-lfu— удалять least frequently usedvolatile-lru— LRU только для ключей с TTLvolatile-ttl— удалять ключи с наименьшим TTL
Лучшие практики
- Используй TTL для автоматической очистки
- Инвалидируй явно при критичных изменениях
- Используй Spring Cache для декларативного подхода
- Избегай keys() в продакшене — используй SCAN
- Мониторь память и настраивай maxmemory-policy
- Тестируй race conditions между кэшем и БД
- Документируй стратегию инвалидации для каждого кэша