Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Виды кеширования в Java
Кеширование — это стратегия оптимизации, которая хранит часто используемые данные в быстрой памяти для снижения времени доступа и уменьшения нагрузки на систему. Существует множество видов кеширования, каждый с собственными характеристиками и применением.
1. In-Memory Caching (Кеш в памяти приложения)
HashMap/ConcurrentHashMap для простого кеша:
public class SimpleUserCache {
private final Map<Long, User> cache = new ConcurrentHashMap<>();
public void put(Long id, User user) {
cache.put(id, user);
}
public User get(Long id) {
return cache.get(id);
}
public void evict(Long id) {
cache.remove(id);
}
}
Spring Cache аннотации:
@Service
public class UserService {
private final UserRepository repository;
public UserService(UserRepository repository) {
this.repository = repository;
}
@Cacheable(value = "users", key = "#id")
public User getUserById(Long id) {
return repository.findById(id).orElse(null);
}
@CachePut(value = "users", key = "#user.id")
public User updateUser(User user) {
return repository.save(user);
}
@CacheEvict(value = "users", key = "#id")
public void deleteUser(Long id) {
repository.deleteById(id);
}
@CacheEvict(value = "users", allEntries = true)
public void clearAllCache() {}
}
2. Guava Cache
Встроенное кеширование с автоматической инвалидацией:
@Service
public class CachedUserService {
private final UserRepository repository;
private final LoadingCache<Long, User> userCache;
public CachedUserService(UserRepository repository) {
this.repository = repository;
this.userCache = CacheBuilder.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.maximumSize(1000)
.recordStats()
.build(new CacheLoader<Long, User>() {
@Override
public User load(Long id) {
return repository.findById(id)
.orElseThrow(() -> new UserNotFoundException(id));
}
});
}
public User getUser(Long id) {
try {
return userCache.get(id);
} catch (ExecutionException e) {
throw new RuntimeException("Cache loading failed", e);
}
}
public void invalidateUser(Long id) {
userCache.invalidate(id);
}
public CacheStats getStats() {
return userCache.stats();
}
}
3. Redis Caching
Распределённый кеш для микросервисной архитектуры:
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(
RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
StringRedisSerializer stringSerializer = new StringRedisSerializer();
GenericJackson2JsonRedisSerializer jsonSerializer =
new GenericJackson2JsonRedisSerializer();
template.setKeySerializer(stringSerializer);
template.setValueSerializer(jsonSerializer);
template.setHashKeySerializer(stringSerializer);
template.setHashValueSerializer(jsonSerializer);
return template;
}
@Bean
public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
return RedisCacheManager.create(connectionFactory);
}
}
@Service
public class RedisCachedUserService {
private final UserRepository repository;
private final RedisTemplate<String, User> redisTemplate;
private static final String CACHE_KEY_PREFIX = "user:";
private static final long CACHE_TTL = 600; // 10 минут
public RedisCachedUserService(
UserRepository repository,
RedisTemplate<String, User> redisTemplate) {
this.repository = repository;
this.redisTemplate = redisTemplate;
}
public User getUser(Long id) {
String cacheKey = CACHE_KEY_PREFIX + id;
// Попытка получить из кеша
User cachedUser = redisTemplate.opsForValue().get(cacheKey);
if (cachedUser != null) {
return cachedUser;
}
// Получить из БД
User user = repository.findById(id)
.orElseThrow(() -> new UserNotFoundException(id));
// Сохранить в кеш
redisTemplate.opsForValue().set(
cacheKey,
user,
Duration.ofSeconds(CACHE_TTL)
);
return user;
}
public void invalidateUser(Long id) {
String cacheKey = CACHE_KEY_PREFIX + id;
redisTemplate.delete(cacheKey);
}
}
4. Кеширование на уровне браузера/HTTP
Управление кешированием через HTTP заголовки:
@RestController
public class ResourceController {
@GetMapping("/api/v1/users/{id}")
public ResponseEntity<User> getUser(
@PathVariable Long id,
HttpServletResponse response) {
User user = userService.findById(id);
// Кеш на 1 час
response.setHeader("Cache-Control", "public, max-age=3600");
response.setHeader("ETag", String.valueOf(user.hashCode()));
return ResponseEntity.ok(user);
}
@GetMapping("/api/v1/static/images/{id}")
public ResponseEntity<byte[]> getImage(@PathVariable String id) {
byte[] imageData = imageService.getImage(id);
// Кеш на 1 год (редко изменяется контент)
return ResponseEntity.ok()
.header("Cache-Control", "public, max-age=31536000, immutable")
.header("ETag", "\"" + id + "\"")
.body(imageData);
}
}
5. Двухуровневое кеширование (L1 + L2)
Комбинация in-memory и Redis:
@Service
public class TwoLevelCacheService {
private final UserRepository repository;
private final LoadingCache<Long, User> l1Cache; // В памяти
private final RedisTemplate<String, User> l2Cache; // Redis
private static final String L2_KEY_PREFIX = "user:";
public TwoLevelCacheService(
UserRepository repository,
RedisTemplate<String, User> l2Cache) {
this.repository = repository;
this.l2Cache = l2Cache;
this.l1Cache = CacheBuilder.newBuilder()
.expireAfterWrite(5, TimeUnit.MINUTES)
.maximumSize(500)
.build(new CacheLoader<Long, User>() {
@Override
public User load(Long id) {
return getFromL2(id);
}
});
}
public User getUser(Long id) {
try {
return l1Cache.get(id);
} catch (ExecutionException e) {
return null;
}
}
private User getFromL2(Long id) {
String l2Key = L2_KEY_PREFIX + id;
User user = l2Cache.opsForValue().get(l2Key);
if (user == null) {
user = repository.findById(id).orElse(null);
if (user != null) {
l2Cache.opsForValue().set(
l2Key,
user,
Duration.ofMinutes(30)
);
}
}
return user;
}
public void invalidateUser(Long id) {
l1Cache.invalidate(id);
l2Cache.delete(L2_KEY_PREFIX + id);
}
}
6. LRU Cache (Least Recently Used)
Кеш с автоматической эвикцией старых данных:
public class LRUCache<K, V> {
private final int capacity;
private final LinkedHashMap<K, V> cache;
public LRUCache(int capacity) {
this.capacity = capacity;
this.cache = new LinkedHashMap<K, V>(capacity, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > capacity;
}
};
}
public synchronized V get(K key) {
return cache.get(key);
}
public synchronized void put(K key, V value) {
cache.put(key, value);
}
}
7. Query Result Caching
Кеширование результатов запросов к БД:
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
@Cacheable("usersByEmail")
Optional<User> findByEmail(String email);
@Cacheable(value = "usersByRole")
List<User> findByRole(String role);
}
8. Message Cache (для асинхронных операций)
Кеширование обработанных сообщений:
@Component
public class MessageCache {
private final Map<String, ProcessedMessage> cache =
new ConcurrentHashMap<>();
private final ScheduledExecutorService executor =
Executors.newScheduledThreadPool(1);
public MessageCache() {
// Очищать кеш каждый час
executor.scheduleAtFixedRate(
this::clearExpiredEntries,
1, 1, TimeUnit.HOURS
);
}
public void cacheMessage(String messageId, ProcessedMessage message) {
cache.put(messageId, message);
}
public Optional<ProcessedMessage> getProcessed(String messageId) {
return Optional.ofNullable(cache.get(messageId));
}
private void clearExpiredEntries() {
// Очистить старые записи
}
}
Таблица сравнения кешей
| Тип | Область | Скорость | TTL | Масштабируемость |
|---|---|---|---|---|
| In-Memory | Одно приложение | Очень быстро | Настраивается | Локально |
| Guava | Одно приложение | Очень быстро | Встроенная | Локально |
| Redis | Распределённая | Быстро | Настраивается | Высокая |
| HTTP Cache | Браузер/CDN | Мгновенно | Настраивается | Очень высокая |
| Query Cache | БД результаты | Быстро | Вместе с данными | Локально |
Best Practices
- Используй многоуровневое кеширование для критичных операций
- Правильно управляй TTL (Time To Live)
- Инвалидируй кеш при изменении данных
- Мониторь hit rate и эффективность кеша
- Будь осторожен с race conditions в распределённом кеше
- Используй cache warming для предзагрузки важных данных
Выбор правильной стратегии кеширования критичен для производительности приложения.