Ведь в середине июня пейзаж Западного озера не совпадает с четырьмя временами года.
Листья лотоса в небе бесконечно зеленые, а цветы лотоса на солнце разные красные.
Рассвет из храма Цзинци, чтобы отправить Линь Цзыфанга - Ян Ванли
На выходных договорился с друзьями на Западном озере, цветы лотоса цветут в нужное время... Прежде чем начать статью, повесьте на здание города красивую картинку "Будда"! ! !
Недавно я использовал гуавскую гуаву, и я запечатал схему шаблона кеша, которая тем самым записана для последующих нужд.
Проблема GC, вызванная запланированной задачей очистки кеша
Зачем с этого начинать, ведь если ты этого не скажешь, Гуаве делать будет нечего!
В последних проектах нам необходимо использовать кеш для кеширования часто запрашиваемых данных, во-первых, мы не хотим внедрять сторонние сервисы, такие как redis или memcache, а во-вторых, наши требования к консистентности данных не очень высоки. Интерфейс запросов в кластере должен совместно использовать кешированные данные, поэтому нам нужно только реализовать кеш в памяти.
Сначала я не рассматривал возможность использования гуавы для этого, но я написал решение для кеша на основе CurrentHashMap; здесь мне нужно внести ясность, потому что кеш надеется обеспечить возможность очистки сверхурочной работы в этом сценарии и на основе кеш в моем собственном Фреймворке добавлена возможность периодической очистки просроченных данных.
Здесь я помещаю этот код с четким таймингом напрямую:
/**
* 静态内部类来进行超时处理
*/
private class ClearCacheThread extends Thread {
@Override
public void run() {
while (true){
try {
long now = System.currentTimeMillis();
Object[] keys = map.keySet().toArray();
for (Object key : keys) {
CacheEntry entry = map.get(key);
if (now - entry.time >= cacheTimeout) {
synchronized (map) {
map.remove(key);
if (LOGGER.isDebugEnabled()){
LOGGER.debug("language cache timeout clear");
}
}
}
}
}catch (Exception e){
LOGGER.error("clear out time cache value error;",e);
}
}
}
}
Этот поток используется для отдельной обработки просроченных данных. Когда кеш инициализируется, запускается метод запуска этого потока, чтобы начать выполнение.
Официально, из-за неразумности этого кода, после того, как я выпустил среду разработки, частота срабатывания машинного GC смехотворно высока. Попробовав разные исправления, наконец сдался; переключился на гуаву!
Друзья могут оставить сообщение ниже, чтобы обсудить, почему здесь частый GC, вывод я напишу в ответе на комментарий.
guava
Зачем использовать гуаву, это, очевидно, рекомендуется старшему брату! ! !
Guava — это набор инструментов для кэширования в памяти, предоставленный Google.Guava Cache предоставляет механизм кэширования данных (пар ключ-значение) в локальную (JVM) память, подходящий для данных, которые редко изменяются. Guava Cache похож на ConcurrentMap, но не совсем такой же. Основное отличие состоит в том, что ConcurrentMap сохраняет все добавленные элементы до тех пор, пока они не будут удалены явным образом. Напротив, чтобы ограничить использование памяти, Guava Cache обычно настроен на автоматическую переработку элементов.
Для нашего сценария возможности, предоставляемые гуавой, соответствуют нашим потребностям:
- Небольшие изменения данных
- на основе памяти
- Может быть переработан автоматически
Теперь, когда мы его выбрали, нам все еще необходимо иметь общее представление о нем; давайте взглянем на некоторые классы и интерфейсы, которые он предоставляет:
Интерфейс / класс | объяснить подробно |
---|---|
Cache | [I] Определить такие операции, как получение, размещение, аннулирование и т. д. Есть только операции добавления, удаления и изменения кеша, а операции загрузки данных нет. |
AbstractCache | [C] Реализовать интерфейс Cache. Среди них пакетная операция предназначена для выполнения одного поведения в цикле, и отдельное поведение не определено конкретно. |
LoadingCache | 【I】; унаследован от кеша. Определите операции, такие как Get, GetCeckected и Getall, которые все данные загружают данные из источника данных. |
AbstractLoadingCache | [C]; Унаследовано от абстрактныхCache, внедряет интерфейс LoadingCache. |
LocalCache | [C] Основной класс всего кеша гуавы, включая структуру данных кеша гуавы и основные методы работы с кешем. |
LocalManualCache | [C]; Внутренний статический класс LocalCache, реализующий интерфейс Cache. Его внутренние операции добавления, удаления и модификации кэша вызывают соответствующие методы переменной-члена localCache (тип LocalCache). |
LocalLoadingCache | [C] Внутренний статический класс LocalCache, унаследованный от класса LocalManualCache, реализует интерфейс LoadingCache. Все его операции также являются соответствующими методами вызова переменной-члена localCache (тип LocalCache) |
CacheBuilder | [C] Построитель кэша. Создайте запись кэша, укажите параметры конфигурации кэша и инициализируйте локальный кэш. В методе сборки CacheBuilder будет передавать все заданные ранее параметры в LocalCache и фактически не участвует ни в каких вычислениях. |
CacheLoader | [C] Используется для загрузки данных из источника данных и определения таких операций, как загрузка, перезагрузка и загрузка всех. |
В целом ядром гуавы должен быть класс LocalCache.
@GwtCompatible(emulated = true)
class LocalCache<K, V> extends AbstractMap<K, V> implements
ConcurrentMap<K, V>
Я не буду вдаваться в детали исходного кода этого класса здесь. Давайте посмотрим на мои идеи упаковки в практических приложениях [пакет соответствует моим текущим потребностям. Если есть небольшие партнеры, которым нужно учиться у них, они могут расширяться за счет самих себя]
private static final int MAX_SIZE = 1000;
private static final int EXPIRE_TIME = 10;
private static final int DEFAULT_SIZE = 100;
private int maxSize = MAX_SIZE;
private int expireTime = EXPIRE_TIME;
/** 时间单位(分钟) */
private TimeUnit timeUnit = TimeUnit.MINUTES;
/** Cache初始化或被重置的时间 */
private Date resetTime;
/** 分别记录历史最多缓存个数及时间点*/
private long highestSize = 0;
private Date highestTime;
private volatile LoadingCache<K, V> cache;
Во-первых, некоторые константы и базовая информация о атрибутах определены здесь. Конечно, эти атрибуты предоставят набор и получать методы для настройки их в реальном использовании.
public LoadingCache<K, V> getCache() {
//使用双重校验锁保证只有一个cache实例
if(cache == null){
synchronized (this) {
if(cache == null){
//CacheBuilder的构造函数是私有的,只能通过其静态方法newBuilder()来获得CacheBuilder的实例
cache = CacheBuilder.newBuilder()
//设置缓存容器的初始容量为100
.initialCapacity(DEFAULT_SIZE)
//缓存数据的最大条目
.maximumSize(maxSize)
//定时回收:缓存项在给定时间内没有被写访问(创建或覆盖),则回收。
.expireAfterWrite(expireTime, timeUnit)
//启用统计->统计缓存的命中率等
.recordStats()
//设置缓存的移除通知
.removalListener((notification)-> {
if (LOGGER.isDebugEnabled()){
LOGGER.debug("{} was removed, cause is {}" ,notification.getKey(), notification.getCause());
}
})
.build(new CacheLoader<K, V>() {
@Override
public V load(K key) throws Exception {
return fetchData(key);
}
});
this.resetTime = new Date();
this.highestTime = new Date();
if (LOGGER.isInfoEnabled()){
LOGGER.info("本地缓存{}初始化成功.", this.getClass().getSimpleName());
}
}
}
}
return cache;
}
Приведенный выше код является ядром кеша, кешируемый объект генерируется нашим кодом [] с использованием одномодового варианта. Определенные параметры атрибута для просмотра комментариев.
Поскольку те, кто выше, инкапсулирован в абстрактном классе AndergualGuavacache внутри, поэтому я завернул CacheManger, используемый для управления кэшем, и внешние интерфейсы для предоставления определенных функций; в CacheManger я использую статический внутренний класс для создания текущего кэша по умолчанию.
/**
* 使用静态内部类实现一个默认的缓存,委托给manager来管理
*
* DefaultGuavaCache 使用一个简单的单例模式
* @param <String>
* @param <Object>
*/
private static class DefaultGuavaCache<String, Object> extends
AbstractGuavaCache<String, Object> {
private static AbstractGuavaCache cache = new DefaultGuavaCache();
/**
* 处理自动载入缓存,按实际情况载入
* 这里
* @param key
* @return
*/
@Override
protected Object fetchData(String key) {
return null;
}
public static AbstractGuavaCache getInstance() {
return DefaultGuavaCache.cache;
}
}
Общая идея такова: если нам нужно расширить, нам нужно только расширить абстрактный класс AbstractGuavaCache в соответствии с фактическими потребностями. Конкретный код вставлен ниже.
закончить два класса
AbstractGuavaCache
public abstract class AbstractGuavaCache<K, V> {
protected final Logger LOGGER = LoggerFactory.getLogger(AbstractGuavaCache.class);
private static final int MAX_SIZE = 1000;
private static final int EXPIRE_TIME = 10;
/** 用于初始化cache的参数及其缺省值 */
private static final int DEFAULT_SIZE = 100;
private int maxSize = MAX_SIZE;
private int expireTime = EXPIRE_TIME;
/** 时间单位(分钟) */
private TimeUnit timeUnit = TimeUnit.MINUTES;
/** Cache初始化或被重置的时间 */
private Date resetTime;
/** 分别记录历史最多缓存个数及时间点*/
private long highestSize = 0;
private Date highestTime;
private volatile LoadingCache<K, V> cache;
public LoadingCache<K, V> getCache() {
//使用双重校验锁保证只有一个cache实例
if(cache == null){
synchronized (this) {
if(cache == null){
//CacheBuilder的构造函数是私有的,只能通过其静态方法ne
//wBuilder()来获得CacheBuilder的实例
cache = CacheBuilder.newBuilder()
//设置缓存容器的初始容量为100
.initialCapacity(DEFAULT_SIZE)
//缓存数据的最大条目
.maximumSize(maxSize)
//定时回收:缓存项在给定时间内没有被写访问
//(创建或覆盖),则回收。
.expireAfterWrite(expireTime, timeUnit)
//启用统计->统计缓存的命中率等
.recordStats()
//设置缓存的移除通知
.removalListener((notification)-> {
if (LOGGER.isDebugEnabled()){
//...
}
})
.build(new CacheLoader<K, V>() {
@Override
public V load(K key) throws Exception {
return fetchData(key);
}
});
this.resetTime = new Date();
this.highestTime = new Date();
if (LOGGER.isInfoEnabled()){
//...
}
}
}
}
return cache;
}
/**
* 根据key从数据库或其他数据源中获取一个value,并被自动保存到缓存中。
*
* 改方法是模板方法,子类需要实现
*
* @param key
* @return value,连同key一起被加载到缓存中的。
*/
protected abstract V fetchData(K key);
/**
* 从缓存中获取数据(第一次自动调用fetchData从外部获取数据),并处理异常
* @param key
* @return Value
* @throws ExecutionException
*/
protected V getValue(K key) throws ExecutionException {
V result = getCache().get(key);
if (getCache().size() > highestSize) {
highestSize = getCache().size();
highestTime = new Date();
}
return result;
}
public int getMaxSize() {
return maxSize;
}
public void setMaxSize(int maxSize) {
this.maxSize = maxSize;
}
public int getExpireTime() {
return expireTime;
}
public void setExpireTime(int expireTime) {
this.expireTime = expireTime;
}
public TimeUnit getTimeUnit() {
return timeUnit;
}
public void setTimeUnit(TimeUnit timeUnit) {
this.timeUnit = timeUnit;
}
public Date getResetTime() {
return resetTime;
}
public void setResetTime(Date resetTime) {
this.resetTime = resetTime;
}
public long getHighestSize() {
return highestSize;
}
public void setHighestSize(long highestSize) {
this.highestSize = highestSize;
}
public Date getHighestTime() {
return highestTime;
}
public void setHighestTime(Date highestTime) {
this.highestTime = highestTime;
}
}
DefaultGuavaCacheManager
public class DefaultGuavaCacheManager {
private static final Logger LOGGER =
LoggerFactory.getLogger(DefaultGuavaCacheManager.class);
//缓存包装类
private static AbstractGuavaCache<String, Object> cacheWrapper;
/**
* 初始化缓存容器
*/
public static boolean initGuavaCache() {
try {
cacheWrapper = DefaultGuavaCache.getInstance();
if (cacheWrapper != null) {
return true;
}
} catch (Exception e) {
LOGGER.error("Failed to init Guava cache;", e);
}
return false;
}
public static void put(String key, Object value) {
cacheWrapper.getCache().put(key, value);
}
/**
* 指定缓存时效
* @param key
*/
public static void invalidate(String key) {
cacheWrapper.getCache().invalidate(key);
}
/**
* 批量清除
* @param keys
*/
public static void invalidateAll(Iterable<?> keys) {
cacheWrapper.getCache().invalidateAll(keys);
}
/**
* 清除所有缓存项 : 慎用
*/
public static void invalidateAll() {
cacheWrapper.getCache().invalidateAll();
}
public static Object get(String key) {
try {
return cacheWrapper.getCache().get(key);
} catch (Exception e) {
LOGGER.error("Failed to get value from guava cache;", e);
}
return null;
}
/**
* 使用静态内部类实现一个默认的缓存,委托给manager来管理
*
* DefaultGuavaCache 使用一个简单的单例模式
* @param <String>
* @param <Object>
*/
private static class DefaultGuavaCache<String, Object> extends
AbstractGuavaCache<String, Object> {
private static AbstractGuavaCache cache = new DefaultGuavaCache();
/**
* 处理自动载入缓存,按实际情况载入
* @param key
* @return
*/
@Override
protected Object fetchData(String key) {
return null;
}
public static AbstractGuavaCache getInstance() {
return DefaultGuavaCache.cache;
}
}
}