Шлюзы API и обнаружение сервисов
Динамически маршрутизируйте запросы, объединяйте данные и находите сервисы
«Шлюзы API и обнаружение сервисов» — бесплатный урок PHP Academy на CoddyKit. Это урок 3 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения PHP Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс PHP Academy содержит 4 уроков всего.
Шлюзы и обнаружение
Когда у Вас появляется с десяток сервисов, возникают две проблемы. Клиентам не нужно знать адрес каждого сервиса или вызывать пять сервисов, чтобы отобразить один экран, — это задача шлюза API. А сервисам, которые масштабируются и меняют IP-адреса, нужен способ находить друг друга — для этого служит обнаружение сервисов.
В этом уроке рассматриваются оба вопроса, а на границе шлюза используется PHP.
Что делает шлюз API
Шлюз API — это единая точка входа перед Вашими сервисами. Типичные задачи:
- Маршрутизация запросов к нужной серверной части.
- Сквозные задачи: аутентификация, ограничение частоты запросов, CORS, завершение TLS.
- Агрегация: объединение нескольких вызовов серверной части в один ответ для клиента.
- Преобразование протоколов: на входе внешний REST, на выходе внутренний gRPC.
Шлюз упрощает клиентов и централизует политики, которые иначе пришлось бы дублировать в каждом сервисе.
Маршрутизация на границе
В основе шлюз сопоставляет входящий путь с вышестоящим сервисом. Промышленные шлюзы (Kong, Traefik, Nginx, AWS API Gateway) настраиваются декларативно, но эту логику достаточно просто показать на PHP.
<?php
$routes = [
'#^/api/orders#' => 'http://orders-svc',
'#^/api/customers#' => 'http://customers-svc',
'#^/api/catalog#' => 'http://catalog-svc',
];
function resolveUpstream(string $path, array $routes): ?string {
foreach ($routes as $pattern => $upstream) {
if (preg_match($pattern, $path)) {
return $upstream . $path;
}
}
return null; // 404 at the gateway
}
echo resolveUpstream('/api/orders/42', $routes), "\n";Централизация аутентификации
Проверяйте вызывающую сторону один раз на шлюзе, а затем передавайте доверенную идентичность дальше, чтобы каждому сервису не приходилось повторно проверять исходный токен. Шлюз проверяет подпись и срок действия JWT и добавляет в заголовки внутреннего запроса такие значения, как X-User-Id (в доверенной сети).
<?php
function authenticate(string $authHeader): ?array {
if (!str_starts_with($authHeader, 'Bearer ')) return null;
$jwt = substr($authHeader, 7);
$claims = verifyJwt($jwt); // signature + exp check
if ($claims === null) return null;
// Forward minimal trusted identity to internal services
return ['X-User-Id' => $claims['sub'], 'X-Scopes' => implode(',', $claims['scopes'])];
}
function verifyJwt(string $j): ?array { return ['sub' => 'u-7', 'scopes' => ['orders:read']]; }
print_r(authenticate('Bearer abc.def.ghi'));Агрегация ответов
Для одного экрана мобильного приложения могут потребоваться данные о заказе, клиенте и товарах. Вместо того чтобы заставлять клиента выполнять три вызова, шлюз отправляет запросы одновременно, ждёт ответы и объединяет их. Чтобы сохранить высокую скорость, выполняйте вызовы вышестоящих сервисов параллельно (обещания Guzzle / curl_multi), а не последовательно.
<?php
require 'vendor/autoload.php';
use GuzzleHttp\Client;
use GuzzleHttp\Promise\Utils;
$http = new Client(['timeout' => 2.0]);
$promises = [
'order' => $http->getAsync('http://orders-svc/orders/42'),
'customer' => $http->getAsync('http://customers-svc/customers/7'),
'catalog' => $http->getAsync('http://catalog-svc/items?order=42'),
];
$results = Utils::settle($promises)->wait(); // run in parallel
// merge the fulfilled bodies into one response for the clientСерверная часть для разных клиентов
Один универсальный шлюз часто не может одинаково хорошо обслуживать веб-приложение, мобильное приложение и партнёров — каждому нужны свои варианты агрегации и формы данных. Паттерн серверной части для клиента (BFF) предоставляет каждому типу клиента собственный тонкий шлюз, настроенный под его потребности, а общие сервисы остаются универсальными.
Это позволяет избежать перегруженного шлюза, который пытается делать всё, и даёт каждой команде клиента возможность работать независимо.
Проблема обнаружения
В динамической среде экземпляры сервисов появляются и исчезают, а их IP-адреса меняются. Жёстко заданный адрес http://10.0.3.14:8080 ненадёжен. Обнаружение сервисов поддерживает актуальный реестр, в котором указано, «какие работоспособные экземпляры сервиса X существуют прямо сейчас», чтобы вызывающая сторона могла во время вызова сопоставить логическое имя с реальным адресом.
Обнаружение на стороне клиента и на стороне сервера
Есть две модели:
- На стороне клиента — вызывающая сторона запрашивает реестр (Consul, etcd), сама выбирает экземпляр и выполняет балансировку нагрузки.
- На стороне сервера — вызывающая сторона обращается к стабильному виртуальному адресу (балансировщику нагрузки или сервису Kubernetes), который сам определяет экземпляр и распределяет нагрузку.
В Kubernetes обнаружение на стороне сервера обычно доступно сразу: вызовите http://customers-svc, а кластерный DNS и сервис сделают остальное. За пределами Kubernetes часто используются реестры в стиле Consul.
Запрос к реестру
При обнаружении на стороне клиента вызывающая сторона на PHP запрашивает у реестра работоспособные экземпляры и выбирает один из них. Реестр возвращает только экземпляры, прошедшие проверки работоспособности, поэтому неработающие узлы автоматически исключаются.
<?php
require 'vendor/autoload.php';
use GuzzleHttp\Client;
function discover(Client $http, string $service): string {
// Consul: only passing-health instances
$res = $http->get("http://consul:8500/v1/health/service/$service?passing=true");
$nodes = json_decode((string) $res->getBody(), true);
if (!$nodes) throw new \RuntimeException("No healthy $service");
$pick = $nodes[array_rand($nodes)]['Service']; // simple LB
return "http://{$pick['Address']}:{$pick['Port']}";
}Проверки работоспособности и регистрация
Качество обнаружения зависит от качества данных о работоспособности. Каждый сервис предоставляет конечную точку /health, которая проверяет его реальные зависимости (базу данных, кэш), и регистрирует себя при запуске (или регистрируется платформой). Реестр проверяет эту конечную точку и удаляет экземпляры, не прошедшие проверку.
Проверка работоспособности должна быть содержательной: ответ 200 при недоступной базе данных хуже, чем отсутствие ответа, — он направляет трафик на неисправный узел.
<?php
// GET /health
function health(PDO $db, Redis $cache): array {
$checks = [
'db' => safe(fn() => $db->query('SELECT 1') !== false),
'cache' => safe(fn() => $cache->ping() === '+PONG'),
];
$ok = !in_array(false, $checks, true);
http_response_code($ok ? 200 : 503);
return ['status' => $ok ? 'pass' : 'fail', 'checks' => $checks];
}
function safe(callable $c): bool { try { return (bool) $c(); } catch (\Throwable) { return false; } }Проверка живости и готовности
Одной конечной точки проверки недостаточно — нужно различать два вопроса:
- Живость: «процесс работает?» Если проверка завершается ошибкой, оркестратор перезапускает контейнер. Проверка должна быть дешёвой и не зависеть от внешних компонентов, иначе нестабильная база данных вызовет бессмысленные перезапуски.
- Готовность: «может ли сервис прямо сейчас обслуживать трафик?» Если проверка завершается ошибкой, трафик не направляется к сервису, но процесс продолжает работать (например, прогревает кэш или временно не может подключиться к базе данных).
Смешение этих проверок приводит к циклам перезапуска или направлению трафика на ещё не готовые узлы.
<?php
// GET /livez - is the process itself healthy? (no external deps)
function livez(): void { http_response_code(200); echo 'alive'; }
// GET /readyz - should we receive traffic? (checks dependencies)
function readyz(PDO $db): void {
try { $db->query('SELECT 1'); http_response_code(200); echo 'ready'; }
catch (\Throwable) { http_response_code(503); echo 'not ready'; }
}Быстрая проверка
Сокращение числа обращений клиента.
Итоги
Маршрутизация и поиск сервисов:
- Шлюз API централизует маршрутизацию, аутентификацию, ограничение частоты запросов, TLS и агрегацию.
- Выполняйте агрегацию параллельно; используйте BFF, когда потребности клиентов различаются.
- Обнаружение сервисов сопоставляет логические имена с актуальными работоспособными экземплярами.
- Обнаружение на стороне клиента (запрос к реестру) отличается от обнаружения на стороне сервера (стабильный балансировщик нагрузки или сервис Kubernetes).
- Содержательные проверки работоспособности не позволяют направлять трафик на неисправные узлы.
Далее: как сделать все эти вызовы устойчивыми к неизбежным сбоям отдельных компонентов.
Часто задаваемые вопросы
Урок «Шлюзы API и обнаружение сервисов» бесплатный?
Да — полный текст урока «Шлюзы API и обнаружение сервисов» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс PHP Academy, подпишись на CoddyKit PRO. Курс PHP Academy содержит 4 уроков всего.
Чему я научусь в уроке «Шлюзы API и обнаружение сервисов»?
Динамически маршрутизируйте запросы, объединяйте данные и находите сервисы Ты практикуешь PHP Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать PHP Academy?
Предыдущий опыт не требуется. PHP Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 4.
Сколько времени занимает урок «Шлюзы API и обнаружение сервисов»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке PHP Academy?
Да. Каждый урок PHP Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- От монолита к микросервисам
- Взаимодействие сервисов: REST и gRPC
- Шлюзы API и обнаружение сервисов
- Отказоустойчивость: размыкатели и повторные попытки