Статьи

Введение в кеширование для приложений Java (часть 1)

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

Понятие кеша

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

public interface Cache { Object get(final Object key);Object put(final Object key, final Object value);}

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

Улучшение производительности с помощью кэширования

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

Например, рассмотрим приложение, в котором отображается 50-строчный отчет о состоянии системы, отображаемый на веб-странице после входа пользователя в систему. Для каждой строки он отправляет сложный запрос SQL в базу данных. Выполнение каждого запроса и получение результатов по сети занимает в среднем 100 миллисекунд. Общее среднее время сбора всех данных для страницы составляет около 5 секунд. Получение того же результата из кэша занимает около 5 микросекунд на современном процессоре с частотой 2 ГГц. Улучшение производительности для этого конкретного сценария использования составляет 1 000 000!

Требование к временной и пространственной локализации

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

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

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

Характеристики производительности кэша

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

Политика выселения кэша

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

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

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

Простой LRU Cache в десяти строках

В Java 5 добавлен класс java.util.LinkedHashMap. Основная цель этого класса — предоставить ключ, значение и итерацию входа, которые соответствуют порядку входа. Кроме того, LinkedHashMap включает в себя положение для реализации простого LRU-кэша. Ниже приведена реализация простого LRU-кэша, который использует только 10 строк кода:

import java.util.Map;import java.util.LinkedHashMap;  public final class SimpleLRUCache extends LinkedHashMap{     private int maxSize;    public SimpleLRUCache(final int maxSize) {        super(1, 0.75f, true);               this.maxSize = maxSize;    }    protected boolean removeEldestEntry(final Map.Entry eldest) {         return size() > maxSize;     }   }

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

Сценарии использования общего кэша

Распространенные сценарии использования кэша включают кэш приложения, кэш второго уровня (L2) и гибридный кэш.

Кэш приложения

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

Следующая схема связи иллюстрирует использование кэша приложения:

 

 

Кэш 2-го уровня

Одним из основных сценариев использования кеша является кеш уровня 2 (L2). Кэш L2 предоставляет услуги кэширования для инфраструктуры объектно-реляционного отображения (ORM) или инфраструктуры отображения данных (DM), таких как Hibernate или iBatis соответственно. Кэш L2 скрывает сложность логики кэширования от приложения.

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

Следующая схема связи иллюстрирует использование кэша L2:

 

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

Гибридный кэш

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

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

Следующая схема связи иллюстрирует использование гибридного кэша:

 

 

Анти-паттерны кеширования

Кэширование обеспечивает такое значительное улучшение производительности, что его часто используют без ограничений. Анти-паттерн Cache Them All характеризуется кэшированием всех данных, независимо от временной или пространственной локальности доступа к данным. Cache Them All ухудшает производительность приложения, а не повышает его. Ухудшение производительности вызвано накладными расходами на поддержание кэша без снижения стоимости доступа к часто используемым данным.

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

Кеширование продуктов для Java

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

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

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

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

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

Коммерческие продукты

Бесплатные продукты

 

Об авторе

Cacheonix Systems . Вы можете связаться со Славой по адресу simeshev@cacheonix.com .