В этой статье рассказывается, как использовать PHP для построения архитектуры микросервисов. Так как PHP идет в ногу со временем, он способен поддерживать микросервисные архитектуры для больших систем.
Проблема сервитизации
При использовании traditional framework (laravel, yii, symfony) для реализации микросервисов на Php эффекта очень мало.
Почему?
В режиме разработки fpm, поскольку резидентная память не может быть предоставлена, каждый запрос должен начинаться с нуля, начиная с загрузки процесса, чтобы потом выйти из него, добавляя много бесполезных накладных расходов.
Кроме того, соединение с базой данных не может быть использовано повторно и не защищается, потому что fpm является процессно-ориентированным, и количество процессов fpm также определяет и число параллельных процессов. Таковы проблемы, с которыми мы столкнулись при обычной разработке fpm.
Он недостаточно дружелюбен к микросервисным инструментам, таким как docker, и полагается на nginx для предоставления услуг.
Таким образом, вот причины, по которым Java сейчас более популярна как интернет-платформа по сравнению с PHP. Помимо PHP non-memory resident, существует множество других проблем, требующих решения.
Теперь давайте посмотрим, как Swoft имплементирует микросервис.
Что такое Swoft?
Swoft — это фреймворк корутин для микросервисов PHP, основанный на расширении Swoole. Как и Go, Swoft имеет встроенный веб-сервер и общий клиент для корутин, и является резидентным в памяти, независимым от традиционного PHP-FPM.
Здесь есть Go-подобные языковые операции, гибкие аннотации, аналогичные фреймворку Spring Cloud, мощный контейнер для внедрения глобальных зависимостей, комплексное управление сервисами, гибкое и мощное AOP (Aspect Oriented Programming), стандартная реализация спецификации PSR (PHP Standards Recommendations) и так далее.
Swoft Github
Что нам нужно для создания микросервиса?
Высокопроизводительный фреймворк для приложений
Регистрация и обнаружение услуг
Автоматическое переключение услуг
Ограничение услуг
Конфигурационный центр
Да, в Swoft все уже готово
Высокая производительность Swoft
Вы можете представить, какие преимущества дает нам резидентная память.
Фреймворк инициализируется только один раз; мы можем сосредоточиться на обработке запросов, потому что фреймворк инициализируется в памяти только один раз при запуске для резидентной памяти
Мультиплексирование соединений; если мы не используем пул соединений, тогда к чему приводит создание соединений для каждого запроса, этого не могут понять некоторые инженеры. Это приводит к чрезмерному использованию ресурсов бэкенда. Для некоторых основных сервисов, таких как Redis, базы данных, соединения оказываются дорогостоящим расходом ресурсов.
Итак, есть ли подходящее решение? Ответ — да, и многие используют фреймворк под названием Swoft. Swoft — это RPC (Remote Procedure Call)-фреймворк с функциональностью Service Governance. Swoft — это первый фулл-стек PHP-фреймворк, резидентный в памяти, корутинный, базирующийся на основной концепции Spring Boot согласно которой соглашение важнее чем конфигурация.
Swoft обеспечивает более элегантный способ использования RPC-сервисов, таких как Dubbo, имеет отличную производительность, схожую с Golang. Вот результат стресс-теста производительности Swoft на моем PC.
Скорость обработки данных в стресс-тесте ab очень впечатляет. С процессором i7 generation 8 и памятью 16GB на 100000 запросов уходит всего 5s. Такого быстродействия практически невозможно достичь в режиме разработки fpm.
Данного теста также достаточно, чтобы продемонстрировать высокую производительность и стабильность Swoft.
Регистрация и обнаружение сервисов
В процессе управления микросервисами часто требуется регистрация сервисов, инициированных на сторонних кластерах, таких как consul/etcd. В этой главе для осуществления регистрации и обнаружения сервисов используется компонент swoft-consul в составе фреймворка Swoft.
Логика реализации
<?php declare(strict_types=1);
namespace App\Common;
use ReflectionException;
use Swoft\Bean\Annotation\Mapping\Bean;
use Swoft\Bean\Annotation\Mapping\Inject;
use Swoft\Bean\Exception\ContainerException;
use Swoft\Consul\Agent;
use Swoft\Consul\Exception\ClientException;
use Swoft\Consul\Exception\ServerException;
use Swoft\Rpc\Client\Client;
use Swoft\Rpc\Client\Contract\ProviderInterface;
/**
* Class RpcProvider
*
* @since 2.0
*
* @Bean()
*/
class RpcProvider implements ProviderInterface
{
/**
* @Inject()
*
* @var Agent
*/
private $agent;
/**
* @param Client $client
*
* @return array
* @throws ReflectionException
* @throws ContainerException
* @throws ClientException
* @throws ServerException
* @example
* [
* 'host:port',
* 'host:port',
* 'host:port',
* ]
*/
public function getList(Client $client): array
{
// Get health service from consul
$services = $this->agent->services();
$services = [
];
return $services;
}
}
Сервисный автоматический выключатель
В базовом режиме автоматический выключатель гарантирует, что поставщик не будет вызван, когда он находится в разомкнутом состоянии, однако нам также необходим дополнительный метод сброса автоматического выключателя после возобновления обслуживания поставщиком.
Одно из возможных решений заключается в том, что автоматический выключатель периодически определяет, возобновлено ли обслуживание. Как только оно возобновляется, он устанавливается в закрытое положение. При повторной попытке состояние становится полуоткрытым.
Использование предохранителя является простым и мощным. Он может быть аннотирован с помощью @Breaker
. Предохранитель Swoft может быть использован в любом сценарии, например, при вызове службы. Он может быть понижен или не вызываться при запросе сторонней службы.
<?php declare(strict_types=1);
namespace App\Model\Logic;
use Exception;
use Swoft\Bean\Annotation\Mapping\Bean;
use Swoft\Breaker\Annotation\Mapping\Breaker;
/**
* Class BreakerLogic
*
* @since 2.0
*
* @Bean()
*/
class BreakerLogic
{
/**
* @Breaker(fallback="loopFallback")
*
* @return string
* @throws Exception
*/
public function loop(): string
{
// Do something
throw new Exception('Breaker exception');
}
/**
* @return string
* @throws Exception
*/
public function loopFallback(): string
{
// Do something
}
}
Ограничение потока, автоматический выключатель, понижение уровня обслуживания Это можно подчеркивать неоднократно, потому что они действительно важны. Когда услуга не работает, она должна быть прекращена. Ограничение потока — это инструмент самозащиты. Если нет механизма самозащиты и соединения принимаются независимо от их количества, то при очень большом трафике фронтенд обязательно зависнет, а бэкенд не сможет обработать все соединения.
Ограничение потока — это ограничение числа одновременных запросов и количества запросов при доступе к дефицитным ресурсам, таким как товары на флэш-распродаже, чтобы эффективно срезать пиковые нагрузки и сгладить кривую потока.
Цель ограничения потока — ограничить скорость одновременного доступа и одновременных запросов, или ограничить скорость запроса в пределах временного окна для защиты системы. Как только предел скорости достигнут или превышен, запросы могут быть отклонены или поставлены в очередь.
Нижний слой ограничения потока Swoft использует алгоритм token bucket
, а основной слой опирается на Redis для реализации распределенного ограничения потока.
Ограничение потока Swoft не только ограничивает контроллеры, оно также ограничивает методы в любом бине и контролирует скорость доступа к методам. Ниже приводится пример с подробным объяснением.
<?php declare(strict_types=1);
namespace App\Model\Logic;
use Swoft\Bean\Annotation\Mapping\Bean;
use Swoft\Limiter\Annotation\Mapping\RateLimiter;
/**
* Class LimiterLogic
*
* @since 2.0
*
* @Bean()
*/
class LimiterLogic
{
/**
* @RequestMapping()
* @RateLimiter(rate=20, fallback="limiterFallback")
*
* @param Request $request
*
* @return array
*/
public function requestLimiter2(Request $request): array
{
$uri = $request->getUriPath();
return ['requestLimiter2', $uri];
}
/**
* @param Request $request
*
* @return array
*/
public function limiterFallback(Request $request): array
{
$uri = $request->getUriPath();
return ['limiterFallback', $uri];
}
}
Здесь поддерживается выражение symfony/expression-language. Если скорость ограничена, будет вызван метод limiterFallback
, определенный в fallback
.
Прежде чем перейти к обсуждению центра конфигурации, давайте поговорим о конфигурационном файле. Он нам хорошо знаком. С его помощью мы можем динамически изменять программу. Вот чья-то цитата об этом:
Динамическая регулировка полета системы во время выполнения
Для автономной версии мы называем его конфигурацией (файлом); для распределенной кластерной системы мы называем его центром конфигурации (системой);
В этой главе в качестве примера используется Apollo для извлечения сервисов конфигурации и безопасного перезапуска из удаленного центра конфигурации. Если вы не знакомы с Apollo, вы можете сначала посмотреть на компонент Apollo расширения Swoft и прочитать официальную документацию Apollo.
В этой главе в качестве примера используется Apollo в Swoft. При изменении конфигурации Apollo перезапустите службу (http-server
/ rpc-server
/ ws-server
). Ниже приведен пример агента:
<?php declare(strict_types=1);
namespace App\Model\Logic;
use Swoft\Apollo\Config;
use Swoft\Apollo\Exception\ApolloException;
use Swoft\Bean\Annotation\Mapping\Bean;
use Swoft\Bean\Annotation\Mapping\Inject;
/**
* Class ApolloLogic
*
* @since 2.0
*
* @Bean()
*/
class ApolloLogic
{
/**
* @Inject()
*
* @var Config
*/
private $config;
/**
* @throws ApolloException
*/
public function pull(): void
{
$data = $this->config->pull('application');
// Print data
var_dump($data);
}
}
Выше приведен обычный способ настройки Apollo, в дополнение к этому методу Swoft-Apollo предоставляет больше способов использования.
Заключение
В данный момент наш простой фреймворк микросервисов был построен. Если использовать традиционный PHP-фреймворк, этого достичь очень сложно. Но с помощью Swoft все гораздо проще.
Материал подготовлен в рамках курса «PHP Developer. Professional».
Всех желающих приглашаем на двухдневный онлайн-интенсив «Пишем микросервисный бэкенд на PHP». На занятии:
- Познакомимся с понятием контейнеризации на примере Docker и принципами в работе с контейнерами;
- Сложим понимание термина микросервис и построим инфраструктуру для приложения, состоящего из двух микросервисов;
- Узнаем как работают Nginx и PHP-FPM в связке и внедрим их в созданные микросервисы.
>> РЕГИСТРАЦИЯ
Комментарии (11)
hack3p
27.09.2021 23:12Если вы уперлись в ресурсы при использовании FPM, и при этом у вас требования как у высоконагруженного приложения с возможностью асинхронной обработки огромного кол-ва запросов и асинхронной передачи их в дальнейшие сервисы.. То почему просто не сменить инструмент на "нормальный" (в рамках необходимых требований), например тот же GO?
Считаю, что переучивать разработчиков которые годами росли в контексте "PHP рождён, чтобы умереть" с синхронным исполнением кода, на асинхронщину на том же PHP - это дорога в никуда. На рынке очень мало востребованных позиций с асинхронным PHP, и на рынке будет очень сложно найти квалифированного PHP разработчика имеющего опыта %костыль% и умеющего в асинхронщину (удачи).
А вот если говорим про тот же Go (или другой подобный инструмент), то тут сразу понятно, что такой разработчик умеет в асинхронщину, во всякие mutex, потоки, и прочее.. И в таком случае, эффективность решения будет в разы выше, чем костыли (swoole, gorunner и прочее) на PHP. Потому-что на "нормальном" инструменте (опять-таки Go), сможете намного лучше утилизировать доступные ресурсы.
Поэтому, если так сложилось, что в компании только PHP разработчики.., считаю перспективным решением найти пару разработчиков на "нормальном" инструменте в компанию, и вокруг них уже организовать процессы обучения php разработчиков в "нормальный" инструмент.
Лучше немного побуксовать на старте (смена стека), чем потом докупать пачками сервера под PHP, и каждого нового PHP разработчика обучать мертворождённым костылям.tzlom
28.09.2021 00:20+1Нормальным в рамках требований может и PHP-FPM быть, просто не надо лепить микросервисную архитектуру как попало. Если у вас сервер который по сути прокси к 20 другим серверам то вылечив проблему скорости запросов вы просто уткнётесь в проблему доступности. А если вы таким не занимаетесь то у вас и PHP тормозить не будет. Конечно есть всякие специальные случаи где нужны специализированные решения, но таких случаев у большинства нет.
evgeniy_p
28.09.2021 12:18Звучит логичино и аргументы понятные, но переписать все на %подставь_свтой_язык% для бизнеса означает потерять кучу времени и не факт, что это окупится, а выгоды мало для бизнеса.
И сейчас все гиганты, которые начинали с PHP (ВКонтатке, facebook, badoo и тд), полностью не переезжают на другие языки, хотя денег найти на рынке Go-шника и обучить у них точно хватает. Они перевозят лишь частями свои сервисы, разгружая узкие места. Именно поэтому все чаще встречаются вакансии PHP+Go, чтобы умел и админку на PHP сделать и API под высокие нагрузки на GO накидать.
maxkain
20.10.2021 17:17И в таком случае, эффективность решения будет в разы выше, чем костыли (swoole, gorunner и прочее) на PHP.
Почему же "костыли"? Swoole позволяет выполняться приложению (на Symfony, например) в режиме сервиса. То есть, скрипт постоянно висит в памяти и не выполняется заново каждый запрос, как при FPM. От этого скорость обработки запросов значительно увеличивается.
А вот если говорим про тот же Go (или другой подобный инструмент), то тут сразу понятно, что такой разработчик умеет в асинхронщину, во всякие mutex, потоки, и прочее.
Сам по себе асинхронный код вряд ли даст значительное ускорение. Зато появится много проблем с поддержкой этого всего.
oxidmod
29.09.2021 10:59ИМХО, основная проблема таких фреймворков в том, что в них не просто интегрировать микросервис написанный с использованием других технологий. Все замечательно пока все сервисы написаны с использованием этого фреймворка но шаг в сторону и приходится самому писать кучу лишнего кода, чтоб работали все эти сервис дискавери. Чтоб объекты запросов\ответов имели один формат. Чтоб работали системы сбора логов, трассировки и прочего. А это как раз убивает одно из преимуществ микросервисов
maxkain
20.10.2021 17:01Symfony может работать через Swoole: https://www.swoole.co.uk/article/symfony-swoole
При этом он быстрее Spring работает: https://www.techempower.com/benchmarks/#section=data-r20&hw=ph&test=fortune
Так что, не понятно, в чем, собственно, проблема.
dimuska139
А какие-то подтверждения этому есть? Впервые слышу об этом, если честно. Да и вроде они в разных нишах немного, и Java обычно в энтерпрайзе используется - ну то есть сравнивать их в плане популярности бессмысленно. Того же Wordpress, кстати, на котором крутятся большинство всяких блогоподобных сайтов, для Java нет. А ведь блог - это самая многочисленная разновидность сайта.
Ну внешний пул можно использовать - PgBouncer, например. А от чего соединение не защищается?
Какие проблемы запихнуть php-fpm в Docker и что знает "полагается на nginx для предоставления услуг"?
Так ли уж каждый и так ли уж с нуля? Как вы думаете, что такое pm.min_spare_servers в конфиге php-fpm?
KGeist
Да, неизвестно, как автор меряет популярность.
>According to W3Techs' data, PHP is used by 78.9% of all websites with a known server-side programming language. So almost 8 out of every 10 websites that you visit on the Internet are using PHP in some way.
Hett
Так это количество сайтов, а не размер кодовой базы. Очень популярен вордпрес/джумла и т.п., которые сильно перекашивают статистику.
Нас, как серьезных разработчиков интересует именно кодовая база в данном контексте.