Сегодня мы продолжим исследования различных новых и не очень технологий, необычного их применения или просто оригинальных вещей. Возможно, вы вспомните, я когда-то писал о проекте распределённого кеша EHcache для платформы Java. Сегодня настало время продолжить эту тему, однако в другом ракурсе — в виде отдельного RESTful сервера.
Сперва еще раз упомянем о EHcache. Это высокопроизводительная и масштабируемая система кеширования для Java, которая является зрелым и серьезным проектом. Доступны варианты кеширования как в оперативной памяти, так и дискового кеша, а также комбинированные стратегии (также есть и вариант обеспечения сохранности данных при перезагрузках виртуальной машины или сервера). Масштабируемость реализована с помощью асинхронных репликаций и кластеризации кешей при помощи JGroup, JMS, RMI, также можно строить распределенные системы на базе сторонних продуктов (Terracota). Именно распределённость мне нравиться больше всего — индивидуальные настройки для каждого кеша (синхронная/асинхронная репликация) в паре с возможность запускать несколько различных экземпляров внутри одной JVM (или разных). Хотя следует заметить, что EHCache хранит данные в памяти JVM-процесса, соответственно, на его объем наложены некоторые ограничения (на 32-битных системах), однако дискового кеша никто не отменял, да и серьезные сервера все уже 64-битные. Известны инсталляции с 20 и боле Гб данных. Кроме этого, прекрасно поддерживается утилизация многопоточных возможностей, конкурентного доступа и многоядерности (хотя здесь можно поспорить немного, в JBoss Cache с этим вроде как ещё лучше, так как поддерживаются транзакции и кое-какие другие «вкусности», однако он сложнее в освоении, а его API достаточно сложен для понимания, у меня за два подхода никак не вышло с ним разобраться, в то время как EHcache запустился сразу).
Кеш очень быстрый и по доступных в сети тестах обгоняет другие системы кеширования (однако в кластерах из 4-х машин JBoss Cache показывает немного лучший результат, но это система немного другого уровня), в том числе и популярнейший Memcached. Знаю, сравнение немного неверное, так как EHcache является in-process кешером, в то время как memcached отдельный демон и работает как внешний сетевой сервис (поэтому в нем нет таких ограничений на размер кеша). В то же время, если бы сравнивать, что ehcache все же более предпочтителен в силу гораздо большей гибкости и масштабируемости, комбинации памяти/диска и тонких настроек кешей. Вот здесь меня и посетила мысль… а можно ли использовать EHcache вместо memcached-а (или вместе с), при этом оставаясь на привычной мне платформе РНР? Да, можно!
Все дело в том, что комьюнити разработчиков, кроме самого кеша, реализовало еще и кеширующий REST-сервер, доступ к которому можно получить через REST-интерфейс или SOAP. Сделано такое решение на базе embedded-версии сервера Sun GlassFish v3 Prelude и является самодостаточным, включая в себя все необходимые компоненты и зависимости. Работа с сервером происходит по протоколу HTTP, используя методы GET/POST/PUT/DELETE/OPTIONS/HEAD, либо через SOAP (также поверх HTTP) через XML. Поддерживаются все возможности HTTP/1.1, в том числе, keep-alive, а также Last-Modified, ETag, то есть, сервер отдает все корректные заголовки, поэтому часто можно использовать встроенное кеширование на промежуточных узлах при передаче или в самом клиенте. Интересным моментом является возможность работы с несколькими форматами данных, а если быть точным, возможность получить ответ в формате XML или JSON, для чего достаточно задать корректный заголовок MIME-type в запросе.
И так, у нас есть возможность буквально в один клик запустить доступный по простому и понятному протоколу сервер кеширования, а также обращаться к нему с любого языка или платформы, которая поддерживает HTTP-запросы. Давайте попробуем!
Загрузить последнюю версию сервера можно на SourceForge, однако я рекомендую параллельно загрузить и дистрибутив последней версии кеша, а затем обновить файлы сервера, так как в нем используется предыдущая версия кеша. Нас интересует ehcache-standalone-server, который на текущий момент имеет версию 0.7.
В дистрибутиве уже есть скрипты для запуска, читайте README, или для запуска перейдите в каталог lib и выполните запуск вручную:
После указания основного файла сервера стоит номер порта, по которому он будет доступен, а также путь к каталогу с веб-приложением (war-файлом). В консоли после запуска вы увидите ход подключения, а также информацию о запущенных сервисах и портах — например так я узнаю, на каком порту доступен JMX-сервис для управления. Так как используется встраиваемая версия сервера GlassFish (это и веб-сервер и сервер приложений), то его настроек и возможностей достаточно мало, но вы всегда можете развернуть полноценный сервер, не обязательно даже GlassFish, а потом использовать только сам EHcache-сервер, который доступен и отдельно, без веб-сервера.
По-умолчанию, кеши доступны по адресу /ehcache/rest — зайдя браузером или выполнив GET-запрос, мы получим XML-документ с описанием всех текущих настроек кеша. Изначально в конфигурационном файле есть описания нескольких кешей, для примера, в том числе парочка распределенных. Для начала работы лучше всего удалить все базовые настройки и создать свои кеши. Простой кеш, без репликаций, мы сейчас сделаем.
Все настройки кеша сконцентрированы в одном xml-файле — /war/WEB-INF/classes/ehcache.xml, который мы и будем редактировать. Внутри есть достаточно много комментариев и описаний всех опций, поэтому я только опишу кратко как сделать базовый кеш, чтобы продолжить эксперименты.
Что означают эти опции:
Мы пока не обсуждаем варианты репликации — это уже углубленная специфика, о которой пусть лучше расскажут более компетентные специалисты.
И так, наш кеш сконфигурирован так, чтобы держать в памяти 10 тыс. элементов, на диске 1 млрд, не использовать настройки времени жизни, обеспечить постоянство данных между перезагрузками. Объем пула для дисковой записи я выбрал достаточно небольшим, а время проверок на срок жизни дисковых элементов — очень большим (но думаю надо больше, в идеале — посмотрим, можно ли отключить вообще). Для чего именно такая конфигурация? Мне интересно попробовать на базе этого кеша сделать простую key-value базу данных (сегодня это очень популярная тема), при этом обеспечив себе возможность как прямого обращения к кешу из внешних сервисов, так и изнутри РНР веб-приложения. Один нюанс — даже если вам не нужна проверка на время жизни и необходим постоянный кеш, не устанавливайте параметры timeToIdle/timeToLive в 0, иначе сервер может не запускаться (вернее — сервис кеша, сам сервер стартует по выдает исправно 404 ошибку).
Для проверки сохраните отредактированный файл ehcache.xml и перезапустите сервер. Теперь откроем в браузере URL: localhost:8080/ehcache/rest/testRestCache — вы должны получить XML-документ со всеми настройками кеша, а также текущую статистику использования кеша (объем, количество данных, процент попадания и промахов) — в дальнейшем это можно разбирать программно, чтобы выводить в нужном виде (например, в админке).
В дальнейшем я буду рассматривать только REST-часть, для работы через SOAP вам надо поменять в URL rest на soap, получить описание сервисов в WSDL-формате и т.п. Для производительности я у себя просто отключил все неиспользуемое, в том числе ненужные мне кеши и доступ по SOAP-протоколу. Настройки сервлетов доступны в файле web.xml в директории /war/WEB-INF.
Работа с кешем заключается в отправке запросов по HTTP-протоколу и разбору ответа. В случае ошибки, ответ будет в формате text/plain, а в теле запроса будет текст ошибки, HTTP-код будет 404 — например, вы обратились к несуществующему кешу или элементу, тогда ответом будет строка «Element not found: 333» (если вы запросили элемент с ключем 333). Но это справедливо для тех URL, которые обслуживаются сервлетом EHcache, если же ошибка будет в другой части, вы получите стандартную 404 страницу ошибки от GlassFish, которая менее приспособлена к автоматическому разбору.
Работать можно как с сервером вообще (с менеджером кешей), так и индивидуально с каждым кешем и элементом, для этого просто дополните строку URL и используйте нужный метод с параметрами.
Для всего кеш (CacheManager-а):
Дальше по иерархии, если указать в URL конкретное имя кеша, над ним можно производить следующие операции:
На уровне элементов кеша поддерживаются следующие операции:
Когда вы сохраняете элемент в кеш, можно указать его MIME-тип (из списка поддерживаемых), тогда при извлечении мы сразу получим нужные данные. Поддерживаются:
Собственно, вот и все описание самого сервера, теперь практическая часть — как работать с сервером из веб-приложения на PHP. Первоначальной идеей было написание специального Cache Backend для Zend Framework, по аналогии с классом для Memcached-а, но я сначала решил просто экспериментировать, как это все может работать. Возможно, такой класс я все же напишу, если это кому-либо, кроме меня, будет интересно и полезно.
Мы будем использовать Zend Framework для экспериментов, в частности, его классы для работы с HTTP-запросами (Zend_Http_Client) и класс для работы с JSON (Zend_Json).
Для начала необходимо установить подключение к серверу. Zend_Http предоставляет для этого несколько возможностей, разные адаптеры, однако по тестах самым быстрым оказался Socket-адаптер, Curl я бы использовал в последнюю очередь, в случае, если сервер кеша удаленный и к нему другим способом не добраться (например, надо использовать SSL, но для кеша это странное требование, однако в ряде случаев такое необходимо, поправка — сокет также может использовать ssl).
Опишем опции подключения, исходя из максимальной производительности, учитывая, что мы не один запрос в рамках страницы будет делать:
Напомним, что наш основной URL следующий: $_url = 'http://localhost:8080/ehcache/rest/testRestCache';
Для первого примера мы попробуем положить в кеш содержимое большого массива, в качестве которого выступит $_SERVER, при этом мы зададим JSON как тип данных (предварительно мы конвертируем массив в JSON перед отправкой).
Теперь получим назад свой массив, для этого можно даже не менять URL, а только сменить тип запроса, остальное такое же, как в предыдущем коде:
Остальные команды можно задать таким же способом. Первое, что немного ограничивает, что в Zend_Http нет поддержки HEAD-запросов, однако они обычно дублируют другие, так что большой надобности в них нет. Второе неудобство — метаданные о кеше или конкретных элементах отдаются в формате XML, хотя работать с элементами можно и в JSON. Статистика отдается вместе со всеми данными, хотя ее хорошо бы вынести в отдельную страницу. Третье неудобство — нет развитых возможностей по извлечению и добавлению данных. Нельзя сразу положить или запросить несколько элементов (хотя в самом Java API есть). А вот удалить все сразу вполне можно. Ну и безопасность никак не обеспечена, поэтому не храните конфиденциальные данные с доступном по HTTP наружу.
В заключение расскажу о главной мысли этого исследования. Так как данные мы можем получить напрямую в JSON, а веб-сервер поддерживает все возможности HTTP, клиентское приложение, например, на AJAX, вполне может напрямую взаимодействовать с кешем, запрашивая данные и получая их в JSON, а серверная сторона будет асинхронно ложить новые данные, когда они есть. Клиент сперва сам может проверить, есть ли в кеше данные, а если нет — напрямую обратится к серверной части.
Также достаточно просто реализовать шардинг кеша и балансировку нагрузки. Кстати, тогда лучше разворачивать сервер на базе полной версии GlassFish, так как в встраиваемой нет некоторых полезных возможностей, вроде админки, gzip-сжатия траффика и балансировщика нагрузки. Можно также использовать фронт-эндом nginx, который будет балансировать нагрузку между серверами, а они в фоне между собой реплицируются средствами Java. Протокол HTTP простой и достаточно гибкий, поэтому мы можем реализовать любую стратегию поведения кеширующего сервера, комбинируя возможности HTTP и платформы Java.
P.S. Пара слов о производительности. Конечно, мои тесты далеки от реальных и никак не могут быть достоверными и вообще что-либо значить. Усредненная цифра, полученные на моей машине (ноутбук для разработки, 1.5 Гб RAM/Celeron M 1.7 Ггц, WinXP SP3) в процессе подготовки материала — 0.020 — 0.025 сек. на операции чтения/записи (если использовать cURL, то примерно в два раза дольше). Интересно конечно протестировать вариант с репликацией и балансировкой нагрузки, но это уже совсем другой уровень, но я бы с радостью принял участие и посмотрел на результаты.
P.P.S. Отвечая на вопрос — а зачем это все? В некоторых случаях может заменить другие системы кеширования, тот же memcached, так как дает более гибкие настройки кешей, постоянство данных, различные системы репликации, отлично масштабируется и распределяется, данные могут быть получены клиентской системой напрямую (AJAX). В то же время, если у вас часть бекенда работает на Java, или даже весь, ей будет гораздо проще складывать туда данные. EHcache также может работать как отлично масштабируемая и надежная key-value база данных, обеспечивая именно репликацию и кластеризацию серьезного уровня, в отличие от множества новых решений, ehcache имеет уже продолжительную историю развития и оптимизации.
Мне кажется, если взять только сам сервлет, обеспечивающий REST-интерфейс, и поставить его на какой-либо быстрый и максимально легкий веб-сервер, например, Tjws, добавив легкий балансировщик, выделив отдельную JVM под каждый кеш (развернув двух-узловой кластер на каждом физическом сервере по сути) — мы получим намного более быструю и легкую систему с отличной масштабируемостью. А если добавить свой сервлет, буквально несколько строк, можем организовать поддержку и других протоколов/форматов — очень интересен был бы такой кешер с возможностью получать данные через Thrift/Google ProtoBuff, учитывая, что клиенты под эти протоколы есть на клиентских машинах (на JS и ActionScript). Поле для исследований широкое и интересное, верно?
Сперва еще раз упомянем о EHcache. Это высокопроизводительная и масштабируемая система кеширования для Java, которая является зрелым и серьезным проектом. Доступны варианты кеширования как в оперативной памяти, так и дискового кеша, а также комбинированные стратегии (также есть и вариант обеспечения сохранности данных при перезагрузках виртуальной машины или сервера). Масштабируемость реализована с помощью асинхронных репликаций и кластеризации кешей при помощи JGroup, JMS, RMI, также можно строить распределенные системы на базе сторонних продуктов (Terracota). Именно распределённость мне нравиться больше всего — индивидуальные настройки для каждого кеша (синхронная/асинхронная репликация) в паре с возможность запускать несколько различных экземпляров внутри одной JVM (или разных). Хотя следует заметить, что EHCache хранит данные в памяти JVM-процесса, соответственно, на его объем наложены некоторые ограничения (на 32-битных системах), однако дискового кеша никто не отменял, да и серьезные сервера все уже 64-битные. Известны инсталляции с 20 и боле Гб данных. Кроме этого, прекрасно поддерживается утилизация многопоточных возможностей, конкурентного доступа и многоядерности (хотя здесь можно поспорить немного, в JBoss Cache с этим вроде как ещё лучше, так как поддерживаются транзакции и кое-какие другие «вкусности», однако он сложнее в освоении, а его API достаточно сложен для понимания, у меня за два подхода никак не вышло с ним разобраться, в то время как EHcache запустился сразу).
Кеш очень быстрый и по доступных в сети тестах обгоняет другие системы кеширования (однако в кластерах из 4-х машин JBoss Cache показывает немного лучший результат, но это система немного другого уровня), в том числе и популярнейший Memcached. Знаю, сравнение немного неверное, так как EHcache является in-process кешером, в то время как memcached отдельный демон и работает как внешний сетевой сервис (поэтому в нем нет таких ограничений на размер кеша). В то же время, если бы сравнивать, что ehcache все же более предпочтителен в силу гораздо большей гибкости и масштабируемости, комбинации памяти/диска и тонких настроек кешей. Вот здесь меня и посетила мысль… а можно ли использовать EHcache вместо memcached-а (или вместе с), при этом оставаясь на привычной мне платформе РНР? Да, можно!
Все дело в том, что комьюнити разработчиков, кроме самого кеша, реализовало еще и кеширующий REST-сервер, доступ к которому можно получить через REST-интерфейс или SOAP. Сделано такое решение на базе embedded-версии сервера Sun GlassFish v3 Prelude и является самодостаточным, включая в себя все необходимые компоненты и зависимости. Работа с сервером происходит по протоколу HTTP, используя методы GET/POST/PUT/DELETE/OPTIONS/HEAD, либо через SOAP (также поверх HTTP) через XML. Поддерживаются все возможности HTTP/1.1, в том числе, keep-alive, а также Last-Modified, ETag, то есть, сервер отдает все корректные заголовки, поэтому часто можно использовать встроенное кеширование на промежуточных узлах при передаче или в самом клиенте. Интересным моментом является возможность работы с несколькими форматами данных, а если быть точным, возможность получить ответ в формате XML или JSON, для чего достаточно задать корректный заголовок MIME-type в запросе.
И так, у нас есть возможность буквально в один клик запустить доступный по простому и понятному протоколу сервер кеширования, а также обращаться к нему с любого языка или платформы, которая поддерживает HTTP-запросы. Давайте попробуем!
Загрузить последнюю версию сервера можно на SourceForge, однако я рекомендую параллельно загрузить и дистрибутив последней версии кеша, а затем обновить файлы сервера, так как в нем используется предыдущая версия кеша. Нас интересует ehcache-standalone-server, который на текущий момент имеет версию 0.7.
В дистрибутиве уже есть скрипты для запуска, читайте README, или для запуска перейдите в каталог lib и выполните запуск вручную:
- java -jar ./ehcache-standalone-server-0.7.jar 8080 ../war
* This source code was highlighted with Source Code Highlighter.
После указания основного файла сервера стоит номер порта, по которому он будет доступен, а также путь к каталогу с веб-приложением (war-файлом). В консоли после запуска вы увидите ход подключения, а также информацию о запущенных сервисах и портах — например так я узнаю, на каком порту доступен JMX-сервис для управления. Так как используется встраиваемая версия сервера GlassFish (это и веб-сервер и сервер приложений), то его настроек и возможностей достаточно мало, но вы всегда можете развернуть полноценный сервер, не обязательно даже GlassFish, а потом использовать только сам EHcache-сервер, который доступен и отдельно, без веб-сервера.
По-умолчанию, кеши доступны по адресу /ehcache/rest — зайдя браузером или выполнив GET-запрос, мы получим XML-документ с описанием всех текущих настроек кеша. Изначально в конфигурационном файле есть описания нескольких кешей, для примера, в том числе парочка распределенных. Для начала работы лучше всего удалить все базовые настройки и создать свои кеши. Простой кеш, без репликаций, мы сейчас сделаем.
Все настройки кеша сконцентрированы в одном xml-файле — /war/WEB-INF/classes/ehcache.xml, который мы и будем редактировать. Внутри есть достаточно много комментариев и описаний всех опций, поэтому я только опишу кратко как сделать базовый кеш, чтобы продолжить эксперименты.
Что означают эти опции:
- name — имя кеша, которое будет использоваться для доступа (будет в URL, поэтому на содержание накладываются те же ограничения, лучше всего — краткое и четко обозначающее тип хранимых данных). В рамках одного сервера может быть множество кешей с разными именами.
- maxElementsInMemory — максимальное количество элементов, которые размещаются в памяти
- maxElementsOnDisk — максимальное количество элементов на диске (0 — без ограничений)
- eternal — указывает, что можно игнорировать установки жизни кеша, тогда элементы будут всегда в кеше, пока вы их не удалите вручную
- overflowToDisk — указывает, могут ли элементы быть вытеснены на диск, если достигнуто максимальное количество объектов в памяти
- timeToIdleSeconds — время от последнего доступа к объекту до момента признания его невалидным.(если он не помечен как eternal). Опциональный параметр
- timeToLiveSeconds — время жизни элемента (с версии 1.6, если не ошибаюсь, эта опция может быть задана для каждого отдельного объекта кеша
- diskPersistent — указывает на сохранение состояния кеша на диске между рестартами
- diskExpiryThreadIntervalSeconds — периодичность запуска процесса проверок объектов на диске на истечение TTL (времени жизни).
- diskSpoolBufferSizeMB — объем пула, который выделен кешу для буферизации записи на диск. Когда пул заполнен, асинхронно вызывается команда записи на диск состояния кеша
- memoryStoreEvictionPolicy — обозначает стратегию определения, какие объекты кеша должны быть вытеснены на диск. Может быть LRU (Least Recently Used), по дате последнего использования, FIFO (First In First Out, первый добавлен, первый вытеснен) и LFU (Less Frequently Used, по частоте использования)
Мы пока не обсуждаем варианты репликации — это уже углубленная специфика, о которой пусть лучше расскажут более компетентные специалисты.
- <cache name="testRestCache"
- maxElementsInMemory="10000"
- eternal="true"
- timeToIdleSeconds="0"
- timeToLiveSeconds="0"
- overflowToDisk="true"
- diskSpoolBufferSizeMB="4"
- maxElementsOnDisk="1000000000"
- diskPersistent="true"
- diskExpiryThreadIntervalSeconds="3600"
- memoryStoreEvictionPolicy="LFU"
- />
* This source code was highlighted with Source Code Highlighter.
И так, наш кеш сконфигурирован так, чтобы держать в памяти 10 тыс. элементов, на диске 1 млрд, не использовать настройки времени жизни, обеспечить постоянство данных между перезагрузками. Объем пула для дисковой записи я выбрал достаточно небольшим, а время проверок на срок жизни дисковых элементов — очень большим (но думаю надо больше, в идеале — посмотрим, можно ли отключить вообще). Для чего именно такая конфигурация? Мне интересно попробовать на базе этого кеша сделать простую key-value базу данных (сегодня это очень популярная тема), при этом обеспечив себе возможность как прямого обращения к кешу из внешних сервисов, так и изнутри РНР веб-приложения. Один нюанс — даже если вам не нужна проверка на время жизни и необходим постоянный кеш, не устанавливайте параметры timeToIdle/timeToLive в 0, иначе сервер может не запускаться (вернее — сервис кеша, сам сервер стартует по выдает исправно 404 ошибку).
Для проверки сохраните отредактированный файл ehcache.xml и перезапустите сервер. Теперь откроем в браузере URL: localhost:8080/ehcache/rest/testRestCache — вы должны получить XML-документ со всеми настройками кеша, а также текущую статистику использования кеша (объем, количество данных, процент попадания и промахов) — в дальнейшем это можно разбирать программно, чтобы выводить в нужном виде (например, в админке).
В дальнейшем я буду рассматривать только REST-часть, для работы через SOAP вам надо поменять в URL rest на soap, получить описание сервисов в WSDL-формате и т.п. Для производительности я у себя просто отключил все неиспользуемое, в том числе ненужные мне кеши и доступ по SOAP-протоколу. Настройки сервлетов доступны в файле web.xml в директории /war/WEB-INF.
Работа с кешем заключается в отправке запросов по HTTP-протоколу и разбору ответа. В случае ошибки, ответ будет в формате text/plain, а в теле запроса будет текст ошибки, HTTP-код будет 404 — например, вы обратились к несуществующему кешу или элементу, тогда ответом будет строка «Element not found: 333» (если вы запросили элемент с ключем 333). Но это справедливо для тех URL, которые обслуживаются сервлетом EHcache, если же ошибка будет в другой части, вы получите стандартную 404 страницу ошибки от GlassFish, которая менее приспособлена к автоматическому разбору.
Работать можно как с сервером вообще (с менеджером кешей), так и индивидуально с каждым кешем и элементом, для этого просто дополните строку URL и используйте нужный метод с параметрами.
Для всего кеш (CacheManager-а):
- OPTIONS — метод возвращает описание доступных операций в формате WADL (Web Application Description Language)
- GET — возвращает в XML-формате список доступных кешей на сервере и их параметры. Это обычный запрос через браузер, как мы делали выше, например: localhost:8080/ehcache/rest/
Дальше по иерархии, если указать в URL конкретное имя кеша, над ним можно производить следующие операции:
- OPTIONS — аналогично вышеописанному, возвращает WADL-описание доступных операций
- HEAD — возвращает те же мета-данные с описанием параметров кеша, но в виде HTTP-заголовков, а не в теле ответа (как в GET)
- GET — XML документ с параметрами кеша и его статистикой.
- PUT — позволяет создать новый кеш (имя которого передано в строке URL) на основе настроек дефолтного кеша (задается в конфигурационном файле).
- DELETE — удаляет указанный в URL кеш. Именно удаляет, а не очищает (для этого есть другая команда, как ни странно, среди операций над элементами кеша), похоже, до следующей перезагрузки сервера (но я не проверял еще этот момент).
На уровне элементов кеша поддерживаются следующие операции:
- OPTIONS — аналогично вышеописанному, возвращает WADL-описание доступных операций
- HEAD — возвращает содержимое элемента в виде строки в HTTP-заголовке (здесь есть неоднозначность в справке, так как для остальных случаев HEAD дублирует GET, для элементов же кеша указано что он именно метаданные возвращает, а не значение).
- GET — возвращает непосредственно содержимое элемента кеша в теле ответа.
- PUT — ложит данные в кеш. Сами данные передаются в теле запроса, имя объекта — в URL, а дополнительный параметр, время жизни, можно передать в HTTP-заголовке с именем «ehcacheTimeToLiveSeconds», учитывайте, что если параметра нет, будет использован параметр из описания кеша, а интервал допустимых значений — 0 (вечно)… 2147483647 (69 лет примерно).
- DELETE — удаляет указанный элемент. Если надо удалить все элементы кеша, используйте маску *, жаль, что другие методы такого не поддерживают (то есть, multi-get-а на уровне REST нет, хотя сам по себе кеш вполне его поддерживает в JavaAPI).
Когда вы сохраняете элемент в кеш, можно указать его MIME-тип (из списка поддерживаемых), тогда при извлечении мы сразу получим нужные данные. Поддерживаются:
- text/plain — обычный текст или произвольные данные
- text/xml — XML-документ, согласно RFC 3023
- application/json — самое интересное, JSON-формат (согласно RFC 4627)
- application/x-java-serialized-object — сериализированный Java-объект
Собственно, вот и все описание самого сервера, теперь практическая часть — как работать с сервером из веб-приложения на PHP. Первоначальной идеей было написание специального Cache Backend для Zend Framework, по аналогии с классом для Memcached-а, но я сначала решил просто экспериментировать, как это все может работать. Возможно, такой класс я все же напишу, если это кому-либо, кроме меня, будет интересно и полезно.
Мы будем использовать Zend Framework для экспериментов, в частности, его классы для работы с HTTP-запросами (Zend_Http_Client) и класс для работы с JSON (Zend_Json).
Для начала необходимо установить подключение к серверу. Zend_Http предоставляет для этого несколько возможностей, разные адаптеры, однако по тестах самым быстрым оказался Socket-адаптер, Curl я бы использовал в последнюю очередь, в случае, если сервер кеша удаленный и к нему другим способом не добраться (например, надо использовать SSL, но для кеша это странное требование, однако в ряде случаев такое необходимо, поправка — сокет также может использовать ssl).
Опишем опции подключения, исходя из максимальной производительности, учитывая, что мы не один запрос в рамках страницы будет делать:
- $_config = Array(
- 'timeout' => 5,
- 'maxredirects' => 1,
- 'httpversion' => 1.1,
- 'adapter' => 'Zend_Http_Client_Adapter_Sockets',
- 'options' => array(
- 'persistent' => true,
- ),
- 'keepalive' => true
- );
* This source code was highlighted with Source Code Highlighter.
Напомним, что наш основной URL следующий: $_url = 'http://localhost:8080/ehcache/rest/testRestCache';
Для первого примера мы попробуем положить в кеш содержимое большого массива, в качестве которого выступит $_SERVER, при этом мы зададим JSON как тип данных (предварительно мы конвертируем массив в JSON перед отправкой).
- //создаем объект подключения
- $ehcache_connect = new Zend_Http_Client('http://localhost', $_config);
- //имя нашего объекта в кеше, его уникальный id
- $_chache_item_name = 'testitem1';
- // задаем полный путь к элементу
- // localhost:8080/ehcache/rest/testRestCache/testitem1
- $ehcache_connect->setUri($_url . '/' . $_chache_item_name);
- //укажем, что мы используем JSON
- $ehcache_connect->setHeaders('Content-type', 'application/json');
- //установим метод
- $ehcache_connect->setMethod(Zend_Http_Client::PUT);
- //добавляем данные с кодированием в JSON
- $ehcache_connect->setRawData(Zend_Json::encode($_SERVER));
- //Все! Выполняем запрос
- $response = $ehcache_connect->request();
- // мы получили ответ в виде объекта класса Zend_Http_Response
- if ($response->isSuccessful())
- {
- //все ок, запрос успешен, сервер вернул правильный HTTP-ответ с кодом 200
- echo 'Request OK!';
- }
- else
- {
- echo $response->getMessage();
- }
* This source code was highlighted with Source Code Highlighter.
Теперь получим назад свой массив, для этого можно даже не менять URL, а только сменить тип запроса, остальное такое же, как в предыдущем коде:
- // URL нашего объекта
- $ehcache_connect->setUri($_url . '/' . $_chache_item_name);
-
- // Метод
- $ehcache_connect->setMethod(Zend_Http_Client::GET);
-
- //выполняем запрос
- $_result = $ehcache_connect->request();
-
- //если все ОК
- if ($_result->isSuccessful())
- {
- // получаем тело запроса и декодируем его из JSON обратно в Array
- $_json_res = Zend_Json::decode($_result->getBody(), Zend_Json::TYPE_ARRAY);
-
- // Выведем на экран
- Zend_Debug::dump($_json_res);
- }
- else
- {
- echo $response->getMessage();
- }
* This source code was highlighted with Source Code Highlighter.
Остальные команды можно задать таким же способом. Первое, что немного ограничивает, что в Zend_Http нет поддержки HEAD-запросов, однако они обычно дублируют другие, так что большой надобности в них нет. Второе неудобство — метаданные о кеше или конкретных элементах отдаются в формате XML, хотя работать с элементами можно и в JSON. Статистика отдается вместе со всеми данными, хотя ее хорошо бы вынести в отдельную страницу. Третье неудобство — нет развитых возможностей по извлечению и добавлению данных. Нельзя сразу положить или запросить несколько элементов (хотя в самом Java API есть). А вот удалить все сразу вполне можно. Ну и безопасность никак не обеспечена, поэтому не храните конфиденциальные данные с доступном по HTTP наружу.
В заключение расскажу о главной мысли этого исследования. Так как данные мы можем получить напрямую в JSON, а веб-сервер поддерживает все возможности HTTP, клиентское приложение, например, на AJAX, вполне может напрямую взаимодействовать с кешем, запрашивая данные и получая их в JSON, а серверная сторона будет асинхронно ложить новые данные, когда они есть. Клиент сперва сам может проверить, есть ли в кеше данные, а если нет — напрямую обратится к серверной части.
Также достаточно просто реализовать шардинг кеша и балансировку нагрузки. Кстати, тогда лучше разворачивать сервер на базе полной версии GlassFish, так как в встраиваемой нет некоторых полезных возможностей, вроде админки, gzip-сжатия траффика и балансировщика нагрузки. Можно также использовать фронт-эндом nginx, который будет балансировать нагрузку между серверами, а они в фоне между собой реплицируются средствами Java. Протокол HTTP простой и достаточно гибкий, поэтому мы можем реализовать любую стратегию поведения кеширующего сервера, комбинируя возможности HTTP и платформы Java.
P.S. Пара слов о производительности. Конечно, мои тесты далеки от реальных и никак не могут быть достоверными и вообще что-либо значить. Усредненная цифра, полученные на моей машине (ноутбук для разработки, 1.5 Гб RAM/Celeron M 1.7 Ггц, WinXP SP3) в процессе подготовки материала — 0.020 — 0.025 сек. на операции чтения/записи (если использовать cURL, то примерно в два раза дольше). Интересно конечно протестировать вариант с репликацией и балансировкой нагрузки, но это уже совсем другой уровень, но я бы с радостью принял участие и посмотрел на результаты.
P.P.S. Отвечая на вопрос — а зачем это все? В некоторых случаях может заменить другие системы кеширования, тот же memcached, так как дает более гибкие настройки кешей, постоянство данных, различные системы репликации, отлично масштабируется и распределяется, данные могут быть получены клиентской системой напрямую (AJAX). В то же время, если у вас часть бекенда работает на Java, или даже весь, ей будет гораздо проще складывать туда данные. EHcache также может работать как отлично масштабируемая и надежная key-value база данных, обеспечивая именно репликацию и кластеризацию серьезного уровня, в отличие от множества новых решений, ehcache имеет уже продолжительную историю развития и оптимизации.
Мне кажется, если взять только сам сервлет, обеспечивающий REST-интерфейс, и поставить его на какой-либо быстрый и максимально легкий веб-сервер, например, Tjws, добавив легкий балансировщик, выделив отдельную JVM под каждый кеш (развернув двух-узловой кластер на каждом физическом сервере по сути) — мы получим намного более быструю и легкую систему с отличной масштабируемостью. А если добавить свой сервлет, буквально несколько строк, можем организовать поддержку и других протоколов/форматов — очень интересен был бы такой кешер с возможностью получать данные через Thrift/Google ProtoBuff, учитывая, что клиенты под эти протоколы есть на клиентских машинах (на JS и ActionScript). Поле для исследований широкое и интересное, верно?