← Назад к вопросам
Какие знаешь способы определить, чем заполнен кэш в момент ошибки сервиса?
3.0 Senior🔥 21 комментариев
#Docker, Kubernetes и DevOps#Кэширование и NoSQL
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Способы определить содержимое кэша при ошибке сервиса
Это критичный вопрос для production debugging. Расскажу о подходах, которые использую для анализа состояния кэша во время сбоев.
1. Логирование ключей и значений при ошибке
Простой способ — логировать кэш при исключении:
@Service
public class CacheService {
private final ConcurrentHashMap<String, Object> cache = new ConcurrentHashMap<>();
private final Logger logger = LoggerFactory.getLogger(CacheService.class);
public Object get(String key) {
try {
Object value = cache.get(key);
if (value == null) {
throw new CacheException("Key not found: " + key);
}
return value;
} catch (Exception e) {
// Логируем состояние кэша при ошибке
logger.error("Error getting key: {}", key, e);
logger.error("Cache state: size={}, keys={}",
cache.size(),
cache.keySet());
// Логируем первые 10 ключей с их типами
cache.entrySet().stream()
.limit(10)
.forEach(entry -> logger.error(" {} -> {}",
entry.getKey(),
entry.getValue().getClass().getSimpleName()));
throw e;
}
}
}
Проблема: если кэш содержит чувствительные данные (пароли, токены), логирование может быть опасным.
2. Защита чувствительных данных в логах
@Service
public class SecureCacheService {
private final ConcurrentHashMap<String, Object> cache = new ConcurrentHashMap<>();
private final List<String> sensitiveKeyPatterns = List.of(
"password.*", "token.*", "secret.*", "api_key.*"
);
private String sanitizeValue(String key, Object value) {
// Скрываем значение если ключ matching чувствительному паттерну
for (String pattern : sensitiveKeyPatterns) {
if (key.matches(pattern)) {
return "[REDACTED]";
}
}
// Для остальных значений логируем только класс
return value.getClass().getSimpleName() + "@" +
Integer.toHexString(System.identityHashCode(value));
}
public void logCacheState(String context) {
logger.info("Cache state at {}: size={}", context, cache.size());
cache.entrySet().stream()
.limit(100)
.forEach(entry -> logger.info(
" {} -> {}",
entry.getKey(),
sanitizeValue(entry.getKey(), entry.getValue())
));
}
}
3. Spring Cache Abstractions для мониторинга
Используй Spring's CacheManager для получения метрик:
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager("users", "products", "orders");
}
}
@Service
public class CacheMonitorService {
private final CacheManager cacheManager;
private final Logger logger = LoggerFactory.getLogger(CacheMonitorService.class);
public CacheMonitorService(CacheManager cacheManager) {
this.cacheManager = cacheManager;
}
public void printCacheState() {
cacheManager.getCacheNames().forEach(cacheName -> {
Cache cache = cacheManager.getCache(cacheName);
if (cache instanceof ConcurrentMapCache) {
ConcurrentMapCache mapCache = (ConcurrentMapCache) cache;
// Получаем нативную карту
ConcurrentHashMap<Object, Object> nativeCache =
(ConcurrentHashMap<Object, Object>) mapCache.getNativeCache();
logger.info("Cache '{}': size={}", cacheName, nativeCache.size());
nativeCache.keySet().stream()
.limit(50)
.forEach(key -> logger.info(" key: {}", key));
}
});
}
public Map<String, CacheStatistics> getCacheStatistics() {
return cacheManager.getCacheNames().stream()
.collect(Collectors.toMap(
cacheName -> cacheName,
this::getStatisticsForCache
));
}
private CacheStatistics getStatisticsForCache(String cacheName) {
Cache cache = cacheManager.getCache(cacheName);
ConcurrentMapCache mapCache = (ConcurrentMapCache) cache;
ConcurrentHashMap<Object, Object> nativeCache =
(ConcurrentHashMap<Object, Object>) mapCache.getNativeCache();
return CacheStatistics.builder()
.cacheName(cacheName)
.size(nativeCache.size())
.keys(new ArrayList<>(nativeCache.keySet()))
.build();
}
}
4. Трассировка с MDC (Mapped Diagnostic Context)
Используй SLF4J MDC для отслеживания контекста ошибки:
@Service
public class UserCacheService {
private final ConcurrentHashMap<String, User> userCache = new ConcurrentHashMap<>();
private final Logger logger = LoggerFactory.getLogger(UserCacheService.class);
public User getUser(String userId) {
try {
// Добавляем контекст в MDC
MDC.put("cache_operation", "getUser");
MDC.put("user_id", userId);
MDC.put("cache_size", String.valueOf(userCache.size()));
User user = userCache.get(userId);
if (user == null) {
throw new UserNotFoundException("User not found in cache: " + userId);
}
return user;
} catch (Exception e) {
MDC.put("error_message", e.getMessage());
MDC.put("cache_keys_sample",
userCache.keySet().stream()
.limit(5)
.collect(Collectors.joining(",")));
logger.error("Failed to get user from cache", e);
throw e;
} finally {
// Не забыть очистить MDC
MDC.clear();
}
}
}
// logback.xml
<configuration>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>logs/app.log</file>
<encoder>
<pattern>
%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36}
[%X{cache_operation}:%X{user_id}] - %msg%n
</pattern>
</encoder>
</appender>
</configuration>
5. Redis/Caffeine Cache Metrics
Для Redis используй Redis CLI:
# Подключение к Redis
redis-cli
# Получить все ключи
KEYS *
# Получить размер БД
DBSIZE
# Информация о памяти
INFO memory
# Получить значение ключа
GET mykey
# Вывести количество ключей по паттерну
KEYS user:*
CARDINALITY
# Для больших БД используй SCAN
SCAN 0 MATCH "user:*" COUNT 100
Для Caffeine Cache:
@Configuration
@EnableCaching
public class CaffeineCacheConfig {
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager("users", "products");
cacheManager.setCaffeine(Caffeine.newBuilder()
.recordStats() // Включаем сбор статистики!
.expireAfterWrite(10, TimeUnit.MINUTES)
.maximumSize(10000));
return cacheManager;
}
}
@Service
public class CacheStatsService {
@Autowired
private CacheManager cacheManager;
public void printCacheStats() {
cacheManager.getCacheNames().forEach(cacheName -> {
Cache cache = cacheManager.getCache(cacheName);
com.github.benmanes.caffeine.cache.Cache<?, ?> caffeineCache =
(com.github.benmanes.caffeine.cache.Cache<?, ?>)
cache.getNativeCache();
CacheStats stats = caffeineCache.stats();
System.out.println("Cache: " + cacheName);
System.out.println(" Hits: " + stats.hitCount());
System.out.println(" Misses: " + stats.missCount());
System.out.println(" Hit rate: " + stats.hitRate());
System.out.println(" Evictions: " + stats.evictionCount());
});
}
}
6. Actuator Endpoint для dump кэша
Создай custom endpoint для получения состояния кэша:
@Component
@Endpoint(id = "cache-state")
public class CacheStateEndpoint {
private final CacheManager cacheManager;
public CacheStateEndpoint(CacheManager cacheManager) {
this.cacheManager = cacheManager;
}
@ReadOperation
public Map<String, Map<String, Object>> getState() {
Map<String, Map<String, Object>> result = new HashMap<>();
cacheManager.getCacheNames().forEach(cacheName -> {
Cache cache = cacheManager.getCache(cacheName);
ConcurrentMapCache mapCache = (ConcurrentMapCache) cache;
ConcurrentHashMap<Object, Object> nativeCache =
(ConcurrentHashMap<Object, Object>) mapCache.getNativeCache();
Map<String, Object> cacheInfo = new HashMap<>();
cacheInfo.put("size", nativeCache.size());
cacheInfo.put("keys", new ArrayList<>(nativeCache.keySet()));
result.put(cacheName, cacheInfo);
});
return result;
}
@ReadOperation
public Map<String, Object> getCacheDetails(@Selector String cacheName) {
Cache cache = cacheManager.getCache(cacheName);
ConcurrentMapCache mapCache = (ConcurrentMapCache) cache;
ConcurrentHashMap<Object, Object> nativeCache =
(ConcurrentHashMap<Object, Object>) mapCache.getNativeCache();
return Map.of(
"cacheName", cacheName,
"size", nativeCache.size(),
"keys", nativeCache.keySet(),
"sample", nativeCache.entrySet().stream()
.limit(10)
.collect(Collectors.toMap(
e -> String.valueOf(e.getKey()),
e -> e.getValue().getClass().getSimpleName()
))
);
}
}
// application.yml
management:
endpoints:
web:
exposure:
include: cache-state # Теперь доступно на /actuator/cache-state
7. Профилирование памяти при ошибке
Используй JDK Flight Recorder для recording heap dump:
@Service
public class HeapDumpService {
private final Logger logger = LoggerFactory.getLogger(HeapDumpService.class);
public void captureHeapOnError(Exception e) {
try {
String timestamp = LocalDateTime.now().format(
DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss")
);
String filename = "heap-dump-" + timestamp + ".hprof";
// Используем HotSpotDiagnosticsMXBean для создания heap dump
HotSpotDiagnosticsMXBean bean = ManagementFactory.getPlatformMXBean(
HotSpotDiagnosticsMXBean.class
);
bean.dumpHeap(filename, false);
logger.info("Heap dump created: {}", filename);
} catch (Exception ex) {
logger.error("Failed to create heap dump", ex);
}
}
}
8. Декоратор для логирования кэша
public class CacheableWithLogging {
private final CacheManager cacheManager;
private final Logger logger = LoggerFactory.getLogger(CacheableWithLogging.class);
@Around("@annotation(org.springframework.cache.annotation.Cacheable)")
public Object logCacheOperation(ProceedingJoinPoint pjp) throws Throwable {
String methodName = pjp.getSignature().getName();
try {
Object result = pjp.proceed();
logger.debug("Cache HIT for method: {}", methodName);
return result;
} catch (Exception e) {
logger.error("Cache operation failed for method: {}", methodName, e);
// Логируем состояние всех кэшей
cacheManager.getCacheNames().forEach(cacheName -> {
Cache cache = cacheManager.getCache(cacheName);
logger.error("Cache '{}' has {} entries",
cacheName,
((ConcurrentMapCache) cache).getNativeCache().size());
});
throw e;
}
}
}
Best Practices
- Логируй ключи, не значения — экономит место, защищает от утечек
- Используй MDC для контекста в многопоточной среде
- Тестируй сценарии с полным кэшем — ошибки часто появляются при edge cases
- Установи пределы размера кэша — OutOfMemory ошибки обычно связаны с расростанием кэша
- Мониторь hit rate — низкий hit rate значит проблему в кэшировании
- Создавай heap dump при критичных ошибках для offline анализа
- Версионируй структуру кэшируемых объектов — изменения структуры могут привести к корруптации
В целом: мониторинг и отладка кэша требует комплексного подхода. Всегда имей план для быстрого доступа к информации о состоянии кэша в production.