Привет! 🤗 Мы надеемся, что вы уже ознакомились с нашим Введением в 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-сервер в асинхронном режиме.
Этот выбор хорошо сработал для нас, так как он позволил нам обрабатывать несколько запросов одновременно и легко масштабировать систему.
Затем мы переписали наш простой MVP Buddy и создали основную структуру, которая упростила бы добавление обработки новых 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 из-за его поддерживаемости и надежности.
Но что такое параллель? Это о параллелизации, создании независимых Runtimes, которые представляют собой потоки, работающие параллельно. Эти потоки могут общаться через каналы, которые предоставляет Parallel, позволяя нам отправлять данные из основного процесса ReactPHP в параллельный и отделенный поток в другом Runtime без изобретения колеса.
Этот подход стал нашей серебряной пулей для обработки высоких уровней параллелизма и поддержания низкого времени отклика. Чтобы это осуществить, мы реализовали компонент Task для выполнения задач в параллельном потоке на уровне Handler. Таким образом, основной процесс не блокировался, что позволяло нам легко обрабатывать множество параллельных запросов.
Изначально мы создавали Runtime на каждый запрос и уничтожали его в конце выполнения. Однако этот подход вызывал ухудшение производительности при большом количестве получаемых запросов. Чтобы обрабатывать больше запросов, мы подготовили Runtimes при первом запуске и использовали их по кругу. Таким образом, мы могли ограничить максимальное количество создаваемых потоков и не превышать общее количество ядер, что также влияло на производительность.
Хотя мы легко решили проблему производительности и параллелизма, мы столкнулись с другой проблемой: развертыванием. Не все операционные системы поддерживают последнюю версию 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 и его легкую для участия экосистему, которая приносит пользу всему сообществу. Это действительно захватывающее время, и мы не можем дождаться, чтобы поделиться с вами еще больше.
