Реализовать локальный кеш Java, начиная с этих точек

Java

Кэш, я считаю, что все должны быть знакомы с ним, в проекте кеш должен быть необходим. На рынке существует множество инструментов кэширования, таких как Redis, Guava Cache или EHcache. Я думаю, что все хорошо знакомы с этими инструментами, поэтому сегодня мы не будем о них, поговорим о том, как реализовать локальное кэширование. Ссылаясь на вышеупомянутые инструменты, брат Пинтоу считает, что для достижения лучшего локального кэша мы должны начать со следующих трех аспектов.

1. Подбор коллекций для хранения

Чтобы реализовать локальное кэширование, контейнер хранилища должен быть структурой данных в форме ключ/значение.В Java это наша часто используемая коллекция карт. На карте есть HashMap, Hashtable и ConcurrentHashMap. Если мы не рассматриваем вопросы безопасности данных в ситуациях с высоким параллелизмом, мы можем выбрать HashMap. Если мы рассматриваем вопросы безопасности данных в ситуациях с высоким параллелизмом, мы можем выбрать один из Hashtable и ConcurrentHashMap.Collection, но мы предпочитаем ConcurrentHashMap, потому что ConcurrentHashMap работает лучше, чем Hashtable.

2. Просроченная обработка кеша

Поскольку кеш хранится непосредственно в памяти, если мы не будем иметь дело с просроченными кешами, память будет занята множеством недействительных кешей, а это не то, что нам нужно, поэтому нам нужно очистить эти недействительные кеши. Обработку кэша с истекшим сроком действия можно реализовать, обратившись к стратегии Redis, которая использует стратегию регулярного удаления + ленивое удаление.

Регулярная политика удаления

Стратегия периодического удаления заключается в регулярном обнаружении кэшей с истекшим сроком действия и их удалении. Преимущество этой стратегии заключается в гарантированном удалении кэшей с истекшим сроком действия. В то же время есть и минусы.Кэш с истекшим сроком действия может не удалиться вовремя, что связано с установленной нами периодичностью синхронизации.Еще один минус в том, что если кешированных данных много, каждое обнаружение тоже будет приносить много давления на чашку.

Стратегия ленивых ликвидации

Стратегия ленивой ликвидации заключается в том, чтобы сначала определить, истек ли срок действия кеша при использовании кеша, удалить его, если он истек, и вернуть пустой. Преимущество этой стратегии в том, что она определяет, истек ли срок ее действия, только при поиске, что оказывает большее влияние на CUP. В то же время у этой стратегии есть фатальный недостаток. Когда хранится большое количество кешей, эти кеши не использовались, срок их действия истек, и они станут недействительными кешами. Эти недействительные кеши будут занимать много места в вашей памяти и в конечном итоге привести к переполнению памяти сервера.

Мы вкратце разобрались с двумя стратегиями обработки просроченного кэша Redis, каждая из которых имеет свои преимущества и недостатки. Поэтому в процессе использования мы можем комбинировать две стратегии, и комбинированный эффект все еще очень идеален.

3. Стратегия ликвидации кеша

Когда кеш с истекшим сроком действия из кеша нужно рассматривать отдельно, из кеша это то, что когда мы достигаем номера буфера кеша мы указываем номер, ведь наша память не безгранична. Если нам нужно продолжать добавлять кеширование, нам нужно удалить существующий кеш на основе стратегии, часть кеша, кеш, чтобы освободить место для недавно добавленного, следуя нескольким общим представлениям о стратегии кеша.

Стратегия первого продвижения

Если места в кеше недостаточно, данные, которые попадают в кеш первыми, будут очищены первыми, чтобы освободить новое пространство для приема новых данных. Эта стратегия в основном сравнивает время создания кэшированных элементов. В некоторых сценариях с высокими требованиями к эффективности данных вы можете выбрать этот тип стратегии, отдавая приоритет обеспечению доступности последних данных.

Политика наименьшего использования

Независимо от того, истекает срок его действия или нет, в зависимости от того, сколько раз используется элемент, очищайте элемент, который используется реже, чтобы освободить место. Эта стратегия в основном сравнивает hitCount (количество попаданий) элементов.В сценарии обеспечения достоверности высокочастотных данных эту стратегию можно выбрать.

Реже всего использованная политика

Независимо от того, просрочен он или нет, согласно метке времени последнего использования элемента элемент с самой дальней использованной меткой времени очищается, чтобы освободить место. Эта стратегия в основном сравнивает время последнего использования кэша командой get. Это более применимо в сценариях данных горячих точек, и приоритет отдается обеспечению достоверности данных горячих точек.

стратегия случайного исключения

Независимо от того, истекает срок его действия или нет, кеш удаляется случайным образом.Если нет необходимости в кэшированных данных, можно рассмотреть эту стратегию.

нет стратегии ликвидации

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

Выше приведены три момента, которые необходимо учитывать при реализации локального кеша.После прочтения мы должны знать, как реализовать локальный кеш.Давайте реализуем локальный кеш вместе.

Реализовать локальное кэширование

В этой демонстрации мы используем ConcurrentHashMap в качестве коллекции хранилища, чтобы даже в случае высокого параллелизма мы могли обеспечить безопасность кеша. Обработка просроченного кеша Здесь я использую только стратегию удаления по времени и не использую стратегию удаления по времени + ленивое удаление Вы можете попробовать использовать эти две стратегии для обработки просроченного кеша самостоятельно. Что касается устранения кеша, здесь я использую стратегию наименьшего использования. Что ж, технический отбор известен, давайте взглянем на кодовую реализацию.

Класс объекта кэша

public class Cache implements Comparable<Cache>{
    // 键
    private Object key;
    // 缓存值
    private Object value;
    // 最后一次访问时间
    private long accessTime;
    // 创建时间
    private long writeTime;
    // 存活时间
    private long expireTime;
    // 命中次数
    private Integer hitCount;
    ...getter/setter()...

добавить кеш

/**
 * 添加缓存
 *
 * @param key
 * @param value
 */
public void put(K key, V value,long expire) {
    checkNotNull(key);
    checkNotNull(value);
    // 当缓存存在时,更新缓存
    if (concurrentHashMap.containsKey(key)){
        Cache cache = concurrentHashMap.get(key);
        cache.setHitCount(cache.getHitCount()+1);
        cache.setWriteTime(System.currentTimeMillis());
        cache.setAccessTime(System.currentTimeMillis());
        cache.setExpireTime(expire);
        cache.setValue(value);
        return;
    }
    // 已经达到最大缓存
    if (isFull()) {
        Object kickedKey = getKickedKey();
        if (kickedKey !=null){
            // 移除最少使用的缓存
            concurrentHashMap.remove(kickedKey);
        }else {
            return;
        }
    }
    Cache cache = new Cache();
    cache.setKey(key);
    cache.setValue(value);
    cache.setWriteTime(System.currentTimeMillis());
    cache.setAccessTime(System.currentTimeMillis());
    cache.setHitCount(1);
    cache.setExpireTime(expire);
    concurrentHashMap.put(key, cache);
}

получить кеш

/**
 * 获取缓存
 *
 * @param key
 * @return
 */
public Object get(K key) {
    checkNotNull(key);
    if (concurrentHashMap.isEmpty()) return null;
    if (!concurrentHashMap.containsKey(key)) return null;
    Cache cache = concurrentHashMap.get(key);
    if (cache == null) return null;
    cache.setHitCount(cache.getHitCount()+1);
    cache.setAccessTime(System.currentTimeMillis());
    return cache.getValue();
}

Получить наименее используемый кеш

    /**
     * 获取最少使用的缓存
     * @return
     */
    private Object getKickedKey() {
        Cache min = Collections.min(concurrentHashMap.values());
        return min.getKey();
    }

Метод обнаружения просроченного кеша

/**
 * 处理过期缓存
 */
class TimeoutTimerThread implements Runnable {
    public void run() {
        while (true) {
            try {
                TimeUnit.SECONDS.sleep(60);
                expireCache();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 创建多久后,缓存失效
     *
     * @throws Exception
     */
    private void expireCache() throws Exception {
        System.out.println("检测缓存是否过期缓存");
        for (Object key : concurrentHashMap.keySet()) {
            Cache cache = concurrentHashMap.get(key);
            long timoutTime = TimeUnit.NANOSECONDS.toSeconds(System.nanoTime()
                    - cache.getWriteTime());
            if (cache.getExpireTime() > timoutTime) {
                continue;
            }
            System.out.println(" 清除过期缓存 : " + key);
            //清除过期缓存
            concurrentHashMap.remove(key);
        }
    }
}

Выше приведен основной код, полный код, который я загрузил наGitHub

Эта статья просто изучает локальный кэш, чтобы обратить внимание на локальный кэш. На самом деле, это также основные технологии кеша, независимо от того, является ли она Redis, Guava Cache или Ehcache или другие инструменты кэша, которые реализованы в принципе, принцип реализации с нашим местным кешем похоже. Пока мы понимаем принцип реализации локального кэша, мы узнаем эти инструменты кэша, я считаю, что это относительно легко.

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

Наконец

Сделайте небольшую рекламу, добро пожаловать, чтобы отсканировать код и подпишитесь на общедоступную учетную запись WeChat: «Технический блог брата Пинтоу», давайте вместе добьемся прогресса.

平头哥的技术博文