Что такое кэш java

Кеширование данных — Java Spring

Многократно вычитывая одни и те же данные, встает вопрос оптимизации, данные не меняются или редко меняются, это различные справочники и др. информация, т.е. функция получения данных по ключу — детерминирована. Тут наверно все понимают — нужен Кеш! Зачем всякий раз повторно выполнять поиск данных или вычисление?

Так вот здесь я покажу как делать кеш в Java Spring и поскольку это тесно связанно скорее всего с Базой данных, то и как сделать это в СУБД на примере одной конкретной.

Кеш в Spring

Далее все поступают примерно одинаково, в Java используют различные HasMap, ConcurrentMap и др. В Spring тоже для это есть решение, простое, удобное, эффективное. Я думаю что в большинстве случаев это поможет в решении задачи. И так, все что нужно, это включить кеш и аннотировать функцию.

Делаем кеш доступным

Кешируем данные поиска функции

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

Пример реализации репозитория «Person» с использованием кеша

Проверяю что получилось

В тесте вызываю два раза

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

Что такое кэш java. Смотреть фото Что такое кэш java. Смотреть картинку Что такое кэш java. Картинка про Что такое кэш java. Фото Что такое кэш java

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

Есть и более сложные схемы получения ключа, это в документации.

Но конечно встанет вопрос, как обновить данные в кеше? Для этой цели есть две аннотации.

Первая это @CachePut

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

Добавлю в репозиторий два метода: удаления и добавления Person

Выполню поиск Person, удалю, добавлю, опять поиск, но по прежнему буду получать одно и тоже лицо из кеша, пока не вызову «findByNameAndPut»

Что такое кэш java. Смотреть фото Что такое кэш java. Смотреть картинку Что такое кэш java. Картинка про Что такое кэш java. Фото Что такое кэш java

Другая аннотация это @CacheEvict

Позволяет не просто посещать хранилище кеша, но и выселять. Этот процесс полезен для удаления устаревших или неиспользуемых данных из кеша.

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

Там же указываются имена кешей, их может быть несколько. В xml конфигурации это указывается так:

Источник

Проблема многопоточности — локальный кэш. Volatile

— Привет, Амиго! Помнишь, Элли тебе рассказывала про проблемы при одновременном доступе нескольких нитей к общему (разделяемому) ресурсу?

— Так вот – это еще не все. Есть еще небольшая проблема.

Как ты знаешь, в компьютере есть память, где хранятся данные и команды (код), а также процессор, который исполняет эти команды и работает с данными. Процессор считывает данные из памяти, изменяет и записывает их обратно в память. Чтобы ускорить работу процессора в него встроили свою «быструю» память – кэш.

Чтобы ускорить свою работу, процессор копирует самые часто используемые переменные из области памяти в свой кэш и все изменения с ними производит в этой быстрой памяти. А после – копирует обратно в «медленную» память. Медленная память все это время содержит старые(!) (неизмененные) значения переменных.

Вспомним вчерашний пример:

КодОписание
Нить «не знает» о существовании других нитей.

В методе run переменная isCancel при первом использовании будет помещена в кэш дочерней нити. Эта операция эквивалентна коду:

Вызов метода cancel из другой нити поменяет значение переменной isCancel в обычной (медленной) памяти, но не в кэше остальных нитей.

— Ничего себе! А для этой проблемы тоже придумали красивое решение, как в случае с synchronized?

Сначала думали отключить работу с кэшем, но потом оказалось, что из-за этого программы работают в разы медленнее. Тогда придумали другое решение.

Вот как нужно исправить наше решение, чтобы все стало отлично работать:

Источник

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

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

Понятие кеша

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

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

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

Кэширование может обеспечить значительное улучшение производительности 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 строк кода:

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

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

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

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

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

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

Источник

Hibernate cache

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

Кеш первого уровня

Кеш первого уровня всегда привязан к объекту сессии. Hibernate всегда по умолчанию использует этот кеш и его нельзя отключить. Давайте сразу рассмотрим следующий код:

Возможно, Вы ожидаете, что будет выполнено 2 запроса в БД? Это не так. В этом примере будет выполнен 1 запрос в базу, несмотря на то, что делается 2 вызова load(), так как эти вызовы происходят в контексте одной сессии. Во время второй попытки загрузить план с тем же идентификатором будет использован кеш сессии.
Один важный момент — при использовании метода load() Hibernate не выгружает из БД данные до тех пор пока они не потребуются. Иными словами — в момент, когда осуществляется первый вызов load, мы получаем прокси объект или сами данные в случае, если данные уже были в кеше сессии. Поэтому в коде присутствует getName() чтобы 100% вытянуть данные из БД. Тут также открывается прекрасная возможность для потенциальной оптимизации. В случае прокси объекта мы можем связать два объекта не делая запрос в базу, в отличии от метода get(). При использовании методов save(), update(), saveOrUpdate(), load(), get(), list(), iterate(), scroll() всегда будет задействован кеш первого уровня. Собственно, тут нечего больше добавить.

Кеш второго уровня

Если кеш первого уровня привязан к объекту сессии, то кеш второго уровня привязан к объекту-фабрике сессий (Session Factory object). Что как бы подразумевает, что видимость этого кеша гораздо шире кеша первого уровня. Пример:

В данном примере будет выполнено 2 запроса в базу, это связано с тем, что по умолчанию кеш второго уровня отключен. Для включения необходимо добавить следующие строки в Вашем конфигурационном файле JPA (persistence.xml):

Только после всех этих манипуляций кеш второго уровня будет включен и в примере выше будет выполнен только 1 запрос в базу.
Еще одна важная деталь про кеш второго уровня про которую стоило бы упомянуть — хибернейт не хранит сами объекты Ваших классов. Он хранит информацию в виде массивов строк, чисел и т. д. И идентификатор объекта выступает указателем на эту информацию. Концептуально это нечто вроде Map, в которой id объекта — ключ, а массивы данных — значение. Приблизительно можно представить себе это так:

Что есть очень разумно, учитывая сколько лишней памяти занимает каждый объект.
Помимо вышесказанного, следует помнить — зависимости Вашего класса по умолчанию также не кешируются. Например, если рассмотреть класс выше — SharedDoc, то при выборке коллекция users будет доставаться из БД, а не из кеша второго уровня. Если Вы хотите также кешировать и зависимости, то класс должен выглядеть так:

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

Кеш запросов

Перепишем первый пример так:

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

а также переписать пример выше добавив после создания объекта Query (то же справедливо и для Criteria):

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

Стратегии кеширования
Cache region

Регион или область — это логический разделитель памяти вашего кеша. Для каждого региона можна настроить свою политику кеширования (для EhCache в том же ehcache.xml). Если регион не указан, то используется регион по умолчанию, который имеет полное имя вашего класса для которого применяется кеширование. В коде выглядит так:

А для кеша запросов так:

Что еще нужно знать?

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

В дополнение фабрика сессий также может генерировать и сохранять статистику использования всех объектов, регионов, зависимостей в кеше:

Для этого есть объекты Statistics для фабрики и SessionStatistics для сессии.

Методы сессии:
flush() — синхронизирует объекты сессии с БД и в то же время обновляет сам кеш сессии.
evict() — нужен для удаления объекта из кеша cессии.
contains() — определяет находится ли объект в кеше сессии или нет.
clear() — очищает весь кеш.

Источник

Java-модель памяти (часть 2)

Привет, Хабр! Представляю вашему вниманию перевод второй части статьи «Java Memory Model» автора Jakob Jenkov. Первая часть тут.

Аппаратная архитектура памяти

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

Вот упрощенная схема аппаратной архитектуры современного компьютера:
Что такое кэш java. Смотреть фото Что такое кэш java. Смотреть картинку Что такое кэш java. Картинка про Что такое кэш java. Фото Что такое кэш java
Современный компьютер часто имеет 2 или более процессоров. Некоторые из этих процессоров также могут иметь несколько ядер. На таких компьютерах возможно одновременное выполнение нескольких потоков. Каждый процессор (прим. переводчика — тут и далее под процессором автор вероятно подразумевает ядро процессора или одноядерный процессор) способен запускать один поток в любой момент времени. Это означает, что если ваше Java-приложение является многопоточным, то внутри вашей программы может быть запущен одновременно один поток на один процессор.

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

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

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

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

Данные, хранящиеся в кэш-памяти, обычно сбрасываются обратно в основную память, когда процессору необходимо сохранить в кэш-памяти что-то еще. Кэш может очищать свою память и записывать в неё новые данные одновременно. Процессор не должен читать/записывать полный кэш каждый раз, когда он обновляется. Обычно кэш обновляется небольшими блоками памяти, называемыми «строками кэша». Одна или несколько строк кэша могут быть считаны в кэш-память, и одна или более строк кэша могут быть сброшены назад в основную память.

Совмещение Java-модели памяти и аппаратной архитектуры памяти

Как уже упоминалось, Java-модель памяти и аппаратная архитектура памяти различны. Аппаратная архитектура не различает стеки потоков и кучу. На оборудовании стек потоков и куча (heap) находятся в основной памяти. Части стеков и кучи потоков могут иногда присутствовать в кэшах и внутренних регистрах ЦП. Это показано на диаграмме:
Что такое кэш java. Смотреть фото Что такое кэш java. Смотреть картинку Что такое кэш java. Картинка про Что такое кэш java. Фото Что такое кэш java
Когда объекты и переменные могут храниться в различных областях памяти компьютера, могут возникнуть определенные проблемы. Вот две основные:
• Видимость изменений, которые произвёл поток над общими переменными.
• Состояние гонки при чтении, проверке и записи общих переменных.
Обе эти проблемы будут объяснены в следующих разделах.

Видимость общих объектов

Если два или более потока делят между собой объект без надлежащего использования volatile-объявления или синхронизации, то изменения общего объекта, сделанные одним потоком, могут быть невидимы для других потоков.

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

Следующая диаграмма иллюстрирует набросок этой ситуации. Один поток, работающий на левом ЦП, копирует в его кэш общий объект и изменяет значение переменной count на 2. Это изменение невидимо для других потоков, работающих на правом ЦП, поскольку обновление для count ещё не было сброшено обратно в основную память.
Что такое кэш java. Смотреть фото Что такое кэш java. Смотреть картинку Что такое кэш java. Картинка про Что такое кэш java. Фото Что такое кэш java
Для того, чтобы решить эту проблему, вы можете использовать ключевое слово volatile при объявлении переменной. Оно может гарантировать, что данная переменная считывается непосредственно из основной памяти и всегда записывается обратно в основную память, когда обновляется.

Состояние гонки

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

Эта диаграмма иллюстрирует возникновение проблемы с состоянием гонки, которое описано выше:
Что такое кэш java. Смотреть фото Что такое кэш java. Смотреть картинку Что такое кэш java. Картинка про Что такое кэш java. Фото Что такое кэш java
Для решения этой проблемы вы можете использовать синхронизированный блок Java. Синхронизированный блок гарантирует, что только один поток может войти в данный критический раздел кода в любой момент времени. Синхронизированные блоки также гарантируют, что все переменные, к которым обращаются внутри синхронизированного блока, будут считаны из основной памяти, и когда поток выйдет из синхронизированного блока, все обновленные переменные будут снова сброшены в основную память, независимо от того, объявлена ли переменная как volatile или нет.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *