Мысли об использовании кеша: Spring Cache против Caffeine

Spring Boot

Недавно я узнал о локальном кэшировании и обнаружил, что при разработке стека технологий Spring можно использовать аннотации Spring Cache для работы с кэшем, а также нативные API различных схем кэширования. Так является ли официальное решение, предоставленное Spring, наиболее подходящим решением? Тогда эта статья раскроет это для вас через кейс.

Spring Cache

Since version 3.1, the Spring Framework provides support for transparently adding caching to an existing Spring application. The caching abstraction allows consistent use of various caching solutions with minimal impact on the code.

Spring Cache похож на slf4j и jdbc. Это уровень абстракции кэширования, предоставляемый Spring Framwork. Его можно использовать для доступа к различным решениям кэширования. Благодаря интеграции Spring Cache нам нужно управлять кешем только через набор аннотаций. . В настоящее время поддерживаютсяGeneric,JCache (JSR-107),EhCache 2.x,Hazelcast,Infinispan,Couchbase,Redis,Caffeine,Simple, который почти содержит основные схемы локального кэширования.

Основной принцип заключается в том, чтобы внедрить два bean-компонента, Cache и CacheManager, в контекст Spring, а затем с помощью технологии автоматической сборки Spring Boot соответствующие реализации Cache и CacheManager будут автоматически внедрены в соответствии с файлами конфигурации в проекте.

Схема локального кэширования

В стеке технологий Java уже есть много зрелых решений для локального кэширования, в том числе большой и всеобъемлющий ehcache и восходящая звезда Google Guava Cache. Ниже приводится сравнение трех наиболее часто используемых схем локального кэширования, приведенных в блоге.Как спроектировать и использовать кеш изящно?

проект Ehcache Guava Cache Caffeine
Чтение и запись производительности хорошо Хорошо, нам нужно выполнить операцию исключения очень хороший
Алгоритм исключения Поддержка нескольких алгоритмов исключения, LRU, LFU, FIFO ЛРУ, генерал W-TinyLFU, очень хорошо
богатство функций очень функциональный Функция очень богата, поддерживает обновление и виртуальную ссылку и т. Д. По функциям аналогичен Guava Cache.
Размер инструмента Очень большой, последняя версия 1.4MB это небольшая часть класса инструментов Guava, меньшая Общая, последняя версия 644 КБ
Это постоянно да нет нет
Поддерживать ли кластер да нет нет

В настоящее время рекомендуется Caffeine Алгоритм устранения является более продвинутым и поддерживается Spring Cache (новая версия Spring Cache больше не поддерживает Guava Cache). В приведенном ниже коде также используется собственный API Caffeine.

кейс

Люди, которые использовали Spring Cache, должны обнаружить, что CRUD-операция кэша может быть легко реализована с помощью нескольких аннотаций, а замена других схем кэширования не требует изменений в коде и не требует написания шаблонного кода, такого как следующий:

{
    // 缓存命中
    if(cache.getIfPresent(key) != null){
        // todo
    }else{
        // 缓存未命中,IO 获取数据,结果存入缓存
        Object value = repo.getFromDB(key);
        cache.put(key,value);
    }
}

Узнав об этом, у меня возникли сомнения, поскольку Spring разработал аннотацию разработки кеша, и большое количество блогов также ссылается на Spring Cache, нужно ли мне все еще использовать нативный API? В конце концов, после Spring Data JPA мы действительно мало внимания уделяли бэкенд-фреймворку ORM и перестали напрямую использовать Hibernate.

Когда я реализую требование в проекте, эта проблема, кажется, внезапно открывается.

На самом деле требования очень просты: таблица сопоставления, которая изначально хранилась в локальном HashMap, позже была помещена в базу данных из-за частых изменений. Однако, поскольку объем данных невелик и таблица сопоставления не настроена, данные остаются без изменений, поэтому, поскольку я изучаю кэширование, я хочу его добавить. Итак, теперь все, что вам нужно сделать, это:

  1. Метод для чтения всей таблицы таблицы сопоставленияaliasMap(). И кэшировать данные в Caffeine.
  2. Страница, которая поддерживает операции CRUD записи сопоставления и обновляет кэш при изменении таблицы сопоставления.
@Cacheable(value = "default", key = "#root.methodName")
@Override
public Map<String, String> aliasMap() {
	return getMapFromDB();
}

Поскольку аннотации Spring Cache обычно добавляются к классам или методам, другими словами, объекты, возвращаемые методами, кэшируются. Очевидно, что метод запуска обновления объекта в другом кеше не будет работать. Означает ли это, что Spring Cache не может быть реализован? Присмотритесь к принципу Spring Cache, это действительно осуществимо.

Spring Cache внедрит два bean-компонента, Cache и CacheManager, в контекст Spring, а затем автоматически внедрит соответствующие реализации Cache и CacheManager в соответствии с файлами конфигурации в проекте с помощью технологии автоматического связывания Spring Boot. См. еще раз исходный код CaffeineCacheManager:

public class CaffeineCacheManager implements CacheManager {
    private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap(16);
    private boolean dynamic = true;
    private Caffeine<Object, Object> cacheBuilder = Caffeine.newBuilder();
    @Nullable
    private CacheLoader<Object, Object> cacheLoader;
    private boolean allowNullValues = true;
}

Очевидно, что кеш существует в ConcurrentHashMap, таком как cacheMap. Пока мы можем вручную получить экземпляр этого bean-компонента для его работы, это требование может быть выполнено. Код выглядит следующим образом:

@Autowired
private CacheManager cacheManager;
@Cacheable(value = "default", key = "#root.methodName")
@Override
public Map<String, String> aliasMap() {
    return getMapFromDB();
}

private Map<String, String> getMapFromDB() {
    Map<String, String> map = new HashMap<>();
    List<PartAlias> list = repository.findAll();
    list.forEach(x -> map.put(x.getAlias(), x.getName()));
    return map;
}

@Override
public PartAlias saveOrUpdateWithCache(PartAlias obj) {
    PartAlias partAlias = repository.saveAndFlush(obj);
    Cache cache = cacheManager.getCache("default");
    cache.clear();
    cache.put("aliasMap", getMapFromDB());
    return partAlias;
}

После тестирования приведенный выше код выполним. Очевидно, что при столкновении с некоторыми немного сложными требованиями недостаточно полагаться только на аннотации Spring Cache, нам нужно самим управлять объектом кеша. Если вы используете нативный API, он очень прост и подходит для разных нужд.

What's More

Spring Cache по-прежнему может выполнять вышеуказанные требования, но что, если вы хотите автоматически загружать и обновлять данные? Теперь Spring Cache плохо поддерживается.

spring:
  cache:
    type: caffeine
    caffeine:
      spec: maximumSize=1024
    cache-names: cache1,cache2

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

Поэтому при столкновении со сложными сценариями по-прежнему необходимо использовать нативный API, и Spring Cache кажется более чем достаточным. Автор также написал класс инструмента, который может глобально использовать кеш.

@Component
public class CaffeineCacheManager {
    private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<>(16);

    /**
     * 缓存创建
     *
     * @param cacheName
     * @param cache
     */
    public void createCache(String cacheName, Cache cache) {
        cacheMap.put(cacheName, cache);
    }

    /**
     * 缓存获取
     *
     * @param name
     * @return
     */
    public synchronized Cache getCache(String name) {
        Cache cache = this.cacheMap.get(name);
        if (cache == null) {
            throw new IllegalArgumentException("No this cache.");
        }
        return cache;
    }

    @Autowired
    private static CaffeineCacheManager manager;
    public static void main(String[] args) {
        manager.createCache("default", Caffeine.newBuilder()
                .maximumSize(1024)
                .build());
        Cache<String, Object> cache = manager.getCache("default");
        // TODO
    }
}

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

Суммировать

Эта статья не является статьей, в которой рассказывается об использовании Spring Cache и Caffeine (при необходимости вы можете прочитать ссылки), но обсуждаются сценарии использования собственного API Spring Cache и Caffeine. Очевидно, что корзина семейства Spring иногда может быть не оптимальным решением (другое дело — возможность перезаписи)! Поэтому я также надеюсь, что больше блогов в Интернете смогут сосредоточиться на использовании самой структуры, а не на шаблонной интеграции в Spring xxx.

приложение

конфигурация yaml

initialCapacity: # 初始的缓存空间大小
maximumSize: # 缓存的最大条数
maximumWeight: # 缓存的最大权重
expireAfterAccess: # 最后一次写入或访问后经过固定时间过期
expireAfterWrite: # 最后一次写入后经过固定时间过期
refreshAfterWrite: # 创建缓存或者最近一次更新缓存后经过固定的时间间隔,刷新缓存
weakKeys: # 打开 key 的弱引用
weakValues:  # 打开 value 的弱引用
softValues: # 打开 value 的软引用
recordStats: # 开发统计功能

Принципы

Используйте кеш с умом

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

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

Базовые концепты

  • скорость попадания. Количество возвращенных правильных результатов / количество запросов к кешу, чем выше частота попаданий, тем выше уровень использования кеша.
  • самый большой элемент. Максимальное количество элементов, которое может храниться в кеше, после его превышения будет очищено с помощью соответствующей стратегии.
  • Пустая стратегия: FIFO, LFU, LRU

тип кэша

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

  • Локальный кеш: локальный кеш обычно относится к кешу, кэшированному внутри процесса приложения. Возьмем в качестве примера стек технологий Java, но вы можете реализовать HashMap в качестве кэша данных самостоятельно или напрямую использовать готовые решения для кэширования, такие как ehcache, caffeine и т. д.
  • Распределенный кеш: кеш отделен от среды приложений и будет храниться отдельно на собственном сервере или кластере, а несколько приложений могут напрямую совместно использовать кеш. Общие схемы кэширования включают MemCache и Redis.

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

использованная литература