PHP + Java, или In-memory кластер теперь и для PHP разработчиков
Intro PHP + Java. Картинка взята отсюдаВ этом комментарии к статье под названием «Пишите код каждый день» я сказал, что скоро покажу свой проект, на который я выделял ежедневно 1 час (кроме выходных). Так как в последнее время моя работа связана с написанием распределенных Java приложений, которые используют in-memory data grid (IMDG) в качестве хранилища данных, то мой проект связан именно с этим.
Подробнее про IMDG можно почитать в моих предыдущих статьях (1, 2). Но если кратко, то это кластерное распределенное хранилище объектов по ключам, которое держит все данные в памяти, за счет чего достигается высокая скорость доступа к данным. Позволяет не только хранить, но и обрабатывать данные, не извлекая их из кластера.И если интерфейс для обработки данных у каждого конкретного IMDG свой, то интерфейс доступа к данным обычно идентичен хеш-таблице.
О чем эта статья Большинство IMDG написано на Java и поддерживают API для Java, C++, C#, при этом API для веб языков программирования (Python, Ruby, PHP) не поддерживается, а протокол для написания клиентов сильно ограничен. Именно этот факт я и считаю основным тормозом для проникновения IMDG в массы — отсутствие поддержки самых массовых языков.Так как производители IMDG пока не предоставляют поддержку веб языков, то веб программисты не имеют возможностей по такому же легкому масштабированию приложений, какие есть у серверных Java разработчиков. Поэтому я решил сделать нечто подобное самостоятельно и выложить в open source, взяв в качестве движка open source IMDG JBoss Infinispan (компания JBoss, принадлежащая Red Hat, довольно хорошо известна в кругу java разработчиков). Мой проект называется Sproot Grid, пока доступен только для PHP, но если у сообщества будет интерес, то сделаю и интеграцию с Ruby и Python.
В этой статье я еще раз расскажу про in-memory data grid и про то, как конфигурировать, запускать и использовать Sproot Grid.
Зачем нужен IMDG? Самым узким местом многих высоконагруженных проектов является хранилище данных, в частности реляционная БД. Для борьбы с недостатками традиционных БД в основном используется 2 подхода:1) Кэшированиеплюсы:
высокая скорость доступа к данным минусы: очень редко встречаются настоящие кластерные решения, в основном пользователю самому приходится заниматься распределением данных по серверам, а при доступе к данным определять тот сервер, на котором лежат эти данные. Равномерности заполненности всех узлов кластера в такой системе достичь сложно требует компромисса между актуальностью данных и скоростью доступа, т.к. данные в кэше могут устареть, а удалять старые данные из кэша с последующим кэшированием новых — это дополнительные задержки и нагрузка на систему Обычно данные кэшируются не в виде доменных объектов, которые используются в приложении, а в виде BLOB либо строк, т.е. при использовании данных, полученных из кэша, необходимо сначала сконструировать нужные объекты 2) NoSQL решенияплюсы: хорошая горизонтальная масштабируемость минусы: не такая высокая скорость получения результатов в случае использования диска практически невозможно обеспечить работу внутрикорпоративного софта, который ориентирован на работу с конкретной реляционной БД IMDG объединяет достоинства обоих подходов и при этом имеет ряд преимуществ перед упомянутыми выше решениями: хорошая горизонтальная масштабируемость высокая скорость доступа настоящая кластеризация (класть данные можно на любой узел, запрашивать данные можно также на любом узле кластера), автоматическая балансировка данных между узлами кластер знает о всех полях объекта, следовательно можно искать объекты не только по ключам, но и значениям полей есть возможность создавать индексы по полям либо по их комбинации при использовании механизмов read-through и write-behind (или write-through) данные будут синхронизироваться с БД, что позволит другим приложениям (либо другим модулям приложения) продолжать пользоваться традиционной БД (MySQL или Mongo — неважно) При использовании схемы работы из предыдущего пункта исчезает проблема актуализации данных в кэше, т.к. они всегда там будут такие же, как и в БД Рассмотрим поближе эти 2 интересных механизма: read-through и write-behind (write-through)read-through Read-through — это механизм, который позволяет подтягивать данные из БД во время запроса.Например вы хотите получить из кэша объект по ключу 'key', и при этом оказывается, что объекта с таким ключом в кластере нет, тогда автоматически этот объект будет прочитан из БД (или любого другого persistence storage), затем положен в кэш, после чего будет возвращен как ответ на запрос.В случае отсутствия такого объекта в БД пользователю будет возвращен null.Естественно, что необходимый sql-запрос, а также маппинг результатов запроса на объект лежит на плечах пользователяwrite-behind (write-through) Для оптимизации скорости записи вы можете писать не в БД, а напрямую в кэш. Звучит на первый взгляд странно, но на практике это хорошо разгружает БД и повышает скорость работы приложения.Выглядит это примерно так: Пользователь делает вызов cache.put (key, value), объект 'value' сохраняется в кеше по ключу 'key' В кластере срабатывает обработчик этого события, происходит составление sql-запроса для записи данных в БД и его выполнение Управление возвращается пользователю Такая схема взаимодействия называется write-through. Она позволяет синхронизировать обновления с БД одновременно с обновлениями в кластере. Как можно заметить, такой подход не ускоряет процесс записи данных, но обеспечивает согласованность данных между кэшом и БД. Также при таком виде записи данные попадают в кэш, а значит доступ к ним на чтение всё равно будет выше, чем запрос к БД.Если же одновременнаяя запись в БД не является критичным условием, тогда можно использовать более популярный механизм write-behind, он позволяет организовать отложенную запись в БД (любой другой сторадж). Примерно так:
Пользователь делает вызов cache.put (key, value), объект 'value' сохраняется в кэше по ключу 'key'
Управление возвращается пользователю
Через некоторое время (конфигурируется пользователем) срабатывает обработчик события записи в кэш
Обработчик собирает всю пачку объектов, которые были изменены со времени предыдущего срабатывания обработчика
Пачка отправляется в БД на запись
При использовании write-behind операция записи существенно ускоряется, потому что пользователь не ждет, пока апдейт дойдет до БД, а просто кладет данные в кэш, а все апдейты одного и того же объекта будут слиты в один результирующий апдейт, при этом запись в БД происходит пачками, что тоже положительно сказывается на загрузке сервера БД, Таким образом можно сконфигурировать свой IMDG так, чтоб каждые 3 секунды (либо 2 мин, либо 50 мс) все обновления данных асинхронно отправлялись в базу.Что из этого есть в Sproot Grid?
В первой версии я решил не реализовывать сразу всё, о чем рассказал выше, т.к. это отняло бы много времени, а мне хотелось бы побыстрее получить фидбэк от пользователей.Итак, что доступно в Sproot Grid 1.0.0: Горизонтальная масштабируемость и честная кластеризация с балансировкой количества данных между узлами кластера
Возможность хранения как встроенных PHP типов, так и доменных объектов
Возможность построения индекса по полю и поиска по этому индексу
Getting Started
Сначала вам надо скачать дистрибутив отсюда и распаковать его.Установка необходимого ПО Так как JBoss Infinispan — это Java приложение, то необходимо было выбрать способ взаимодействия между Java и PHP. В качестве такого связующего звена был выбран Apache Thrift (протокол был разработан для сериализации и транспорта между узлами в Cassandra), поэтому для того, чтоб Sproot Grid мог работать на вашей системе необходимо установить следующее: Java
Thrift — установка в production не требуется, установка нужна только на девелоперской машине (подробности в пункте Генерация кода). При деплое в production вам потребуется только скопировать .php файлы библиотеки Thrift и java библиотеку в формате .jar
PHP (если еще не установлен)
Инструкции по установке расположены на wiki проектаКонфигурация
Файл конфигурации должен находиться в $deploymentFolder/sproot-grid/config/definition.xml, где deploymentFolder — это путь к директории, в которой вы распаковали дистрибутивПример конфигурации:
Генерация кода для интеграции с вашим приложением Для эффективной работы кластеру необходимо сгенерировать код, специфичный для вашего приложения (вашей доменной модели) и скомпилировать его Java часть, так как это работает быстрее, чем доступ к объектам через reflection. Чтобы сгенерировать и скомпилировать весь необходимый код, надо: 1) cd $deploymentFolder/sproot-grid/scripts 2) build.sh (or build.cmd) , где $deploymentFolder — это тот каталог, в который вы распаковали дистрибутивГенерацию кода необходимо производить только в случае изменения описания доменной модели, т.е. если ваша модель стабильна, то эту операцию вам придется произвести лишь один раз, после этого сгенеренные php исходники можно хранить в репозитории кода, а java часть будет скомпилирована в библиотеку. Т.е. не надо ничего генерить по 10 раз перед тем, как задеплоить ваше приложение, это делается только 1 раз на этапе разработки.После окончания выполнения генерации кода, скопируйте папку с .php файлами из $deploymentFolder/sproot-grid/php/org в корень вашего приложенияЗапуск 1) cd $deploymentFolder/sproot-grid/scripts 2) run.sh (run.cmd) nodeId memorySize , где nodeId — значение атрибута id секции в конфигурационном файле, memorySize — количество памяти (в Мб или Гб), которые вы хотите выделить узлуНапример:
run.sh 1 256m или run.cmd 2 2g Использование внутри приложения На шаге генерации кода вы получили всё необходимое для интеграции с вашим приложением. Остальось только скопировать этот код в свое приложение, для этого скопируйте всё из папки $deploymentFolder/sproot-grid/php в корень своего приложенияВсё! Теперь можете использовать кластер из своего приложения.Пример кода:
use org\sproot_grid\SprootClient; use some\package\User;
$client = new SprootClient ('localhost', 12345); // в качестве параметров в конструктор передаются хост и порт узла кластера типа 'service' echo $client → cacheSize ('user-cache');
$user = new User (); $user→setName ('SomeUser'); $user→setId (1234);
$client→put ('user-cache', '1234', $user);
echo $client → cacheSize ('user-cache'); ?> Описание API можете найти здесь, но если вкратце, то API сейчас такой: get ($cacheName, $key) getAll ($cacheName, array $keys) cacheSize ($cacheName) cacheKeySet ($cacheName) containsKey ($cacheName, $key) search ($cacheName, $fieldName, $searchWord) remove ($cacheName, $key) removeAll ($cacheName, array $keys) put ($cacheName, $key, $domainObject) putAll ($cacheName, array $domainObjects) clearCache ($cacheName) Заключение Sproot Grid опубликован под лицензией MIT.ИсходникиВикиДистрибутив