Привет всем! 🤗 Мы надеемся, что вы уже ознакомились с нашим Введением в Buddy и хорошо понимаете, как он работает. Мы хотим поделиться нашим путем и опытом разработки и теми вызовами, с которыми мы столкнулись.
В Manticore Software мы столкнулись с двумя основными проблемами:
- Расширение Manticore Search некритичными для производительности функциями без изменения C++.
- Упрощение процесса внесения вкладов в улучшения и реализации новых функций.
Мы были полны решимости найти решение. Так что давайте погрузимся в наш путь разработки Buddy и проблемы, с которыми мы столкнулись. Готовы? Поехали!
Начало путешествия
Наше путешествие началось с тщательного изучения проблемы. Хотя Manticore Search является исключительным продуктом поисковой базы данных, мы столкнулись с трудностями в выпуске новых функций с желаемой скоростью, потому что кодовая база написана на C++. Написание кода на C++ требует низкоуровневого взаимодействия с структурами данных, байтами, глубоких знаний о том, как работает машина, компиляции, отладки и нахождения наилучшего подхода к написанию кода, что занимает много времени, но приводит к более быстрому выполнению программы. Это значительно задерживало процесс разработки. Хотя C++ является отличным вариантом для производительности, на его разработку уходит время. Мы хотели двигаться быстро, поставлять больше функций и делать это последовательно.
Мы пришли к идее создания компаньона для нашего основного процесса searchd, который мог бы обрабатывать неудачные запросы от Manticore Search и возвращать результаты оригинальному клиенту. Мы не раздумывали долго, решая, какой язык использовать, и остановились на PHP по нескольким причинам:
- Большинство членов основной команды были с ним знакомы, поэтому на его настройку уйдет меньше времени.
- PHP быстрый, особенно с самой новой версией (8+), даже когда мы не требовали производительной работы от Buddy. Он быстрее, чем Python или JavaScript, поэтому это подходило под наши требования.
- PHP не только быстрый, но и простой, что снижает уровень экспертизы, необходимый для вклада в будущую экосистему.
Вот почему мы выбрали PHP и начали реализовывать базовый код, чтобы понять, что нам потребуется позже.
Мы по-прежнему используем C++ для разработки функций, критичных к скорости. C++ идеально подходит для задач, которые требуют скорости. Для задач, которые не требуют высокой скорости, Buddy является оптимальным выбором.
Так был создан Buddy. Чтобы сделать это возможным на стороне C++ Manticore Search, мы реализовали отдельный цикл и связь между демоном searchd и процессом Buddy на PHP с использованием расширения CURL. Мы разработали наш внутренний протокол, который представляет собой простой JSON, для маршрутизации запросов к Buddy; он обрабатывает эти запросы и отправляет нам соответствующий ответ для передачи обратно оригинальному клиенту.
Реализация протокола связи
При начале нового проекта важно оставаться гибким и не планировать слишком подробно. В нашем случае мы начали с базовой реализации связи с использованием расширения sockets в PHP. Хотя это работало хорошо, это не было масштабируемым. Наша цель состояла в том, чтобы соединить Manticore Search с Buddy, и эта простая реализация позволила нам подтвердить эту идею.
Вместо того чтобы изобретать велосипед, мы изучили варианты для повышения масштабируемости и неблокирующей работы системы. Мы изначально рассматривали OpenSwoole, но из-за лицензионной проблемы мы не могли его использовать. Затем мы нашли ReactPHP, у которого была подходящая лицензия. Поэтому мы решили остановиться на ReactPHP.
ReactPHP — это простой PHP-фреймворк, который позволяет нам запускать TCP-сервер в асинхронном режиме.
Этот выбор хорошо работал для нас, поскольку он позволил нам обрабатывать несколько запросов одновременно и легко масштабировать систему.
Затем мы переписали наш простой Buddy MVP и создали основную структуру, которая упростит добавление обработки новых SQL-команд в будущем. Процесс таков:
- Manticore Search получает SQL-запрос от пользователя и пытается его обработать.
- Если Manticore Search может обработать запрос, он возвращает ответ клиенту без привлечения Buddy.
- Если Manticore Search не может обработать запрос, он отправляет специальную структуру со всей информацией о входном запросе и любых ошибках в Buddy.
- Buddy парсит структуру и проверяет, есть ли обработчик для него. Если его нет, он возвращает ту же ошибку, которую бы отправил Manticore Search клиенту.
- Если все хорошо и у нас есть реализация для обработки запроса, мы разбиваем процесс на два шага: подготовка запроса с необходимыми данными и его обработка с логикой обработчика. Запрос представляет собой простую структуру, которая представляет класс с предопределёнными переменными и входными параметрами, разобранными из входного SQL-запроса. Если что-то пойдёт не так, он может завершиться неудачно и вернуть ошибку клиенту.
- Обработчик затем получает запрос, выполняет необходимые действия и возвращает окончательный результат HTTP-запросу.
Эта система проста в обслуживании, проста и может быть легко расширена новой функциональностью. Однако есть одна проблема с этим подходом. Если у нас есть что-то тяжелое или необходимо подождать в Buddy, это может замедлить конкурирующие запросы. Это связано с тем, что, хотя асинхронный код не является параллельным, PHP по-прежнему блокирует код, и ReactPHP использует fibers для эмуляции асинхронного подхода. Мы обсудим эту проблему более подробно в следующем разделе.
Проблема асинхронности в PHP и масштабирование для параллелизма
Чтобы справиться с большими нагрузками, нашей команде в Manticore Search нужно было решение, которое могло бы обрабатывать больше запросов, чем ReactPHP. Хотя ReactPHP работал для реализации асинхронного HTTP-сервера и обработки некоторой конкуренции, его масштабируемости было недостаточно. После быстрого поиска мы решили использовать расширение parallel вместо pthreads из-за его поддерживаемости и надежности.
Но что такое параллель? Это о параллелизации, создании независимых Виртуальных Машин, которые представляют собой потоки, работающие параллельно. Эти потоки могут общаться через каналы, которые предоставляет Parallel, позволяя нам отправлять данные из основного процесса ReactPHP в параллельный и отсоединенный поток в другой Виртуальной Машине без изобретения колеса.
Этот подход стал нашим серебряным пули для обработки высоких уровней параллелизма и поддержания низкого времени отклика. Чтобы это реализовать, мы внедрили компонент Task для выполнения задач в параллельном потоке на уровне Обработчика. Таким образом, основной процесс не блокировался, позволяя нам легко обрабатывать множество параллельных запросов.
Изначально мы создавали Виртуальную Машину для каждого запроса и уничтожали её в конце выполнения. Однако этот подход вызывал деградацию производительности при большом количестве полученных запросов. Чтобы справиться с большим количеством запросов, мы подготовили Виртуальные Машины при первом запуске и использовали их по кругу. Таким образом, мы могли ограничить максимальное количество создаваемых потоков и не превышать общее количество ядер, что также влияло на производительность.
Хотя мы легко решили проблему производительности и параллелизма, мы столкнулись с другой задачей: развертыванием. Не все операционные системы поддерживают последнюю версию PHP 8, и некоторые редкие расширения не включены в стандартную установку. Но мы нашли решение, и вы тоже можете.
Поздороваться с manticore-executor
Мы провели исследование, чтобы найти простой способ доставки нового инструмента клиентам и обнаружили отличный подход - компилирование как <strong>PHP</strong>, так и Buddy в один статический бинарный файл . Это включало в себя внедрение PHP в его исходные файлы и создание бинарного файла, который мог бы работать. Однако мы столкнулись с препятствием, потому что хотели смешать различные лицензии - PHP 3.01 и GPL 2.0 - что было невозможно. В результате мы выбрали предкомпилированный PHP, связывая его статически, и назвали его manticore-executor.
К сожалению, процесс не был простым. Мы пытались собрать его на Ubuntu, но возникла проблема - нам нужен был OpenSSL для установления безопасных соединений с внешними доменами. Однако при использовании динамического GCC мы не могли связать OpenSSL статически.
Почему мы использовали GCC? Это было необходимо для компиляции PHP и его расширений. Проблема заключалась в том, что нам нужен был статически собранный GCC для статической линковки, что не просто и требует много работы. В результате мы искали альтернативы.
К счастью, мы обнаружили MUSL и Alpine, которые позволили нам собрать полностью статическую версию PHP со всеми необходимыми расширениями и библиотеками без особых трудностей! Кроме того, это работает на любой дистрибутиве Linux.
Alpine Linux является отличным выбором для компиляции C-программ благодаря своему небольшому размеру и легковесности, что делает его подходящим для систем с ограниченными ресурсами, такими как встроенные устройства или контейнеры. Кроме того, Alpine Linux безопасен. Он использует защищенное ядро и немного пакетов, ограничивая поверхность атаки и делая его менее уязвимым для угроз безопасности. Это особенно важно для C-программ, которые могут быть подвержены уязвимостям безопасности.
Кроме того, Alpine Linux использует MUSL libc в качестве своей стандартной C-библиотеки, которая является легковесной и эффективной C-библиотекой, что приводит к более быстрому и эффективному коду, чем другие C-библиотеки.
В результате мы использовали его и настроили действия для использования образа Alpine и сборки его в Docker. Прелесть этого подхода в том, что он также упростил нам сборку для ARM, потому что Docker имеет команду buildx
, позволяя нам использовать QEMU в готовой схеме сборки и достигать того же потока для сборки как для архитектуры AMD, так и ARM на одной машине! Ознакомьтесь с нашим потоком сборки
здесь
.
Github Actions автоматизирует сборку и развертывание пакетов для всех поддерживаемых операционных систем. Для пользователей установка проста: просто запустите apt-get install manticore-executor
или yum install manticore-executor
, и у вас будет версия PHP, готовая к использованию, со всеми необходимыми пакетами, предустановленными для запуска любого проекта PHP, поставляемого Manticore. Легко!
Как мы поставляем наш исходный код
В Manticore Search мы столкнулись с задачей предоставления нашего PHP-приложения, состоящего из нескольких файлов исходного кода, пользователю. У нас было много файлов, разбросанных по отдельным папкам, и зависимости, которые нужно было устанавливать с помощью Composer, что усложняло процесс установки.
Как вы помните, мы разработали пользовательскую версию PHP, называемую manticore-executor , которую можно было легко установить из репозиториев. Однако это все еще не решало проблему предоставления всего PHP приложения пользователю.
Мы нашли решение в PHAR, которое позволяло нам создать единый файл, который можно было бы добавить как пакет в репозиторий. Это упростило процесс установки. Однако обеспечить корректное включение всех зависимостей в окончательный архив PHAR было непросто. Чтобы решить эту задачу, мы создали и отделили внешнюю систему сборки, которую мы также используем для нашего инструмента manticore-backup .
Чтобы сделать пакет исполняемым, мы решили использовать скрипт Bash и Shebang с нашим пакетом Manticore-Executor. Этот скрипт проверяет дату изменения PHAR в временной папке системы и извлекает данные PHAR там, позволяя несколько запусков оставаться производительными и актуальными по новым установленным версиям. Для получения дополнительной информации о том, как мы реализовали это, вы можете обратиться к нашему проекту phar_builder на GitHub.
Извлеченные уроки
- Начинайте с простого, базового решения, не используя паттерны проектирования, когда не уверены в будущем успехе проекта. Приоритизируйте валидацию сначала, а затем рефакторите и вносите изменения в обновления.
- Параллелизм в PHP может быть сложным, но использование потоков и асинхронных фреймворков может помочь достичь высокой пропускной способности. Для оптимальной производительности рекомендуется использовать оба. Предварительное выделение времени выполнения для потоков может помочь достичь желаемой производительности.
- Упростите процесс доставки для пользователей. Сократите количество необходимых инструкций. В нашем случае один PHAR-архив и один бинарный файл со всеми включенными расширениями для нашего кастомного PHP решали проблему.
- Используйте самые последние версии PHP или других инструментов, чтобы быть в курсе последних разработок и обеспечивать безопасность ваших данных. Устаревшее программное обеспечение может быть уязвимо для хакерских атак и нарушений безопасности. Обновление предлагает улучшенную производительность, новые функции и эффективный процесс кодирования.
- Ищите пакеты, которые могут решить вашу проблему, и изучайте их зависимости. Выбирайте пакеты с минимальными зависимостями, чтобы избежать адской зависимости. Используйте небольшие пакеты как строительные блоки, а не создавайте индивидуальные решения.
Итог
На протяжении разработки Buddy мы столкнулись с множеством проблем, которые мы преодолели с восторгом. Инструмент полностью написан на PHP и поставляется как пакет ОС, что делает его невероятно простым для установки пользователями и для нас в поддержке и автоматизации сборок благодаря GitHub Actions. Хотя есть еще возможности для улучшения, чтобы сделать инструмент еще проще, наша история демонстрирует, как можно создать легко поддерживаемый и легко устанавливаемый инструмент, используя всю мощь PHP.
Мы надеемся, что вам понравилось читать о нашем пути к выпуску Manticore 6.0.0 . Не забывайте следить за нашей следующей статьей, в которой мы исследуем новый плагинный дизайн в Buddy и его удобную для участия экосистему, benefiting все сообщество. Это действительно захватывающее время, и мы не можем дождаться, чтобы поделиться с вами еще больше.