моя контактная информация
Почтамезофия@protonmail.com
2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
В мире Java технология кэширования широко используется для повышения производительности приложений и в основном делится на две категории: удаленное кэширование и локальное кэширование. Удаленное кэширование, обладающее превосходной производительностью и гибкостью, часто реализуется с помощью популярных решений, таких как Redis и Memcached. Локальный кеш с его легкими и быстрыми характеристиками доступа представлен такими технологиями, как HashMap, Guava Cache, Caffeine и Ehcache.
В будущих статьях блога мы углубимся в тайны удаленного кэширования, а сегодня давайте сосредоточимся на локальном кэшировании. В этой статье вы сначала познакомитесь с технологией локального кэширования, чтобы предоставить вам полный обзор. Далее мы углубимся в технологию кэширования, известную как король производительности, и изучим лежащие в ее основе принципы и методы реализации. Наконец, на серии практических примеров мы продемонстрируем, как эффективно использовать эти высокопроизводительные технологии локального кэширования в повседневной работе для повышения эффективности разработки и производительности приложений.
Используя базовую реализацию Map, мы можем хранить объекты, подлежащие кэшированию, непосредственно в памяти, что является прямым и эффективным методом.
Преимущество: Этот метод прост и понятен, не требует использования внешних библиотек и очень подходит для приложений с несложными требованиями к кэшированию и простыми сценариями.
Недостатки: Однако в этом методе отсутствует механизм автоматического устранения кэша. Если необходимо реализовать более продвинутые стратегии кэширования, могут потребоваться более высокие затраты на разработку.
- public class LRUCache extends LinkedHashMap {
-
- /**
- * 可重入读写锁,保证并发读写安全性
- */
- private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
- private Lock readLock = readWriteLock.readLock();
- private Lock writeLock = readWriteLock.writeLock();
-
- /**
- * 缓存大小限制
- */
- private int maxSize;
-
- public LRUCache(int maxSize) {
- super(maxSize + 1, 1.0f, true);
- this.maxSize = maxSize;
- }
-
- @Override
- public Object get(Object key) {
- readLock.lock();
- try {
- return super.get(key);
- } finally {
- readLock.unlock();
- }
- }
-
- @Override
- public Object put(Object key, Object value) {
- writeLock.lock();
- try {
- return super.put(key, value);
- } finally {
- writeLock.unlock();
- }
- }
-
- @Override
- protected boolean removeEldestEntry(Map.Entry eldest) {
- return this.size() > maxSize;
- }
- }
Guava Cache — это технология кэширования, разработанная Google и основанная на алгоритме замены LRU (наименее недавно использовавшемся). Однако с появлением кофеина тайник гуавы постепенно исчез из поля зрения людей. Кофеин не только наследует преимущества Guava Cache, но и превосходит его во многих аспектах. Хотя конкретный пример кода здесь не представлен, читатели, интересующиеся Guava Cache, могут посетить его официальный сайт для получения дополнительной информации.
Преимущество: Guava Cache поддерживает установку ограничений максимальной емкости и предоставляет две стратегии истечения срока действия: на основе времени вставки и времени доступа, а также поддерживает некоторые базовые статистические функции.
Недостатки: С выпуском Spring Boot 2 и Spring 5 использование Guava Cache больше не рекомендуется для них обоих.
Caffeine — это технология кэширования с открытым исходным кодом, использующая алгоритм W-TinyLFU, который представляет собой стратегию устранения кэша, сочетающую в себе преимущества LRU и LFU (наименьшая используемая частота). Производительность кэша Caffeine близка к теоретическому оптимальному решению и может рассматриваться как обновленная версия кэша Guava.
- public class CaffeineCacheTest {
-
- public static void main(String[] args) throws Exception {
- //创建guava cache
- Cache<String, String> loadingCache = Caffeine.newBuilder()
- //cache的初始容量
- .initialCapacity(5)
- //cache最大缓存数
- .maximumSize(10)
- //设置写缓存后n秒钟过期
- .expireAfterWrite(17, TimeUnit.SECONDS)
- //设置读写缓存后n秒钟过期,实际很少用到,类似于expireAfterWrite
- //.expireAfterAccess(17, TimeUnit.SECONDS)
- .build();
- String key = "key";
- // 往缓存写数据
- loadingCache.put(key, "v");
-
- // 获取value的值,如果key不存在,获取value后再返回
- String value = loadingCache.get(key, CaffeineCacheTest::getValueFromDB);
-
- // 删除key
- loadingCache.invalidate(key);
- }
-
- private static String getValueFromDB(String key) {
- return "v";
- }
- }
Ehcache — это чистая среда внутрипроцессного кэширования Java, известная своей быстрой и эффективной производительностью. Он широко известен и служит поставщиком кэша по умолчанию для Hibernate.
Преимущество : Ehcache предоставляет широкий спектр алгоритмов вытеснения кэша, включая LFU (наименее часто используемый), LRU (наименее используемый в последнее время) и FIFO (первым вошел — первым вышел). Он поддерживает различные типы кэш-хранилищ, такие как кэш в куче, кэш вне кучи и дисковый кэш, чтобы адаптироваться к различным потребностям хранения. Кроме того, Ehcache также поддерживает множество кластерных решений, эффективно решая проблему совместного использования данных.
Недостатки: Хотя Ehcache превосходит во многих аспектах, по эффективности он немного отстает от Caffeine.
-
- public class EncacheTest {
-
- public static void main(String[] args) throws Exception {
- // 声明一个cacheBuilder
- CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
- .withCache("encacheInstance", CacheConfigurationBuilder
- //声明一个容量为20的堆内缓存
- .newCacheConfigurationBuilder(String.class,String.class, ResourcePoolsBuilder.heap(20)))
- .build(true);
- // 获取Cache实例
- Cache<String,String> myCache = cacheManager.getCache("encacheInstance", String.class, String.class);
- // 写缓存
- myCache.put("key","v");
- // 读缓存
- String value = myCache.get("key");
- // 移除换粗
- cacheManager.removeCache("myCache");
- cacheManager.close();
- }
- }
Cache<Key, Graph> cache = Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.maximumSize(10_000)
.build();
// 查找一个缓存元素, 没有查找到的时候返回null
Graph graph = cache.getIfPresent(key);
// 查找缓存,如果缓存不存在则生成缓存元素, 如果无法生成则返回null
graph = cache.get(key, k -> createExpensiveGraph(key));
// 添加或者更新一个缓存元素
cache.put(key, graph);
// 移除一个缓存元素
cache.invalidate(key);
Интерфейс Cache предоставляет возможность находить, обновлять и удалять кэшированные элементы с помощью явного поиска. Если кешированный элемент не может быть сгенерирован или в процессе генерации выдается исключение и генерация элемента завершается неудачно, кэш.get может вернуть значение null .
LoadingCache<Key, Graph> cache = Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build(key -> createExpensiveGraph(key));
// 查找缓存,如果缓存不存在则生成缓存元素, 如果无法生成则返回null
Graph graph = cache.get(key);
// 批量查找缓存,如果缓存不存在则生成缓存元素
Map<Key, Graph> graphs = cache.getAll(keys);
LoadingCache — это реализация кэша Cache с добавленной возможностью CacheLoader.
Если кэш отсутствует, соответствующий элемент кэша будет создан с помощью CacheLoader.load.
AsyncCache<Key, Graph> cache = Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.maximumSize(10_000)
.buildAsync();
// 查找一个缓存元素, 没有查找到的时候返回null
CompletableFuture<Graph> graph = cache.getIfPresent(key);
// 查找缓存元素,如果不存在,则异步生成
graph = cache.get(key, k -> createExpensiveGraph(key));
// 添加或者更新一个缓存元素
cache.put(key, graph);
// 移除一个缓存元素
cache.synchronous().invalidate(key);
AsyncCache — это асинхронная форма Cache, предоставляющая Исполнителю возможность генерировать элементы кэша и возвращать CompletableFuture. Реализация пула потоков по умолчанию — ForkJoinPool.commonPool(), но вы также можете настроить выбор пула потоков, переопределив и реализовав метод Caffeine.executor(Executor).
AsyncLoadingCache<Key, Graph> cache = Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(10, TimeUnit.MINUTES)
// 你可以选择: 去异步的封装一段同步操作来生成缓存元素
.buildAsync(key -> createExpensiveGraph(key));
// 你也可以选择: 构建一个异步缓存元素操作并返回一个future
.buildAsync((key, executor) -> createExpensiveGraphAsync(key, executor));
// 查找缓存元素,如果其不存在,将会异步进行生成
CompletableFuture<Graph> graph = cache.get(key);
// 批量查找缓存元素,如果其不存在,将会异步进行生成
CompletableFuture<Map<Key, Graph>> graphs = cache.getAll(keys);
AsyncLoadingCache — это асинхронная форма LoadingCache, которая предоставляет функцию асинхронной загрузки для создания элементов кэша.
// 基于缓存内的元素个数进行驱逐
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
.maximumSize(10_000)
.build(key -> createExpensiveGraph(key));
// 基于缓存内元素权重进行驱逐
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
.maximumWeight(10_000)
.weigher((Key key, Graph graph) -> graph.vertices().size())
.build(key -> createExpensiveGraph(key));
// 基于固定的过期时间驱逐策略
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
.expireAfterAccess(5, TimeUnit.MINUTES)
.build(key -> createExpensiveGraph(key));
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.build(key -> createExpensiveGraph(key));
// 基于不同的过期驱逐策略
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
.expireAfter(new Expiry<Key, Graph>() {
public long expireAfterCreate(Key key, Graph graph, long currentTime) {
// Use wall clock time, rather than nanotime, if from an external resource
long seconds = graph.creationDate().plusHours(5)
.minus(System.currentTimeMillis(), MILLIS)
.toEpochSecond();
return TimeUnit.SECONDS.toNanos(seconds);
}
public long expireAfterUpdate(Key key, Graph graph,
long currentTime, long currentDuration) {
return currentDuration;
}
public long expireAfterRead(Key key, Graph graph,
long currentTime, long currentDuration) {
return currentDuration;
}
})
.build(key -> createExpensiveGraph(key));
// 当key和缓存元素都不再存在其他强引用的时候驱逐
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
.weakKeys()
.weakValues()
.build(key -> createExpensiveGraph(key));
// 当进行GC的时候进行驱逐
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
.softValues()
.build(key -> createExpensiveGraph(key));
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
.maximumSize(10_000)
.refreshAfterWrite(1, TimeUnit.MINUTES)
.build(key -> createExpensiveGraph(key));
Стратегию обновления можно использовать только в LoadingCache. В отличие от вытеснения, если элемент кэша запрашивается во время обновления, его старое значение все равно будет возвращено, а обновленное новое значение не будет возвращено до завершения обновления элемента.
Cache<Key, Graph> graphs = Caffeine.newBuilder()
.maximumSize(10_000)
.recordStats()
.build();
Сбор данных можно включить с помощью метода Caffeine.recordStats(). Метод Cache.stats() вернет объект CacheStats, который будет содержать некоторые статистические показатели, такие как:
hitRate(): частота попаданий в кэш запросов.
evictionCount(): количество вытесняемых кешей.
AverageLoadPenalty(): среднее время, необходимое для загрузки нового значения.
С помощью контроллера RESTful, предоставляемого SpringBoot, вы можете легко запросить использование кэша.