102 lines
4.0 KiB
Java
102 lines
4.0 KiB
Java
package com.krylov.refound.service.redis;
|
||
|
||
import com.krylov.refound.dto.CachedPostPage;
|
||
import com.krylov.refound.dto.MapMarkerDto;
|
||
import java.time.Duration;
|
||
import java.util.List;
|
||
import lombok.RequiredArgsConstructor;
|
||
import lombok.extern.slf4j.Slf4j;
|
||
import org.springframework.data.redis.core.RedisTemplate;
|
||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||
import org.springframework.stereotype.Service;
|
||
import org.springframework.transaction.support.TransactionSynchronization;
|
||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||
|
||
@Service
|
||
@RequiredArgsConstructor
|
||
@Slf4j
|
||
public class PostCacheService {
|
||
|
||
// Общая версия ВСЕХ данных posts. От неё зависят и лента, и карта —
|
||
// любое изменение поста должно инвалидировать оба представления разом.
|
||
// Ключ без TTL. Redis должен работать с maxmemory-policy volatile-lru,
|
||
// чтобы этот ключ не вытеснялся
|
||
private static final String VERSION_KEY = "posts:version";
|
||
|
||
private final RedisTemplate<String, CachedPostPage> feedRedisTemplate;
|
||
private final RedisTemplate<String, List<MapMarkerDto>> mapRedisTemplate;
|
||
private final StringRedisTemplate stringRedisTemplate;
|
||
|
||
public CachedPostPage getFeed(String key) {
|
||
try {
|
||
return feedRedisTemplate.opsForValue().get(key);
|
||
} catch (Exception e) {
|
||
// Кэш best-effort: любая проблема (Redis недоступен, битый JSON) = miss
|
||
log.warn("Не удалось прочитать feed из Redis, key={}: {}", key, e.toString());
|
||
return null;
|
||
}
|
||
}
|
||
|
||
public void saveFeed(String key, CachedPostPage value, Duration ttl) {
|
||
try {
|
||
feedRedisTemplate.opsForValue().set(key, value, ttl);
|
||
} catch (Exception e) {
|
||
log.warn("Не удалось сохранить feed в Redis, key={}: {}", key, e.toString());
|
||
}
|
||
}
|
||
|
||
public List<MapMarkerDto> getMap(String key) {
|
||
try {
|
||
return mapRedisTemplate.opsForValue().get(key);
|
||
} catch (Exception e) {
|
||
log.warn("Не удалось прочитать map из Redis, key={}: {}", key, e.toString());
|
||
return null;
|
||
}
|
||
}
|
||
|
||
public void saveMap(String key, List<MapMarkerDto> value, Duration ttl) {
|
||
try {
|
||
mapRedisTemplate.opsForValue().set(key, value, ttl);
|
||
} catch (Exception e) {
|
||
log.warn("Не удалось сохранить map в Redis, key={}: {}", key, e.toString());
|
||
}
|
||
}
|
||
|
||
/** Текущая версия данных posts. Должна читаться ДО запроса в БД, в обоих сервисах. */
|
||
public String currentVersion() {
|
||
try {
|
||
String version = stringRedisTemplate.opsForValue().get(VERSION_KEY);
|
||
return version == null ? "0" : version;
|
||
} catch (Exception e) {
|
||
log.warn("Не удалось прочитать версию posts cache: {}", e.toString());
|
||
return "0";
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Инвалидирует ВСЕ производные кэши (ленту и карту) после коммита текущей транзакции
|
||
* (или сразу, если транзакции нет).
|
||
*/
|
||
public void invalidatePosts() {
|
||
if (TransactionSynchronizationManager.isSynchronizationActive()) {
|
||
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
|
||
@Override
|
||
public void afterCommit() {
|
||
bumpVersion();
|
||
}
|
||
});
|
||
} else {
|
||
bumpVersion();
|
||
}
|
||
}
|
||
|
||
private void bumpVersion() {
|
||
try {
|
||
Long version = stringRedisTemplate.opsForValue().increment(VERSION_KEY);
|
||
log.info("Posts cache инвалидирован (лента + карта), версия={}", version);
|
||
} catch (Exception e) {
|
||
// Устаревшие записи доживут до TTL
|
||
log.warn("Не удалось инвалидировать posts cache: {}", e.toString());
|
||
}
|
||
}
|
||
} |