Как стать автором
Обновить

EHcache RESTful сервер, РНР и просто эксперименты…

Время на прочтение13 мин
Количество просмотров3.8K
logoСегодня мы продолжим исследования различных новых и не очень технологий, необычного их применения или просто оригинальных вещей. Возможно, вы вспомните, я когда-то писал о проекте распределённого кеша 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 и выполните запуск вручную:

  1. 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, по частоте использования)

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

  1. <cache name="testRestCache"
  2.       maxElementsInMemory="10000"
  3.       eternal="true"
  4.       timeToIdleSeconds="0"
  5.       timeToLiveSeconds="0"
  6.       overflowToDisk="true"
  7.       diskSpoolBufferSizeMB="4"
  8.       maxElementsOnDisk="1000000000"
  9.       diskPersistent="true"
  10.       diskExpiryThreadIntervalSeconds="3600"
  11.       memoryStoreEvictionPolicy="LFU"
  12.       />
* 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-а):

  • GET — возвращает в XML-формате список доступных кешей на сервере и их параметры. Это обычный запрос через браузер, как мы делали выше, например: localhost:8080/ehcache/rest/


cache_manager_options


Дальше по иерархии, если указать в 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).

Опишем опции подключения, исходя из максимальной производительности, учитывая, что мы не один запрос в рамках страницы будет делать:

  1. $_config = Array(
  2.   'timeout' => 5,
  3.   'maxredirects' => 1,
  4.   'httpversion' => 1.1,
  5.   'adapter' => 'Zend_Http_Client_Adapter_Sockets',
  6.   'options' => array(
  7.      'persistent' => true,
  8.   ),
  9.   'keepalive' => true
  10. );
* This source code was highlighted with Source Code Highlighter.


Напомним, что наш основной URL следующий: $_url = 'http://localhost:8080/ehcache/rest/testRestCache';

Для первого примера мы попробуем положить в кеш содержимое большого массива, в качестве которого выступит $_SERVER, при этом мы зададим JSON как тип данных (предварительно мы конвертируем массив в JSON перед отправкой).

  1. //создаем объект подключения
  2. $ehcache_connect = new Zend_Http_Client('http://localhost', $_config);
  3. //имя нашего объекта в кеше, его уникальный id
  4. $_chache_item_name = 'testitem1';
  5. // задаем полный путь к элементу
  6. // localhost:8080/ehcache/rest/testRestCache/testitem1
  7. $ehcache_connect->setUri($_url . '/' . $_chache_item_name);
  8. //укажем, что мы используем JSON
  9. $ehcache_connect->setHeaders('Content-type', 'application/json');
  10. //установим метод
  11. $ehcache_connect->setMethod(Zend_Http_Client::PUT);
  12. //добавляем данные с кодированием в JSON
  13. $ehcache_connect->setRawData(Zend_Json::encode($_SERVER));
  14. //Все! Выполняем запрос
  15. $response = $ehcache_connect->request();
  16. // мы получили ответ в виде объекта класса Zend_Http_Response
  17. if ($response->isSuccessful())
  18. {
  19.   //все ок, запрос успешен, сервер вернул правильный HTTP-ответ с кодом 200
  20.   echo 'Request OK!';
  21. }
  22. else
  23.    {
  24.      echo $response->getMessage();
  25.    }
* This source code was highlighted with Source Code Highlighter.


Теперь получим назад свой массив, для этого можно даже не менять URL, а только сменить тип запроса, остальное такое же, как в предыдущем коде:

  1. // URL нашего объекта
  2. $ehcache_connect->setUri($_url . '/' . $_chache_item_name);
  3.  
  4. // Метод
  5. $ehcache_connect->setMethod(Zend_Http_Client::GET);
  6.  
  7. //выполняем запрос
  8. $_result = $ehcache_connect->request();
  9.  
  10. //если все ОК
  11. if ($_result->isSuccessful())
  12. {
  13.    // получаем тело запроса и декодируем его из JSON обратно в Array
  14.    $_json_res = Zend_Json::decode($_result->getBody(), Zend_Json::TYPE_ARRAY);
  15.  
  16.    // Выведем на экран
  17.    Zend_Debug::dump($_json_res);
  18. }
  19. else
  20.    {
  21.      echo $response->getMessage();
  22.    }
* 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). Поле для исследований широкое и интересное, верно?
Теги:
Хабы:
Всего голосов 20: ↑19 и ↓1+18
Комментарии9

Публикации

Истории

Ближайшие события

15 – 16 ноября
IT-конференция Merge Skolkovo
Москва
22 – 24 ноября
Хакатон «AgroCode Hack Genetics'24»
Онлайн
28 ноября
Конференция «TechRec: ITHR CAMPUS»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань