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

Какие знаешь виды кеширования?

2.2 Middle🔥 151 комментариев
#Кэширование и NoSQL

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

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

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

Виды кеширования в 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 для предзагрузки важных данных

Выбор правильной стратегии кеширования критичен для производительности приложения.