Когда в проекте необходимо использовать локальный кеш, обычно реализуется собственный LRU-кэш на основе ConcurrentHashMap или LinkedHashMap. В процессе сборки колеса, как правило, необходимо решить следующие задачи:
1. Память ограничена, поэтому необходимо ограничить максимальную емкость кеша.
2. Как очистить "слишком старые" записи кеша.
3. Как бороться с одновременным чтением и записью.
4. Прозрачность данных кеша: частота попаданий, частота отказов и т. д.
Плюсы и минусы кэша в основном зависят от того, как элегантно и эффективно решить вышеуказанные проблемы. Кэш Guava очень хорошо решает эти проблемы: это очень хороший локальный кеш, потокобезопасный, полнофункциональный, простой в использовании и с хорошей производительностью. В целом, кэш Guava — лучший выбор для локального кэширования.
Вот простой пример:
LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
.maximumSize(1000).expireAfterWrite(10, TimeUnit.MINUTES)
.removalListener(MY_LISTENER) .build(new CacheLoader<Key, Graph>() {
public Graph load(Key key) throws AnyException {
return createExpensiveGraph(key); } });
Далее мы расскажем, как использовать кеш Guava с разных точек зрения.
1. Создайте кеш:
Вообще говоря, на работе мы обычно используем удаленный кеш или локальный кеш, например:
User user = cache.get(usernick); if(user == null){ user = userDao.getUser(usernick); cache.put(usernick, user);
} return user;
То есть, если кешировано, вернуть; в противном случае создать/загрузить/вычислить, кэшировать и вернуть.Кэш Guava реализует эту логику более элегантно следующими двумя способами:
1. From A CacheLoader
2. From A Callable
Разница между кэшами, созданными этими двумя методами, и обычными кэшами карты заключается в том, что все они реализуют вышеупомянутое «если кэшировано, вернуть; в противном случае создать/загрузить/вычислить, кэшировать и вернуть». Но разница в том, что определение cacheloader относительно широкое, и оно определено для всего кеша, что можно рассматривать как унифицированный метод загрузки значения по значению ключа. Вызываемый метод является более гибким, позволяя указать его при получении. Дайте два каштана, чтобы представить, как использовать эти два метода.
Из CacheLoader:
LoadingCache<String, String> graphs = CacheBuilder.newBuilder().maximumSize(1000)
.build(new CacheLoader<String, String>() {
public String load(String key) {
// 这里是key根据实际去取值的方法,例如根据这个key去数据库或者通过复杂耗时的计算得出
System.out.println("no cache,load from db"); return "123"; } }); String val1 = graphs.get("key"); System.out.println("1 value is: " + val1); String val2 = graphs.get("key"); System.out.println("2 value is: " + val2);From Callable: Cache<String, String> cache = CacheBuilder.newBuilder()
.maximumSize(1000).build(); String val1 = cache.get("key", new Callable<String>() {
public String call() {
// 这里是key根据实际去取值的方法,例如根据这个key去数据库或者通过复杂耗时的计算得出
System.out.println("val call method is invoked"); return "123"; } }); System.out.println("1 value is: " + val1); String val2 = cache.get("testKey", new Callable<String>() {
public String call() {
// 这里是key根据实际去取值的方法,例如根据这个key去数据库或者通过复杂耗时的计算得出
System.out.println("val call method is invoked"); return "123"; } }); System.out.println("2 value is: " + val2);
Следует отметить, что все кэши Guava, независимо от того, находятся ли они в режиме загрузчика или нет, поддерживают метод get(Key,Callable).
Кроме того, в дополнение к двум вышеуказанным методам обновления кеша, конечно же, кеш Guava также поддерживает Inserted Directly: значения также могут напрямую вставлять значения в кеш через cache.put(key, value). Этот метод переопределит запись, соответствующую ключу.
Во-вторых, удалите кеш
Память ограничен, поэтому вы не можете поместить все загруженные в память, локальный кеш слишком большой для любых приложений Java - это кошмар. Поэтому локальный кэш должен предоставить различные механизмы для удаления «ненужных» входных кэш-памяти, балансировка использования памяти и хитов. Guava Cache предоставляет 3 кэш-политике: выселение на основе размера, выселение на основе времени и опорное выселение.
Выселение на основе размера: удаление на основе емкости кэша. Если ваш кеш не может расширяться, то есть не может превышать установленное максимальное значение, используйте CacheBuilder.maxmuSize(long). При этом условии кэш автоматически освобождает память записей, которые не используются в последнее время или используются редко. Здесь следует отметить две вещи:
1. Эти записи удаляются не при превышении лимита, а когда лимит вот-вот будет достигнут, то надо внимательно отнестись к этой ситуации, т.к. очевидно, что даже если лимит не будет достигнут, кеш все равно будет обработано, операция удаления.
2. Если запись ключа была удалена, при повторном вызове get(key), если CacheBuilder примет режим CacheLoader, он все равно будет загружен из cacheLoader один раз.
Кроме того, если записи в вашем кеше занимают очень разные места в памяти, если значения вашего кеша занимают очень разные места в памяти, вы можете использовать CacheBuilder.weighter(Weigher), чтобы установить вес для разных записей, а затем использовать CacheBuilder.maximumWeight (long) устанавливает максимальное значение. В tpn информация о подписке пользователя на категории сообщений кэшируется через локальный кеш.Некоторые пользователи подписываются на большее количество категорий сообщений и занимают больше памяти.Некоторые пользователи подписываются на меньшее количество категорий сообщений и, естественно, занимают память.Меньше. Затем я могу использовать следующий метод, чтобы установить разные веса в соответствии с количеством категорий сообщений, на которые подписаны пользователи, чтобы кеш мог охватить как можно больше пользователей без изменения размера кеша:
LoadingCache<Key, User> Users= CacheBuilder.newBuilder()
.maximumWeight(100000) .weigher(new Weigher<Key, User>() {
public int weigh(Key k, User u) {
if(u.categories().szie() >5){
return 2; }else{ return 1; } } }) .build( new CacheLoader<Key, User>() {
public Userload(Key key) { // no checked exception
return createExpensiveUser(key); } });
PS: Этот пример может быть не очень уместным, этого было достаточно, чтобы проиллюстрировать использование веса.
выселение на основе времени: удаление на основе времени. Кэш Guava предоставляет два способа реализации этой логики:
1. expireAfterAccess(long, TimeUnit)
Записи освобождаются по истечении указанного времени с момента последнего доступа (чтения или записи). Примечание. Порядок этих удаленных записей очень похож на вытеснение на основе размера.
2. expireAfterWrite(long,TimeUnit)
Отсчитывается от момента создания записей или последнего изменения их значения. Если с этого момента прошло указанное время, записи будут удалены. Это продумано до мелочей, поскольку данные со временем устаревают.
Если вы хотите протестировать Timed Eviction, используйте интерфейс Ticker и метод CacheBuilder.ticker(Ticker), чтобы установить время для вашего кеша, тогда вам не нужно ждать системного времени.
Выселение на основе ссылки: удаление на основе ссылки. Guava подготавливает для вас сборщик мусора для записей, вы можете использовать слабую ссылку для ключей или значений и мягкую ссылку для значений.
1. CacheBuilder.weakKeys(): хранить ключи через слабую ссылку. В этом случае записи будут удалены сборщиком мусора, если на ключи не ссылаются сильные или мягкие.
2. CacheBuilder.weakValues(): В этом случае, если вентили не являются сильными или мягкими ссылками по слабым референсным хранимым значениям, то записи будут удалены сборщиком мусора.
Следует отметить, что сборщик мусора в этом случае основан на ссылках, поэтому весь кеш будет использовать == для сравнения двух ключей вместо equals();
В дополнение к трем указанным выше методам удаления записей из кэша вы также можете активно освобождать некоторые объекты с помощью следующих трех методов:
1. Для отдельного удаления: Cache.invalidate(key)
2. Для пакетного удаления: Cache.invalidateAll(keys)
3. Удалите все вызовы с помощью :Cache.invalidateAll()
Если вам нужно выполнить действие при удалении данных, вы также можете определить прослушиватель удаления, но следует отметить, что поведение в прослушивателе удаления по умолчанию выполняется синхронно с действием удаления.Если вам нужно изменить его на асинхронную форму, вы можете рассмотреть возможность использования RemovalListeners.asynchronous(RemovalListener, Executor).
Наконец, давайте посмотрим, когда кэш Guava выполняет действие очистки. Кэш, созданный CacheBuilder, не очищается автоматически и не удаляет запись, а также не очищается сразу после истечения срока действия записи. Вместо этого он выполняет очистку с небольшим количеством операций при выполнении операции записи или операции чтения (в случае очень небольшого количества операций записи). Причина этого: если мы собираемся продолжать чистить и удалять кеш, нам нужно создать поток, бизнес которого будет конкурировать за общую блокировку с операциями пользователя. Кроме того, некоторые среды ограничивают создание потоков очистки, что делает CacheBuilder непригодным для использования в этой среде. Таким образом, кеш Guava оставляет за пользователем выбор времени очистки. Если ваш кеш предназначен для приложений с высокой пропускной способностью, вам не нужно беспокоиться об обслуживании кеша, очистке записей с истекшим сроком действия и т. д. Если ваш кеш предназначен для приложений, которые больше читают и меньше пишут, во избежание влияния на чтение кеша вы можете создать собственный поток обслуживания для вызова Cache.cleanUp() через регулярные промежутки времени.Например, для организации обслуживания можно использовать ScheduledExecutorService.
Если вы хотите запланировать периодическое обслуживание кеша с небольшим количеством записей в кеше, просто используйте ScheduledExecutorService для планирования обслуживания.
3. Статистическая функция:
Статистика — очень полезная функция кэша Guava. Сбор данных кэша можно запустить с помощью метода CacheBuilder.recordStats():
1. Cache.stats(): возвращает объект CacheStats, предоставляя некоторые методы данных.
2. hitRate(): Запрос частоты кликов
3. mediumLoadPenalty(): загрузить новое значение, затраченное время, единицы наносекунд.
4. ExictionCount (): количество очистки