Статьи

Кэширование лучших практик

Вступление

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

Лучшие практики

  1. Коллекция ключ / значение не является кэшем

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

    • политика выселения
    • максимальный размер
    • постоянный магазин
    • ключи слабых ссылок
    • статистика

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

  2. Используйте слой абстракции кеша

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

  3. Остерегайтесь кэширования

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

  4. Если ваши запросы к базе данных медленные, кеш должен быть вашим последним средством

    Если вы используете инструмент ORM , такой как Hibernate, это первое место, с которого должен начаться процесс оптимизации. Убедитесь, что стратегия выборки разработана правильно, и вы не страдаете от проблем с N + 1 запросами . Вы также можете установить количество операторов SQL для проверки сгенерированных ORM запросов.

    Когда вы закончите оптимизировать генерацию SQL-запросов ORM, вы должны проверить свою базу данных на медленные запросы. Убедитесь, что все индексы на месте и что ваши SQL-запросы эффективны. Индексы всегда должны помещаться в ОЗУ, иначе вы попадете на более дорогой SSD или HDD. Ваша база данных имеет возможность кэшировать результаты запроса, поэтому воспользуйтесь этим.

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

    Если всех этих действий недостаточно, вы можете подумать о профессиональном решении для кэширования, таком как Memcached .

  5. Как насчет согласованности данных?

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

Время игры

Вдохновленный этим очень интересным сообщением о добавлении Java 8 computeIfAbsent Map, я решил представить вам альтернативу Guava Cache, которая имеет следующие преимущества:

  1. фиксированный размер кеша 2 записи
  2. это работает с Java 1.6
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
private LoadingCache<Integer, Integer> fibonacciCache = CacheBuilder.newBuilder()
        .maximumSize(2)
        .build(new CacheLoader<Integer, Integer>() {
            public Integer load(Integer i) {
                if (i == 0)
                    return i;
 
                if (i == 1)
                    return 1;
 
                LOGGER.info("Calculating f(" + i + ")");
                return fibonacciCache.getUnchecked(i - 2) + fibonacciCache.getUnchecked(i - 1);
            }
        });
 
@Test
public void test() {
    for (int i = 0; i < 10; i++) {
        LOGGER.info("f(" + i + ") = " + fibonacciCache.getUnchecked(i));
    }
}

И вывод:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
INFO  [main]: FibonacciGuavaCacheTest - f(0) = 0
INFO  [main]: FibonacciGuavaCacheTest - f(1) = 1
INFO  [main]: FibonacciGuavaCacheTest - Calculating f(2)
INFO  [main]: FibonacciGuavaCacheTest - f(2) = 1
INFO  [main]: FibonacciGuavaCacheTest - Calculating f(3)
INFO  [main]: FibonacciGuavaCacheTest - f(3) = 2
INFO  [main]: FibonacciGuavaCacheTest - Calculating f(4)
INFO  [main]: FibonacciGuavaCacheTest - f(4) = 3
INFO  [main]: FibonacciGuavaCacheTest - Calculating f(5)
INFO  [main]: FibonacciGuavaCacheTest - f(5) = 5
INFO  [main]: FibonacciGuavaCacheTest - Calculating f(6)
INFO  [main]: FibonacciGuavaCacheTest - f(6) = 8
INFO  [main]: FibonacciGuavaCacheTest - Calculating f(7)
INFO  [main]: FibonacciGuavaCacheTest - f(7) = 13
INFO  [main]: FibonacciGuavaCacheTest - Calculating f(8)
INFO  [main]: FibonacciGuavaCacheTest - f(8) = 21
INFO  [main]: FibonacciGuavaCacheTest - Calculating f(9)
INFO  [main]: FibonacciGuavaCacheTest - f(9) = 34
  • Код доступен на GitHub .