|
Персональные инструменты |
|||
|
Кэширование в веб-приложениях - что, где, когдаМатериал из CustisWikiКороткая ссылка: WebAppCaching
Содержание
АннотацияКэширование — базовый пример компромисса между временем выполнения и памятью, используемый повсеместно и в больших масштабах. Доклад будет являть собой общий обзор приёмов, а также некоторых АНТИ-приёмов кэширования, применяемых в веб-разработке. А именно:
Кэширование в веб-приложениях — что, где, когда @@Виталий ФилипповКто я? @@В CUSTIS — ведущий веб-разработчик Мои доклады и контакты: http://yourcmc.ru/wiki/User:VitaliyFilippov Поддерживаю сборку MediaWiki4Intranet: http://wiki.4intra.net/ («И давно вы страдаете программизмом?»)
Адрес этого доклада: http://lib.custis.ru/WebAppCaching О чём доклад?! @@
Веб-приложения @@ %%Веб-приложение ≈ примерно САЙТ! @@
Схема веб-приложения @@ %%Кэши есть везде! @@ %%Краткий экскурс в архитектуру веб-приложенийЧто такое веб-приложение, сейчас объяснить довольно просто, ибо это просто то, что обеспечивает работу веб-сайтов, а ими мы все пользуемся постоянно. Однако, некоторые особенности хотелось бы повторить:
Для написания собственно веб-приложений на серверной стороне используются абсолютно всевозможные языки программирования — вплоть до функциональных (Haskell, Erlang), низкоуровневых (C/C++), а также всяких Go. Однако наибольшую популярность имеют высокоуровневые, обычно скриптовые, языки — PHP, Java, Perl, Python, Ruby, и в последнее время — JavaScript. ПО, обеспечивающее выполнение приложения, написанного на соответствующем языке, обычно называют сервером приложений. При этом данные обычно хранятся в реляционной СУБД (а возможно, в НЕреляционной, типа MongoDB). Наибольшее распространение имеют свободные MySQL/MariaDB и PostgreSQL. Данных обычно довольно много, запросы по большей части OLTP’шные, то есть их много, но все относительное лёгкие — это диктуется необходимостью отображать страницы за вменяемое время. Общая же черта всех баз данных в том, что они работают медленно относительно самого языка программирования, а также в том, что сетевое взаимодействие с ними порождает накладные расходы. Всё это даёт хорошую почву для внедрения кэширования. На самом деле с кэшами вы сталкиваетесь каждый раз, когда открываете какой-то сайт, причём сталкиваетесь на всех уровнях:
Далее — на сервере:
Об этом всём мы и поговорим далее. Кэширование @@ %%Кэширование @@ %%Обмен вычислений на память! То есть, сохранение и повторное использование чего-нибудь Кэшировать можно почти всё, что угодно — данные, код, соединения, адреса… Определение кэшированияКэширование — это наиболее часто используемый вариант «обмена» памяти на скорость вычислений. Идея в основе лежит простейшая — вместо того, чтобы выполнять одни и те же действия много раз, результат их выполнения можно сохранить в быструю память, а когда он потребуется в следующий раз, просто взять готовый. Это обычно даёт выигрыш в скорости, потому что выполняется меньше действий, но и ведёт к увеличению используемой памяти, потому что вычисленный результат занимает место. Обычно потому, что какие-то действия кэшировать бесполезно: например, зачастую бесполезно пытаться кэшировать результаты математических вычислений, оперирующих большими массивами данных — процессоры во много раз быстрее, чем память, а в кэш самого процессора большие массивы данных не помещаются. При этом вполне может оказаться, что считать готовый результат вычислений из памяти гораздо медленнее, чем вычислить его, и это тем вернее, чем больше степень параллелизма — параллельно работающие процессоры всё больше сражаются за пропускную способность памяти. Также практически бесполезно кэшировать данные, повторный запрос которых маловероятен. Например, легко убить эффективность всего кэша, если умудриться включить в кэшируемую страницу всего одну случайно выбираемую рекламную ссылку. Кстати, кэшировать можно не только результаты вычислений. Например, можно кэшировать соединения, или потоки/процессы в операционной системе. И то, и то применяется повсеместно, так как создание потока или соединения — небыстрая операция. Есть даже отдельный класс приложений — connection pooler’ы для СУБД, почтовых серверов и так далее. В то же время некоторые системы без кэша бы, можно сказать, не работали вообще. Простой пример — это жёсткие диски. Если бы не было кэша, от них было бы очень сложно добиться нормальной скорости даже последовательного чтения/записи, не говоря уже о случайном: на аппаратном уровне диск вынужден всё время писать и читать большими кусками через кэш-буфер — иначе, условно говоря, нужно было бы точно синхронизировать момент чтения/записи следующего сектора с программным обеспечением. Кэш vs БД @@База заранее вычисленных данных — не кэш! Ибо:
Например, memcached — кэш, а Redis — БД. Отличия кэша от БДКэш — не база данных. Если вы заранее вычислите все возможные варианты выходных данных, сохраните их и потом просто будете использовать их в готовом виде — это не кэш (хотя иногда это им называют). Кэш отличается от БД тем, что, как правило, строится на лету, динамически, а также тем, что записи в нём непостоянны, и приложение не должно полагаться на их наличие. То есть, при использовании кэширования всегда существует ветвление в коде: «есть сохранённая запись — используем, нет — вычисляем и сохраняем». Кэш-память, не только очевидно конечна (всё конечно :)), но часто и на порядок/порядки меньше, чем основная — просто потому, что для кэша используется быстрая память, а быстрая память дорога. Поэтому кэш обычно не вмещает все данные приложения (хотя может и вмещать, если приложение маленькое). Поэтому цель системы кэширования в том, чтобы в каждый момент сохранять в кэше наиболее используемые элементы. Если память иссякает, при добавлении элемента обычно определяется элемент или элементы для удаления из кэша и освобождения памяти, с помощью стратегии замещении. Это тоже не всегда так — существуют реализации, которые в этой ситуации просто отказываются добавлять новый элемент. Это не очень хорошо (потенциально снижает эффективность), однако так ведёт себя, например, весьма популярный кэш APC для языка PHP. Кроме того, из-за требования быстрого доступа к кэшу он почти всегда имеет очень простую структуру: таблица вида ключ-значение, из которой можно быстро читать и в которую можно быстро писать по ключу. И ключ, и значение — произвольные строки, а детали реализации (деревья и прочие структуры данных) обычно не видны вообще. Примеры кэшей и Key-Value БД
Чтобы можно было использовать memcached как key-value базу данных, люди придумали memcacheDB — патченый memcached без вытеснения элементов. Сейчас memcacheDB уже, правда, не развивается, и уже больше используется Redis, не в последнюю очередь засчёт его продвинутых возможностей. Где кэшировать данные? @@
Память процесса @@Больше проблем, чем плюсов ☹. Самая главная:
Управляемая память:
Неуправляемая/разделяемая (например, так делают Одноклассники)
Память процессаНа первый взгляд кажется, что проще всего кэшировать объекты прямо внутри процесса приложения, причём в «живом», инициализированном виде внутри управляемой языком памяти. Однако на деле у такого подхода оказывается больше проблем, чем плюсов. Начать следует с того, что в Некоторых Языках (а именно, в PHP и только в PHP) при обычном использовании, без сильных извращений, сохранить что-то живым — будь то объект или загруженный в память класс — до следующего запроса невозможно: при каждом запросе состояние интерпретатора очищается (да-да, зато писать легко). То есть максимум, что можно (но не нужно) сделать в PHP для кэширования в памяти процесса — это использовать разделяемую память (shared memory). Но так делать не надо, так как работать оно будет хуже, чем банальный memcached, а масштабироваться не сможет. Собственно, невозможность масштабирования — то есть, невозможность использования кэша на другом сервере/серверах — верна для любого внутрипроцессного кэша, а не только для PHP, и является основным недостатком. Ещё минусы:
ОК, «живые» объекты не кэшируем. Второй вариант — использовать неуправляемую или разделяемую (с помощью C/C++) память как внешний кэш — для кэширования сериализованных представлений объектов. Теоретически даже можно сделать реализацию, позволяющую размазывать такой кэш по нескольким серверам. Но опять встаёт вопрос — а зачем? Выигрыш в производительности будет невелик, а в остальном это то же самое, что и использование обычного memcached (только писать надо руками). Внешний кэш @@Просто и популярно — memcached ± redis ☺ Может быть разделяемый, распределённый ☹ Накладные расходы на сериализацию и сеть Consistent hashing @@ %%Для быстрого добавления/удаления узлов Внешний кэшПростое и популярное, хотя и чуть более медленное, решение — внешний кэш. Обычно для этого используется популярное решение — memcached и/или redis. Также существуют разные альтернативные решения — например, Bullet Cache, которые могут быть несколько быстрее, чем memcached (но несильно, поэтому, может, и не нужно морочиться). Минус такого решения — накладные расходы на сетевое взаимодействие и сериализацию объектов. Посему:
Зато внешний кэш элементарно масштабируется путём выноса на отдельный сервер или сервера. Для удобства добавления/удаления кэш-серверов можно даже использовать консистентное хеширование, доступное «из коробки» в большинстве клиентских библиотек. Смысл в том, что всё пространство ключей отражается на целые числа, а они представляются в виде кольца и разбиваются на интервалы, соответствующие серверам. После этого при добавлении/удалении серверов расположение меняется не у почти всех элементов, как в обычных хеш-таблицах, а в среднем лишь у 1/N элементов, где N — новое число серверов. Ну а для обеспечения лучшей равномерности этого распределения одному серверу, как правило, отводится не один большой интервал, а много маленьких. Всё это показано на иллюстрации ниже. @@ %%А если данные меняются? Инвалидация кэша @@⇒ Кэш нужно обновлять (сбрасывать). Простейшие варианты:
Инвалидация @@ %%А если у объектов сложные зависимости? Что такое инвалидацияИнвалидация — это обновление элементов в кэше с целью обеспечения актуальности используемых данных. Казалось бы, в чём тут проблема? Если нечто закэшировано по известному ключу, приложение может просто отправить запрос на удаление этого элемента из кэша. Это простейший метод инвалидации — по ключу. Нормально работает в простейших случаях, когда зависимостей между элементами либо нет, либо почти нет. Второй простейший вариант — при любом обновлении делать полный сброс кэша. Опять-таки, если известно, что данные меняются только 1 раз в день, но ВСЕ целиком — это вполне нормальное решение. При появлении зависимостей между элементами всё становится сложнее — кэшируемые объекты образуют весьма развесистый граф, который нужно как-то очищать. Не сбрасывать кэш вообще — пояснениеИногда кэш на самом деле можно и не сбрасывать. Иногда, в кривых системах, это даже происходит неумышленно — разработчики просто не пишут код для сброса кэша. Например, такое встречается во множестве сайтов, построенных на готовых CMS типа 1с битрикса или вордпресса. Кстати, в тему — в плагине WP Super Cache недавно обнаружили уязвимость, позволяющую через этот кэш взломать сервер, навставлять редиректов, Не сбрасывать кэшированные элементы — это более-менее нормальная стратегия, если элементы данных никогда не изменяются, а только добавляются новые. Кэшировать, соответственно, можно страницы объектов — выборки нельзя. Кроме того, нужно помнить, что кэш всё равно нужно будет сбрасывать при обновлении кода системы. По времени (TTL) @@☺ Просто, но не оперативно ☹
Кэширование по времениЕсли оперативность (мгновенная доступность обновлённой информации) не важна — можно просто задавать определённое время жизни (TTL, Time To Live) для кэшируемых элементов. Отслеживать зависимости тогда не нужно — вы просто знаете, что ваше приложение всегда отдаёт элементы, устаревшие не более, чем на заданное время. Системы кэширования обычно сами поддерживают установку TTL при записи в кэш, но если даже нет — не страшно: TTL можно приписывать к записываемым данным, проверять программно при чтении ключа, и при истечении программно же удалять элемент. В этом случае кэш, правда, должен поддерживать вытеснение, иначе память кончится очень быстро. Ещё существует такой способ, как микрокэширование — кэширование с TTL, равным, например, 1 секунде или даже меньше. В высоконагруженной среде даже такой способ может серьёзно повысить производительность засчёт снижения конкуренции между параллельными процессами, при практически неизменной оперативности информации. В вебе это применяют обычно на самом внешнем уровне (например, nginx) для кэширования готовых страниц. Однако следует помнить, что это всё ещё кэш, и что бездумное применение (например, кэширование ВСЕХ выборок из базы) может привести к очень весёлым глюкам. Наиболее гибко — По тегам @@
Теги без поддержки тегов @@Списочный метод:
Версионный метод :
Но: нужен Redis! Инвалидация по тегамНаиболее гибкий из вариантов инвалидации элементов кэша, имеющих сложные зависимости — это теги. Тег представляет собой зависимость ключа кэша от чего-либо, выражается обычно произвольной строкой (как и сам ключ), и позволяет разом удалять из кэша все элементы, на которые он установлен. При этом, в принципе, можно сделать, чтобы и сам тег мог зависеть от других тегов — это и будет отражать весь граф зависимостей между объектами. Теги являются более продвинутым функционалом, чем TTL, и доступны далеко не везде. Однако их тоже можно реализовать поверх существующего кэша, с одной оговоркой — метаданные очень желательно хранить не в самом кэша, а в перманентном key-value хранилище — например, в Redis. Причина всё в том же — записанный в кэш ключ может исчезнуть в любой момент, и если там хранить используемые для сброса метаданные — ключи кэша могут не сброситься, и опять-таки привести к прикольным ошибкам. Варианта реализации два:
Версионный вариант существует в виде путешествующей по просторам интернета реализации тегов от Дмитрия Котерова, но она, увы, не совсем корректна, так как использует для хранения метаданных сам кэш, а не перманентное хранилище. Оценка эффективности кэша @@Главное — ВЫИГРЫШ В ПРОИЗВОДИТЕЛЬНОСТИ
Оценка эффективности кэшированияПервое, на что все обычно смотрят при оценке эффективности кэша — это количество попаданий/промахов. То есть, процент числа случаев, в которых приложение смогло и не смогло использовать кэш соответственно. Естественно, при снижении процента попаданий эффективность кэша тоже снижается; также это может означать, что кэшируется что-то не то, например, слишком «горячие» (часто меняющиеся) элементы. Однако, % попаданий — не единственный и на самом деле не главный показатель. А кроме того, низкий % попаданий может сигнализировать не об одной конкретной, а о разных проблемах:
В целом, главный показатель эффективности кэширования — это просто выигрыш в производительности, который оно даёт. Его можно выяснить путём профилирования или мониторинга приложения с использованием кэша и без, желательно — под нагрузкой. Мониторинг можно делать даже на боевых серверах с помощью легковесных инструментов — например, для PHP есть pinba, позволяющая отправлять из скриптов различные счётчики на сторонний сервер мониторинга, совершенно не влияя на производительность. Кроме того, можно вспомнить о размере кэша и количестве вытеснений:
Ещё один показатель — это среднее число попаданий на элемент, так сказать, «средняя температура по больнице». Смысл следующий — если в среднем после записи в кэш ключ за всё время жизни считывается один раз, по-видимому, это очень редко используемый элемент, и его можно не кэшировать вообще. Этот показатель вычислить сложнее: с момента запуска до момента удаления из кэша первого элемента это просто отношение общего числа попаданий к суммарному количеству элементов; но вот когда элементы начинают удаляться или вытесняться — без дополнительных телодвижений его уже не вычислишь. Типичные фейлы @@ %%(Анти-паттерны кэширования)
Fail № 1 @@ %%«Положил и точно заберу» Например, сессии в memcached Кэш как базаНаиболее простой пример этого неправильного использования — хранение пользовательских сессий в memcached. Системы кэширования обычно полагаются на то, что кэшируемые элементы не первичны, и считают нормальным удалить из кэша любой элемент в любой момент. С этим связана довольно популярная ошибка использования — расчёт приложения на наличие элемента в кэше в каких-то условиях, даже если это условие заключается, например, в наличии в кэше другого элемента. Например, такая ошибка есть в коде MediaWiki :) авторы сначала сохраняют в кэш отдельными элементами все сообщения локализации, плюс дополнительный элемент с индексом, и думают, что если прочитали индекс, то прочитают и сами сообщения — а это, вообще говоря, совсем не так! Ошибка может появиться, даже если вы только что записали этот элемент и сразу считываете. В условиях активного использования кэша и в зависимости от используемой стратегии замещения может вообще не быть гарантии, что «добавленный» элемент будет сохранён в кэше. Кроме того, ошибки такого характера коварны — они могут не проявляться в тестовом окружении, так как нагрузка на приложение и, соответственно, кэш в тестовом окружении обычно сильно меньше. Возвращаясь к хранению сессий в memcached, можно сказать, что оно, вероятно, приведёт к тому, что в боевом окружении, где пользователей много, сессии будут постоянно «слетать». Fail № 2 @@ %%Кэширование авторизованных страниц Или одного и того же списка с выбранным элементом (итог — комбинаторный взрыв) Комбинаторный взрывДанные нужно кэшировать так, чтобы после этого их можно было с пользой употребить. Если каждый элемент кэша будет уникален — кэш будет только память занимать, а не скорость работы увеличивать. Fail № 3 @@ %%Аппарат искусственного дыхания Будет очень грустно его отключать (сбрасывать кэш) Аппарат искусственного дыханияЧасто вместо того, чтобы хоть как-то оптимизировать работу системы, её просто накрывают кэшированием — например, это относится ко ВСЕМ PHP’шным «коробочным» CMS — в их неэффективности лидирует главный отстой под названием 1С-Битрикс. Так тоже делать не надо, ибо:
Справедливости ради можно отметить, что при действительно огромной нагрузке (уровня яндекса? гугла? социальных сетей?) при сбросе кэша лечь может и оптимизированная система. Тогда, если вы действительно уверены, что оптимизировать больше просто нечего — ну что ж, тогда нужно пытаться двигаться в сторону «плавного прогрева» кэша (разрешения сосуществования старого и нового кода, и постепенного ввода в строй серверов с новой версией). Но сначала — оптимизация работы. Fail № 4 @@ %%Cache hit под 100 %, а всё тормозит! Кэшировали яро, но не то, что надо Не то кэшируемСм. выше — #Оценка эффективности кэширования. Заключеньице @@
Клиентское кэширование @@ %%HTTP @@
Особенности HTTPHTTP — это простой текстовый протокол для работы в стиле «запрос-ответ» (браузер посылает запрос, сервер отвечает). Запрос состоит из метода, адреса (URI), заголовков и иногда — тела запроса (обычно при загрузке файлов на сервер). Ответ — из статуса, заголовков и тела. Заголовки — пары вида «Ключ: значение»; различных HTTP-заголовков существует много. После ответа на запрос по желанию клиента и сервера (управляется заголовками) соединение может не закрываться, а оставаться открытым. Это первый пример кэша — кэш соединений, называемый «Keepalive». Работу он ускоряет весьма прилично, особенно, при использовании HTTPS, так как обмен ключами и проверка сертификатов — относительно нетривиальная операция. HTTP «почти» не имеют состояния, то есть каждый запрос выполняется независимо от предыдущего, а ресурсы при этом описываются в RESTоподобном виде с помощью адресов (URI). Однако совсем без состояний многое бы не реализовывалось, поэтому некоторая поддержка всё-таки есть, в виде Cookies. Кроме того, существуют «не совсем веб»-приложения, написанные по аналогии с обычными, и использующие HTTP как просто транспортный протокол. Но таких, к счастью, довольно мало — было бы много, так хорошо бы Web не развился. Блин! Что ещё за кэш? @@google://php отключить кэш
Пацаны, у меня фаервол @@Cache-Control: no-cache, no-store, must-revalidate, max-age=0
Pragma: no-cache
Vary: *
Expires: Thu, 01 Jan 1970 00:00:00 GMT
@@ %%HTTP-кэш любят все @@
HTTP-кэш @@
HTTP-кэшВ протоколе HTTP предусмотрена возможность кэширования ответов от сервера. Можно даже сказать, что возможностей там предусмотрено больше, чем обычно используется :) Некоторые комбинации заголовков управления кэшем довольно странны, хотя своим существованием никому не мешают. Основа — это заголовки запроса и ответа для управления кэшированием, с помощью которых клиент и сервер сообщают друг другу, как кэшировать ответ и можно ли это делать вообще, и статус ответа 304 («содержимое не менялось»), с помощью которого сервер сообщает клиенту, что тот может использовать предыдущий сохранённый ответ. Об этом мы и поговорим далее. HTTP-кэш: схема @@ %%Управление HTTP-кэшированием @@HTTP 1.0: (по времени)
HTTP 1.1: (по времени и значениям)
HTTP-заголовки для управления кэшемHTTP 1.0Кэширование появилось ещё в версии протокола HTTP 1.0 (в которой ещё даже не было Keepalive). Там оно имело относительно простой вид (тем не менее, зачастую достаточный) — только по времени последней модификации и сроку годности, плюс была возможность полного запрета кэширования путём отправки заголовка Pragma: no-cache. Эту прагму все, как правило, включают до сих пор, в расчёте на клиентов, поддерживающих только HTTP 1.0 и не поддерживающих 1.1. Полный запрет кэширования на самом деле означает не то, что клиент вообще не может сохранять ответ, а то, что использовать его без повторного запроса на сервер (валидации) нельзя. Кэширование по времени работает так:
HTTP 1.1Новая версия протокола вместо заголовка Pragma вводит заголовок Cache-Control (причём и в запрос, и в ответ), который даёт более расширенное управление кэшированием. Кроме того, в HTTP 1.1 к поддержке кэширования по времени добавляется возможность кэширования по значениям. А именно, заголовки ETag, If-None-Match и Vary. ETag (entity tag, «тег сущности») и If-None-Match работают аналогично Last-Modified и If-Modified-Since, только значения этих заголовков сравниваются не как даты, и проверяется не то, что дата последней модификации не больше If-Modified-Since, а просто как строки, и проверяется точное соответствие сохранённого на клиенте ETag’а (If-None-Match) актуальному. Эту проверку нужно делать на сервере, включая в ETag версии элементов, использованных на странице. Все кэши в HTTP 1.1 делятся на две категории: «общие» и «личные». Общий кэш — это кэширующий прокси-сервер, который может использоваться многими людьми параллельно и не должен сохранять ответы, содержащие конфиденциальные данные. Личный кэш — это кэш браузера, используемый только одним человеком и, соответственно, пригодный для кэширования таких ответов. Заголовок Vary служит для того, чтобы сказать прокси-серверу, что по одному и тому же адресу (URL) может отдаваться разная страница в зависимости от заголовков запроса, перечисленных в значении Vary. Например, страница может отличаться в зависимости от языка, запрошенного браузером, тогда можно указать Vary: Accept-Language, и прокси-серверы смогут корректно кэшировать ответ. Специальное значение Vary: * действует как запрет кэширования на прокси-серверах — логика * здесь означает «ответ зависит от дополнительных параметров, не включённых в запрос». Например, от IP-адреса клиента или от фазы луны. Соответственно, сами прокси-серверы заголовок Vary: * добавлять не могут. Однако считается, что любой HTTP 1.1 совместимый сервер-источник должен генерировать заголовок Vary с любым кэшируемым ответом, чтобы помочь работать проксям. Cache-Control @@
В Cache-Control… @@ %%…Есть странные опции Странные опции @@
О Cache-ControlТеперь о Cache-Control. Значение заголовка состоит из перечисления директив через запятую. Основные используемые в ответе директивы:
Однако, кроме этого есть и довольно странные и почти не используемые параметры:
В запросе Cache-Control тоже может использоваться, но веб-приложениями этот заголовок запроса обычно не обрабатывается вообще, а браузеры реально используют только один вариант:
Остальные варианты использования весьма странны:
В общем, в протоколе HTTP есть куча фич для обеспечения функционирования прокси-серверов. Только вот текущие тенденции заключаются в том, что прокси-серверов становится всё меньше — они используются в основном в офисах для фильтрации трафика, и мало кто использует их как средство ускорения работы в сети. А HTTPS (HTTP через SSL-шифрованное соединение) возможность кэширования на прокси-сервере вообще убивает на корню, иначе бы нарушалась секретность и прокси-сервер перехватывал бы страницы; а сайтов, доступных только через HTTPS, становится всё больше и больше. Любопытно также, что в новом протоколе SPDY, созданном для ускорения сетевого взаимодействия HTTP, шифрование вообще обязательно ⇒ с ним прокси вообще ничего кэшировать не может. Хотя безопасность самого HTTPS при этом — вещь достаточно специфичная засчёт того, что сертификаты предоставляются коммерческими компаниями, а эти компании совершенно спокойно продают дочерние корневые сертификаты, с помощью которых можно незаметно перехватить трафик любых сайтов, использующих любые их сертификаты. Так что возможно, про прокси-кэши и многие фичи HTTP, с ними связанные, в будущем вообще можно будет забыть. :) Тем не менее, полезное применение у всех этих директив есть — во-первых, для управления кэшем браузера, а во-вторых, с их помощью можно управлять кэшем обратного прокси — вашего личного прокси-сервера, который обычно стоит «перед» серверами приложений и может кэшировать их ответы, что снижает нагрузку на backend’ы. Long Poll @@(как пример кэша соединений) Задача: твиттер/вконтактик, показывать новых котиков в реальном времени. При ожидании ответа сервер подвешивает соединение клиента на N секунд. Заключение @@Для содержимого достаточно отслеживать даты изменений:
Для статики:
ЗаключениеАвторами мелких приложений HTTP-кэширование часто рассматривается как нечто вредное и «лишь ведущее к глюкам», и его стараются просто отключить, для чего во все ответы включают полный набор запрещающих заголовков. Получается этакий «вечный Ctrl-Shift-R» (это сочетания клавиш для сброса кэша в Firefox). Так делать не надо — даже несмотря на то, что прокси-серверов сейчас довольно мало, кэширование полезно как с точки зрения времени открытия страниц браузером. Например, можно хорошо снизить время открытия страниц, если выставить большой срок жизни для всех статических элементов (картинок, стилей и т. п.). Чтобы при этом не поймать глюков, связанных со слишком долгим кэшированием — при изменении статических файлов лучше всего менять и их адрес — тогда срок жизни будет можно делать хоть бесконечным. Кроме того, браузеры, соблюдающие спецификацию HTTP (Firefox, Chrome) при переходе «Назад», если кэширование запрещено, пытаются перезагрузить страницу. Таким образом, заголовки кэширования ещё и помогают пользователю быстрее ходить по истории посещений. И ещё один важный момент — корректные заголовки для управления кэшем любят поисковики. Заголовки позволяют им корректнее и быстрее индексировать сайт и отслеживать даты изменений материалов. Так что использовать кэширование нужно, хотя бы на основе Last-Modified. Кэш приложения @@ %%(основное, на что мы можем повлиять!) Что кэшировать? @@Как можно бОльшие куски информации:
Приёмы @@ %%Что делать с макаронами? [плохим кодом] Приёмы кэшированияПри кэшировании задача — как можно сильнее сократить объём работы. Следовательно, при реализации нужно стараться кэшировать как можно бОльшие куски информации. В случае веб-приложения сначала надо постараться закэшировать страницу целиком. Если в соответствии с требованиями актуальности в вашем случае так делать можно, а комбинаторного взрыва при этом не происходит — супер, делайте так. Однако такое счастье бывает редко — почти всегда есть как минимум авторизация пользователя, а пользователей много, и это не даёт эффективно кэшировать страницы целиком. Однако всё равно не стоит упускать это из виду — наиболее «горячие» страницы (главную?) можно попробовать, например, отдавать кэшированные, и потом уже на клиенте динамически подставлять в них блок с авторизацией. MVC @@ %%Нет понятия «объект»? ⇒ Модель Не можем кэшировать шаблоны, так как непонятно, где шаблоны? ⇒ View StashПри построении страницы сначала выполняется либо берётся из кэша вывод каждого блока, а потом блоки подставляются в layout и, возможно, друг в друга. Проблемой могут оказаться взаимные зависимости («побочные эффекты») выполнения блоков. Простой пример — заголовок страницы. Вставлять его нужно в layout, но определяется он, скорее всего, контроллером основного блока. Для решения нужно разрешить каждому View, кроме просто HTML-кода, выдавать на выходе ещё и небольшой ассоциативный массив — «Stash» («заначку») с данными, предназначенными для других шаблонов и кэшировать их вместе. Остальные побочные эффекты — жестоко удалить. Побочные эффекты? @@ %%⇒ Инкапсулировать их в Stash Связанные сущностиМожет оказаться, что какие-то блоки закэшировать вообще невозможно, но данные там, тем не менее, выводятся. Тогда надо попытаться закэшировать выборку, используемую в этом блоке, причём желательно — со всеми связанными сущностями. Под такими понимаются объекты, подгружаемые в зависимости от основных, например, любые наборы картинок, атрибутов и т. п. Это вообще довольно интересная тема. Проблема связанных сущностей в том, что какие из них действительно нужны для отображения, обычно знает View, но не знает контроллер. Но при этом основную выборку строит контроллер, а не View. Обращение к связанным сущностям стараются сделать максимально простым — таким же, как просто чтение свойства объекта. В итоге дополнительные данные загружаются в цикле для каждого элемента, а не все разом (что медленно). Решение проблемы:
Всё описанное, разумеется, реализуемо только при наличии модели в целом. Если код представляет собой макароны, в которых получение данных из БД перемешано с их выводом и никак не обёрнуто в объекты, так красиво приёмы не применишь. Придётся либо структурировать код и вводить модель, либо забить и оптимизировать всё взаимодействие каждый раз по месту. Lambda-Walk по связанным объектам? @@ %%Либо M-V-Presenter Либо массовая автозагрузка Если шаблон читает из БД связанные объекты……То кэширование выборки основных сущностей становится бесполезным, так как в шаблоне, уже после взятия выборки из кэша, всё равно произойдёт вычитка связанных сущностей отдельными запросами к базе данных, убивающая производительность. Решение — кэшировать основную выборку после view. То есть, при кэш-промахе сначала читать выборку, потом запускать шаблон, а уже потом сохранять дополненную данными выборку в кэш целиком. Когда в следующий раз такая выборка загрузится из кэша, дочитывать из БД уже ничего не будет нужно. Шаблон читает из БД… @@ %%…как кэшировать? ⇒ Кэшировать после шаблона HMVCОбычно вполне нормальный подход — это разбить страницу на отдельные блоки и кэширование их отдельно. Это можно назвать HMVC, просто потому, что с выделением каждого блока в свой маленький контроллер любой MVC превращается в H (Hierarchical, иерархический). Такое разбиение достаточно естественное, так как структура сайтов обычно блочная сама по себе. Обычный набор блоков — это:
HMVC @@ %%Иерархический MVC @@
Об HMVC в KohanaЛюбой PHP-фреймворк — вещь достаточно кривая и состоящая из бесполезных обёрток чуть менее, чем полностью. Это верно и для Zend, и для Yii, и для всяких Kohana. В первую очередь потому, что PHP изначально веб-ориентированный язык и сам по себе содержит практически весь нужный функционал, и единственное, что остаётся фреймворкам — это заворачивать простые процедурные интерфейсы, предоставленные языком, в чуть менее простые объектно-ориентированные обёртки. В языках, более-менее являющихся языками общего назначения (Python, Java, Perl, Ruby) это не совсем так — там фреймворки обычно как минимум выполняют полезную функцию абстрагирования от реализации веб-сервера. Но, например, во всём Zend Framework’е я лично могу назвать 1 полезный кусок — это то, чего не хватает PHP’шному SoapServer’у: автоматический генератор WSDL. Если же учесть, что фреймворки, как правило, задают довольно жёсткие рамки для разработки (шаг в сторону — побег, прыжок на месте — попытка улететь) — получается, что они не только не расширяют возможности языка, но и сужают их. Фреймворк Kohana использует как раз HMVC. Однако, его авторы, похоже, абсолютно не понимают, зачем они используют HMVC. На их сайте какой-то идиот написал, что главное, мол, преимущество HMVC — это сетевая прозрачность, то есть то, что кусок приложения, отвечающий за отдельный блок, можно отсадить на отдельный сервер и система продолжит работать. Сетевая прозрачность в таком виде при масштабировании веб-приложений вообще не нужна и даже бывает вредна засчёт ввода лишнего сетевого взаимодействия. гораздо проще поставить ещё один равнозначный сервер. Главное преимущество HMVC — это как раз кэшируемость! То есть, тот факт, что при разбиении страницы на блоки каждый блок можно кэшировать отдельно, а потом собирать из них страницу. И то, что при минимальных дополнительных усилиях вдобавок к серверному (на уровне приложения) кэшированию можно легко прикрутить ещё и клиентское (на уровне HTTP). Для этого надо всего лишь вычислять время модификации каждого блока (легко делается по тегам, если отслеживать время сброса каждого тега) и разбить обработку запроса на две стадии:
Однако из-за того, что авторы Kohana этого не знают — используя этот фреймворк, так сделать нелегко. Ну и в целом — я (автор доклада) не знаю фреймворка, в котором была бы реализована данная идея. Так что это ещё один аргумент за то, чтобы писать самому и не использовать готовые фреймворки. На что ещё можно влиять @@Веб — не низкий уровень, до кэша CPU не спустишься :)
JavaScript:
На что ещё можно влиять при веб-разработкеВ теории, при разработке пытаться повлиять можно на всё, вплоть до кэшей процессора (которых у него много — L1/L2/L3, кэш TLB…). При низкоуровневом программировании или, например, программировании сложной математики так и стараются делать — засчёт лучшей кэш-локализации можно получить серьёзный выигрыш в производительности. Однако, при веб-разработке спуститься до такого уровня оптимизации трудно, а большого выигрыша это не даст. Разве что можно попытаться оптимизировать работу дисков, например, на уровне размера блока файловой системы — это может повысить производительность при раздаче больших объёмов статических файлов. Кроме этого остаётся лишь:
Кстати, о JavaScript, выполняющемся на клиенте, и его кэше. Сколько бы ни оптимизировали его реализации авторы браузеров, а фреймворками типа ExtJS и jQuery производительность убить всё равно легко. Как минимум, из-за того, что какие-то действия, которые браузер бы в простом виде закэшировал, при использовании фреймворка закэшировать уже не получится. Например, в прототипной объектной ориентированности многие используют всякие $.extend(), и JS движку, вместо того, чтобы спокойно взять из кэша уже разобранное представление вашего класса с функциями, приходится выполнять все эти extend()'ы и динамически формировать прототипы. Не надо так делать — пишите на «голых» прототипах: просто function Cl() {}, а потом Cl.prototype.fn = function() { ... }. Аналогично с обработчиками — если они навешиваются кодом после загрузки страницы, да ещё и на jQuery’вские селекторы — кэшировать это браузер не может, код приходится выполнять. А вот если их inline’ить — то есть, писать на странице всякие onchange() и onclick(), браузер спокойно достанет это из кэша, изначально даже не смотря, что там написано. Аналогично и с просто динамически генерируемыми элементами, особенно, когда их много. Сюда подпадает, например, весь ExtJS, а из примеров помельче сюда попадает WikiEditor для MediaWiki, инициализация которого происходит через заметное время уже после загрузки страницы (что прилично бесит). В общем-то, если со всем этим не перебарщивать, если JS мало — проблем производительности не будет даже с фреймворками. Другое дело, что если JS мало — то и таскать за собой полный обоз костылей в виде фреймворка ни к чему, и лучше не привыкать к их использованию — когда оно таки разрастётся, переписывать будет уже поздно. Кэш СУБД — примеры @@MySQL:
PostgreSQL:
Резюмируем @@
@@ %%http://lib.custis.ru/WebAppCaching vfilippov d0g custis d0t ru
Любые правки этой статьи будут перезаписаны при следующем сеансе репликации. Если у вас есть серьезное замечание по тексту статьи, запишите его в раздел «discussion». Репликация: База Знаний «Заказных Информ Систем» → «Кэширование в веб-приложениях - что, где, когда»
|
||||||||||||