предисловие
В современном стремительном развитии Интернета широко используется технология кэширования. Будь то в отрасли или за ее пределами, до тех пор, пока упоминаются проблемы с производительностью, каждый выпалит: «Решите их с помощью кеша».
Это утверждение однобоко или даже наполовину понятно, но как профессионалы мы должны иметь более глубокое и широкое понимание кэширования.
Технология кэширования существует во всех аспектах сценариев приложений. От запросов браузера до обратных прокси-серверов, от внутрипроцессного кэширования до распределенного кэширования. Среди них стратегии и алгоритмы кэширования также появляются одна за другой.Сегодня я познакомлю вас с кэшем.
текст
Кэш вполне знаком каждому разработчику, для повышения производительности программы мы добавим кеш, но куда добавить кеш и как добавить кеш?
Предполагая, что веб-сайту необходимо повысить производительность, кеш можно разместить в браузере, на обратном прокси-сервере, в процессе приложения и в системе распределенного кеша.
От данных запроса пользователя до возврата данных данные проходят через браузер, CDN, прокси-сервер, сервер приложений и различные ссылки базы данных. Каждая ссылка может использовать технологию кэширования.
Начать запрашивать данные из браузера/клиента и получать изменения данных через HTTP и CDN.При обращении к прокси-серверу (Nginx) вы можете получать статические ресурсы через обратный прокси.
Далее вниз к серверу приложений данные могут быть получены с помощью внутрипроцессного (внутрикучного) кеша, распределенного кеша и других прогрессивных методов. Если ни один из вышеперечисленных кешей не попал в данные, источник будет возвращен в базу данных.
Порядок запросов кэша следующий: пользовательский запрос → кэш HTTP → кэш CDN → кэш прокси-сервера → внутрипроцессный кэш → распределенный кэш → база данных.
Кажется, что кэширование можно добавить к каждой ссылке в технической архитектуре, чтобы увидеть, как каждая ссылка применяет технологию кэширования.
1. HTTP-кеширование
Когда пользователь запрашивает сервер через браузер, будет инициирован HTTP-запрос.Если каждый HTTP-запрос кэшируется, нагрузка на сервер приложений может быть снижена.
При первом запросе локальная библиотека кеша браузера не имеет кешированных данных, она извлечет данные с сервера и поместит их в библиотеку кеша браузера.При следующем запросе он будет читать локальную или сервисную Информация.
Общая информация передается через заголовки HTTP-запроса. В настоящее время существует два распространенных метода кэширования, а именно:
-
Принудительно кэшировать
-
Сравнить кеш
1.1. Принудительное кэширование
Когда локальная библиотека кэша браузера сохраняет кэшированную информацию, кэшированные данные можно использовать напрямую, если кэшированные данные не признаны недействительными. В противном случае данные необходимо получить снова.
Этот механизм кэширования кажется относительно простым, так как же определить, являются ли кэшированные данные недействительными? Здесь нужно обратить внимание на два поля Expires и Cache-Control в заголовке HTTP.
Expires — это время истечения срока действия, возвращаемое сервером.При первом запросе клиента к серверу сервер вернет время истечения срока действия ресурса. Если клиент снова запрашивает сервер, он сравнивает время запроса со временем истечения срока действия.
Если время запроса меньше времени истечения, это означает, что срок действия кеша не истек, и можно напрямую использовать информацию из локальной библиотеки кеша.
Напротив, это означает, что срок действия данных истек, и информацию необходимо повторно получить с сервера, а последнее время истечения будет обновлено после получения.
Этот метод больше используется в HTTP 1.0, а Cache-Control будет использоваться вместо него в HTTP 1.1.
В Cache-Control есть свойство max-age, единица измерения — секунды, которое используется для указания времени истечения кэшированного контента на стороне клиента.
Например: max-age 60 секунд, а в текущем кеше нет данных, после первого запроса клиента данные помещаются в локальный кеш.
Тогда, если клиент отправит запрос в течение 60 секунд, сервер приложений запрашиваться не будет, а данные будут возвращены прямо из локального кеша. Если время между двумя запросами превышает 60 секунд, то данные необходимо получить с сервера.
1.2 Сравнить кеш
Необходимо сравнить два флага кеша до и после, чтобы определить, следует ли использовать кеш. Когда браузер запрашивает в первый раз, сервер вернет идентификатор кеша вместе с данными, и браузер сделает резервную копию этих двух данных в локальном репозитории кеша. Когда браузер снова запрашивает, он отправляет резервный идентификатор кэша на сервер.
Сервер оценивает в соответствии с идентификатором кеша.Если оцененные данные не изменились, он отправляет код состояния 304 об успешном решении в браузер.
Затем браузер может использовать кэшированные данные. Сервер возвращает только заголовок, а не тело.
Два правила идентификации описаны ниже:
1.2.1. Правила Last-Modified/If-Modified-Since
Когда клиент запрашивает в первый раз, сервер вернет время последней модификации ресурса, записанное как Last-Modified. Клиент кэширует это поле вместе с ресурсом.
После сохранения Last-Modified оно будет отправлено с полем Last-Modified-Since при следующем запросе.
Когда клиент снова запрашивает сервер, он отправляет Last-Modified вместе с запрошенным ресурсом на сервер.В это время Last-Modified будет называться If-Modified-Since, а сохраненное содержимое будет тем же.
Сервер получает запрос и сравнивает поле If-Modified-Since с полем Last-Modified, хранящимся на сервере:
-
Если время последней модификации Last-Modified на сервере больше запрошенного If-Modified-Since, это означает, что ресурс был изменен, и ресурс (включая Заголовок+Тело) будет возвращен в браузер, а статус будет возвращен код 200.
-
Если время последней модификации ресурса меньше или равно If-Modified-Since, это означает, что ресурс не был изменен, возвращается только заголовок и возвращается код состояния 304. Когда браузер получает это сообщение, он может использовать данные библиотеки локального кэша.
Примечание. Last-Modified и If-Modified-Since относятся к одному и тому же значению, но вызываются по-разному на стороне клиента и сервера.
1.2.2 Правила ETag / If-None-Match
Когда клиент запрашивает в первый раз, сервер генерирует тег ETag для каждого ресурса. Этот ETag представляет собой уникальную строку хэша, сгенерированную в соответствии с каждым ресурсом.При изменении ресурса соответственно изменяется ETag, а затем ETag возвращается клиенту, и клиент кэширует запрошенный ресурс и ETag локально.
После сохранения ETag он будет отправлен как поле If-None-Match при следующем запросе.
Когда браузер запрашивает один и тот же ресурс с сервера во второй раз, он вместе отправляет на сервер ETag, соответствующий этому ресурсу. ETag преобразуется в If-None-Match по запросу, но его содержимое не меняется.
Когда сервер получает запрос, он сравнивает If-None-Match с ETag ресурса на сервере:
-
Если оно несовместимо, это означает, что ресурс был изменен, тогда возвращается ресурс (Заголовок+Тело) и возвращается код состояния 200.
-
Если он такой же, это означает, что ресурс не был изменен, затем возвращает заголовок и возвращает код состояния 304. Когда браузер получает это сообщение, он может использовать данные библиотеки локального кэша.
Примечание. ETag и If-None-Match относятся к одному и тому же значению, но вызываются по-разному на стороне клиента и сервера.
2. Кэш CDN
HTTP-кеширование в основном кэширует статические данные и кэширует данные, полученные с сервера, на клиент/браузер.
Если вы добавите уровень CDN между клиентом и сервером, вы можете позволить CDN предоставлять кеш для сервера приложений, а если вы кешируете в CDN, вам не нужно запрашивать сервер приложений. И две стратегии, упомянутые в кэшировании HTTP, также могут быть реализованы на сервере CDN.
Полное название CDN — Content Delivery Network, то есть Content Delivery Network.
Давайте посмотрим, как это работает:
-
Клиент отправляет URL-адрес на DNS-сервер.
-
DNS разрешает доменное имя и направляет запрос балансировщику нагрузки DNS в сети CDN.
-
Балансировщик нагрузки DNS сообщает DNS об IP-адресе ближайшего узла CDN, а DNS сообщает клиенту IP-адрес последнего узла CDN.
-
Клиент запрашивает ближайший узел CDN.
-
Узел CDN получает ресурсы от сервера приложений и возвращает их клиенту, кэшируя при этом статическую информацию. Примечание. Объектом следующего взаимодействия клиента является кэш CDN, и CDN может синхронизировать информацию кэша с сервером приложений.
CDN принимает запрос клиента, это ближайший к клиенту сервер, он свяжет за собой несколько серверов, играющих роль кэширования и балансировки нагрузки.
3. Кэш балансировки нагрузки
После разговора о кэшировании на стороне клиента (HTTP) и кэшировании CDN мы все ближе и ближе подходим к службе приложения, и запрос должен пройти через балансировщик нагрузки, прежде чем достигнет службы приложения.
Хотя его основной задачей является балансировка нагрузки серверов приложений, его также можно использовать для кэширования. Здесь могут кэшироваться некоторые редко изменяемые данные, такие как информация о пользователе и информация о конфигурации. Достаточно периодически обновлять этот кеш через сервис.
Взяв в качестве примера Nginx, давайте посмотрим, как это работает:
-
Прежде чем пользовательский запрос достигнет сервера приложений, он сначала получит доступ к балансировщику нагрузки Nginx, и если кешированная информация будет найдена, она будет возвращена пользователю напрямую.
-
Если информация о кеше не найдена, Nginx возвращается к серверу приложений для получения информации.
-
Кроме того, существует служба обновления кеша, которая периодически обновляет относительно стабильную информацию в сервере приложений в локальный кеш Nginx.
4. Кэш в процессе
Через клиент, CDN, балансировщик нагрузки мы, наконец, приходим к серверу приложений. Приложения развернуты на сервере приложений, и эти приложения работают в виде процессов, так что же такое кеш в процессе?
Внутрипроцессный кеш также называется кешем управляемой кучи.На примере Java эта часть кеша размещается в управляемой куче JVM и будет затронута алгоритмом перезапуска управляемой кучи.
Поскольку он работает в памяти и быстро реагирует на данные, мы обычно помещаем сюда горячие данные.
Когда внутрипроцессный кеш отсутствует, мы будем искать внепроцессный кеш или распределенный кеш. Преимущество этого кеша в том, что нет сериализации и десериализации, и это самый быстрый кеш. Недостатком является то, что объем кеша не может быть слишком большим, что влияет на производительность сборщика мусора.
В настоящее время наиболее популярными реализациями являются Ehcache, GuavaCache и Caffeine. Эти архитектуры могут легко поместить некоторые горячие данные во внутрипроцессный кеш.
Здесь нам нужно обратить внимание на несколько стратегий утилизации кеша.Стратегии утилизации конкретной архитектуры реализации будут разными, но общая идея одна и та же:
-
FIFO (First In First Out): Алгоритм «первым поступил — первым обслужен», данные, которые первыми помещаются в кеш, удаляются первыми.
-
LRU (наименее недавно использованный): Алгоритм наименее недавно использовавшегося удаляет из кэша данные, которые не использовались в течение самого длительного времени.
-
LFU (наименее часто используемый): наименее часто используемый алгоритм, данные, которые используются реже всего в течение определенного периода времени, удаляются из кэша.
В современной распределенной архитектуре, если внутрипроцессный кэш используется в нескольких приложениях, возникнут проблемы с согласованностью данных.
Здесь рекомендуются два варианта:
-
модификация очереди сообщений
-
Схема модификации таймера
4.1 Схема модификации очереди сообщений
После того, как приложение изменяет свои собственные кэшированные данные и данные базы данных, оно отправляет уведомление об изменении данных в очередь сообщений.Другие приложения подписываются на уведомление о сообщении и изменяют кэшированные данные при получении уведомления.
4.2 Схема модификации таймера
Чтобы избежать связанности и уменьшить сложность, он не чувствителен к «согласованности в реальном времени». Каждое приложение запускает таймер, чтобы регулярно извлекать последние данные из базы данных и обновлять кеш.
Однако после того, как некоторые приложения обновят базу данных, другие узлы будут считывать грязные данные, прежде чем получать данные через Timer. Здесь необходимо контролировать частоту Таймера, а также приложений и сценариев, не требующих высокой производительности в реальном времени.
Каковы сценарии использования внутрипроцессного кеша?
-
Сценарий 1: данные только для чтения, вы можете загрузить их в память при запуске процесса. Конечно, загрузка данных во внепроцессный кэш-сервис, такой как Redis, также может решить эту проблему.
-
Сценарий 2: высокий параллелизм, вы можете рассмотреть возможность использования внутрипроцессного кеша, например: Spike.
5. Распределенный кеш
После разговора о внутрипроцессном кеше естественно перейти к внепроцессному кешу. В отличие от внутрипроцессного кеша, внепроцессный кеш находится вне процесса, в котором запущено приложение. Он имеет больший объем кеша и может быть развернут на разных физических узлах. Обычно он реализуется в распределенном кеше.
Распределенный кэш — это служба кэширования, отделенная от приложений. Самая большая особенность заключается в том, что это независимое приложение/служба, изолированное от локальных приложений.Несколько приложений могут напрямую совместно использовать одно или несколько приложений/служб кэша.
Поскольку это распределенный кеш, кэшированные данные будут распределяться по разным узлам кеша, а размер данных, кэшируемых каждым узлом кеша, обычно ограничен.
Данные кешируются на разных узлах, и чтобы легко получить доступ к этим узлам, нужно ввести кеширующий прокси, аналогичный Twemproxy. Он поможет запросу найти соответствующий узел кэша.
В то же время, если количество узлов кеша увеличивается, прокси будет только идентифицировать и сегментировать новые данные кеша на новые узлы для горизонтального расширения.
Чтобы улучшить доступность кеша, к исходному узлу кеша будет добавлена конструкция Master/Slave. Когда кэшированные данные записываются на главный узел, копия одновременно синхронизируется с подчиненным узлом.
После выхода из строя Master-узла вы можете напрямую переключиться на Slave-узел через прокси, в это время Slave-узел становится Master-узлом для обеспечения нормальной работы кеша.
Каждый кеш-узел также предоставляет механизм истечения срока действия кеша и регулярно сохраняет кешированное содержимое в файл в виде снимков, что удобно для запуска прогревочной загрузки после сбоя кеша.
5.1 Высокая производительность
При распределении кеша данные будут распределяться между каждым приложением/службой кеша в соответствии с определенными правилами.
Если мы назовем эти узлы кэширования приложений/сервисов кэшем, то каждый узел обычно может кэшировать определенный объем данных, например, узел Redis может кэшировать 2 ГБ данных.
Если объем данных, которые необходимо кэшировать, относительно велик, для достижения этого необходимо расширить несколько узлов кеша Что делать, если запрос клиента не знает, к какому узлу обращаться с таким количеством узлов кеша? Как разместить кэшированные данные на этих узлах?
Кэширующий прокси-сервис помог нам решить эти проблемы, например: Twemproxy может не только помочь в маршрутизации кэша, но и управлять узлами кэша.
Вот три алгоритма для кэширования данных, с помощью которых прокси-сервер кэша может легко найти сегментированные данные.
5.1.1 Алгоритм хеширования
Хеш-таблица — наиболее распространенная структура данных, реализуемая путем хэширования ключевых значений записей данных, а затем присвоения данных остатку, полученному по модулю количества узлов кэша, которые необходимо фрагментировать.
Например: Есть три записи данных R1, R2, R3. Их идентификаторы соответственно равны 01, 02 и 03. Если предположить, что идентификаторы этих трех записей используются в качестве ключевых значений, результат алгоритма хеширования по-прежнему будет 01, 02 и 03.
Мы хотим поместить эти три части данных в три узла кеша.Мы можем взять результат по модулю 3, чтобы получить остаток, который является узлом кеша, где размещены три записи.
Алгоритм хеширования в некоторой степени является средним размещением, и стратегия относительно проста.Если вы хотите добавить узлы кеша, в существующие данные будут внесены большие изменения.
5.1.2 Алгоритм последовательного хеширования
Согласованное хеширование — это сопоставление данных со сквозным хеш-кольцом в соответствии с собственными значениями, а также сопоставление узлов кэша с этим кольцом.
Если вы хотите кэшировать данные, вы можете найти собственное место хранения на кольце через значение ключа данных (Key). Эти данные располагаются на кольце по порядку в соответствии со значением, полученным после взятия Хэша по собственному ID.
Если вы хотите вставить новые данные в это время, их идентификатор равен 115, тогда они должны быть вставлены в позицию, как показано ниже.
Точно так же, если вы хотите добавить кеш-узел N4 150, вы также можете поместить его в положение, показанное на рисунке ниже.
Этот алгоритм имеет относительно небольшие накладные расходы для увеличения данных кэша и кэширования узлов.
5.1.3 Алгоритм на основе диапазона
Этот метод делит данные на разные интервалы по значениям ключей (например, ID), и каждый узел кэша отвечает за один или несколько интервалов. Аналогично последовательному хэшированию.
Например: есть три кэш-узла N1, N2, N3. Интервалы, которые они используют для хранения данных: N1 (0, 100), N2 (100, 200), N3 (300, 400].
Затем данные будут соответствующим образом размещены в этих областях после хэширования данных по собственному идентификатору в качестве ключа.
5.2 Доступность
В соответствии с двумя сторонами, в то время как распределенный кеш обеспечивает высокую производительность, мы также должны обращать внимание на его доступность. Итак, от каких потенциальных рисков нам нужно защищаться?
5.2.1 Лавина кэша
Когда кеш становится недействительным, срок действия кеша очищается, а кеш обновляется. Запрос не может попасть в кеш. В это время запрос вернется к источнику непосредственно в базу данных.
Если вышеуказанные ситуации возникают часто или одновременно, это приведет к тому, что большая часть запросов будет направляться непосредственно в базу данных, что приведет к узким местам доступа к базе данных. Мы называем эту ситуацию лавиной кеша.
Подумайте о решении со следующих двух аспектов:
Аспект кэша:
-
Чтобы избежать аннулирования кеша одновременно, установите разные тайм-ауты для разных ключей.
-
Добавьте мьютекс для блокировки и защиты операции обновления кеша, чтобы гарантировать, что только один поток обновляет кеш. Если кеш недействителен, его можно быстро восстановить, сделав снимок кеша. К узлу кэша добавляется первично-резервный механизм, и при сбое основного кэша он переключается на резервный кэш для продолжения работы.
Что касается дизайна, вот несколько советов для справки:
-
Механизм прерывателя цепи: когда узел кэша не работает, необходимо уведомить прокси-сервер кэша, чтобы он не перенаправлял запросы на этот узел, что сокращает время ожидания пользователя и время запроса.
-
Текущий механизм ограничения: Текущее ограничение может быть выполнено на уровне доступа и уровне прокси.Если служба кэширования не может поддерживать высокий уровень параллелизма, внешний интерфейс может ставить в очередь или отбрасывать неотвечающие запросы.
-
Механизм изоляции: когда кеш не может предоставлять услуги или разогревается и перестраивается, запрос помещается в очередь, чтобы запрос не направлялся на другие узлы кеша из-за изоляции.
-
Таким образом, другие узлы не будут затронуты проблемой этого узла. Когда кэш перестраивается, запросы извлекаются из очереди и обрабатываются последовательно.
5.2.2 Проникновение в кэш
Кэш обычно представляет собой ключ, и существует метод значения.Если значение, соответствующее ключу, не существует, запрос будет возвращен в базу данных.
Если соответствующее значение не существует все время, база данных будет часто запрашиваться, что приведет к ограничению доступа к базе данных. Если кто-то использует эту уязвимость для атаки, у него будут проблемы.
Решение: если запрос значения, соответствующий ключу, возвращает пустое значение, мы по-прежнему кэшируем пустой результат.Если значение не изменяется, база данных не будет запрашиваться для следующего запроса.
Хешируйте все возможные данные в достаточно большое растровое изображение, тогда несуществующие данные будут перехвачены этим фильтром растрового изображения, избегая давления запросов на базу данных.
5.2.3 Разбивка кэша
Во время запроса данных определенный кеш просто недействителен или записывается в кеш, и кешированные данные могут быть запрошены сверхвысоким одновременным доступом в этот момент времени и стать «горячими» данными.
Это проблема разбивки кеша.Разница между этим и лавинным кешем в том, что это для определенного кеша, а первое для нескольких кешей.
Решение: Причина проблемы в том, что кеш читается/записывается одновременно, поэтому гарантируется только одновременная запись только одного потока.После завершения записи другие запросы могут снова использовать кеш.
Более распространенной практикой является использование мьютекса (блокировка взаимного исключения). Когда кеш недействителен, вместо немедленной записи в кеш сначала устанавливается мьютекс (блокировка взаимного исключения). После записи в кеш блокировка снимается для запроса на доступ.
резюме
Подводя итог, можно выделить пять стратегий проектирования кеша, начиная с запросов пользователей:
-
HTTP-кэш
-
Кэш CDN
-
кеш балансировки нагрузки
-
Кэш внутри процесса
-
Распределенный кеш
Среди них первые два кэшируют статические данные, а последние три кэшируют динамические данные:
-
Кэширование HTTP включает принудительное кэширование и контрастное кэширование.
-
Кэширование CDN и HTTP-кэширование — хорошие партнеры.
-
Балансировщик нагрузки кэширует относительно стабильные ресурсы и нуждается в службах, помогающих в его работе.
-
Внутрипроцессный кеш эффективен, но его емкость ограничена.Есть два решения проблемы синхронизации кеша.
-
Распределенный кэш имеет большую емкость и широкие возможности, учитывая три алгоритма производительности и предотвращая три риска кэширования.
Обратите внимание на официальную учетную запись [Zero One Technology Stack] и продолжайте продвигать высококачественные серверные технологии без добавок, включая основу виртуальной машины, многопоточное программирование, высокопроизводительную структуру, асинхронное ПО, промежуточное ПО для кэширования и обмена сообщениями, распределенное и микросервисы, изучение архитектуры и Advanced и другие учебные материалы и статьи.