Фон проекта и причины выбора Spring Cloud Gateway
Данный проект полностью использует архитектуру микросервисов (MSA), чтобы быстро реагировать на быстро меняющиеся требования бизнеса и обеспечить индивидуальную масштабируемость без вмешательства между сервисами. Особенно он реализуется в сотрудничестве с несколькими разработчиками, обладающими экспертными знаниями в своей области, в рамках структуры 'многопоставщиков (Multi-vendor)', что позволяет осуществлять параллельную разработку. Однако с точки зрения архитектуры возникает задача управления, связанная с фрагментацией границ сервиса и резким увеличением точек управления, что приводит к управленческой сложности.
Ключевые вопросы, решение которых было обязательным для успешного выполнения проекта, следующие.
-
Упрощение управления сложностью многопользовательской среды разработки:Синхронизация конечных точек каждого сервиса в процессе независимого развертывания и отката десятков сервисов невозможна. Было необходимо центральное маршрутизирующее устройство, абстрагирующее сетевую позицию и связывающее запросы.
-
Максимизация производительности через централизацию функций безопасности: Реализация аутентификации (Authentication) и авторизации (Authorization) для каждого сервиса по отдельности является дублированием разработки и приводит к уязвимостям безопасности во всей системе. Это требовало интегрированной реализации на уровне Gateway для синхронизации уровня безопасности всей службы.
-
Стабильная внешняя интеграция в инфраструктуре закрытой сети:Данная система была построена в закрытой сети для обеспечения конфиденциальности, но была необходима точка внешнего взаимодействия. Ключевым моментом стало 'Безопасный шлюз (Secure Gate)', который посредничал в связи, не подвергая прямому раскрытию внутренние службы и скрывая внутреннюю топологию.
-
Обеспечение гибкости для изменения и добавления функций:Для того чтобы реагировать на быстро меняющиеся требования и спецификации интеграции, потребовался гибкий шлюз, который мог бы управлять бизнес-логикой через код, вместо аппаратного оборудования, зависимого от настройки инфраструктуры.
Чтобы комплексно удовлетворить эти требования, мы выбрали Spring Cloud Gateway (SCG). SCG не только полностью интегрируется с экосистемой JVM, но и Асимметричная неблокирующая архитектура на основе Nettyобеспечивает стабильную обработку большого количества одновременных соединений даже с минимальными ресурсами.
Прежде всего, решающим было то, что можно свободно настраивать логику фильтрации с помощью Java-кода. Это позволило эффективно реализовать сложное управление правами между несколькими разработчиками, специализированную безопасность закрытых сетей и динамическую маршрутизацию на уровне приложения, превосходя простую роль передачи трафика.
Реализация бесперебойной динамической маршрутизации
На начальном этапе разработки мы выбрали самый стандартный способ, предлагаемый Spring Cloud Gateway (далее SCG), а именно статическую маршрутизацию (Static Routing). Это способ, при котором маршрутизационные правила явно указываются в файле настройки application.yml, как показано ниже.
spring:
cloud:
gateway:
server:
webflux:
routes:
- id: shared
predicates:
- Path=${server.servlet.context-path}/shared/**
uri: <http://foo-bar.com/api/shared>
filters:
- RewritePath=${server.servlet.context-path}/, /api/
- AddRequestHeader=xgate-id, gate1234
metadata:
response-timeout: 1800000 #30분
connect-timeout: 3000 #30초
Этот подход имеет интуитивно понятную структуру и быстрое внедрение, но в средах, где участвуют несколько разработчиков и часто меняются операционные требования, он проявляет критические недостатки. Даже при добавлении всего одного маршрута или изменении значения таймаута необходимо было изменять файл конфигурации, а затем снова собирать и развертывать весь сервис шлюза.
Особенно процесс, при котором оператор сервиса не может немедленно контролировать путь и должен каждый раз передавать запрос на изменения разработчику, а затем ожидать внедрения в соответствии с графиком развертывания, стал большим бременем, препятствующим гибкости бизнеса.
Чтобы преодолеть такие операционные ограничения, метаданные маршрутизации управляются в базе данных и синхронизируются с движком шлюза в реальном времени.'Динамическая маршрутизация'Мы решили перейти на новый способ. Сначала мы разработали домен GatewayRoute, чтобы нормализовать информацию о маршрутах, разбросанную в существующем YAML-файле, в реляционной модели данных, как показано ниже.
public class GatewayRoute {
private String id;
private String routeName;
private String pathPattern;
private String rewriteFrom;
private String rewriteTo;
private String uri;
private long responseTimeout;
private long connectTimeout;
private boolean active;
private List<ApiPermission> apiPermissions;
private boolean useQueue;
private int maxQueueSize;
private int maxConcurrent;
private List<KeyValue> requestHeaders;
}
public class ApiPermission {
private String path;
private List<String> permittedServices;
}
Эта модель домена была расширена для комплексного управления всей информацией маршрутизации, которая ранее обрабатывалась в статическом режиме, а также метаданными для [интегрированной аутентификации и авторизации] и [управления очередью], которые будут описаны позже.
Также ключевой технический аспект применения динамически сохраненных данных к реальной маршрутизации заключается в использовании внутренних механизмов SCG. SCG автоматически обнаруживает реализации интерфейса RouteDefinitionLocator в момент инициализации и загружает список маршрутов.
Мы создали пользовательский класс GatewayRouteConfig и реализовали логику, которая запрашивает информацию о маршруте из базы данных и преобразует её в формат RouteDefinition, понятный SCG. Это позволило операторам настраивать значения через интерфейс администратора, применяя правила маршрутизации в реальном времени без перезапуска шлюза.
@Configuration
public class GatewayRouteConfig {
@Bean
public RouteDefinitionLocator dbRouteDefinitionLocator() {
return () -> policySeek.findAllGatewayRoutes()
.filter(GatewayRoute::isActive)
.doOnNext(route -> {
queueManager.updateRouteConfig(
route.getId();
route.getMaxQueueSize(),
route.getMaxConcurrent()
);
}
.map(this::convertToRouteDefinition);
}
private RouteDefinition convertToRouteDefinition(GatewayRoute route){
RouteDefinition definition = new RouteDefinition();
definition.setId(route.getId());
definition.setUri(route.getUri());
String fullPath = normalizedContexPath() + route.getPathPattern();
definition.setPredicates(List.of(new PredicateDefinition("Path=": + fullPath)));
List<FilterDefinition> filters = new ArrayList<>();
if (route.isUseQueue()){
filters.add(new FilterDefinition("Queue"));
}
filters.add(new FilterDefinition("RewritePath=" + normalizeContextPath() + route.getRewriteFrom() + "," + route.getRewriteTo()));
route.getRequestHeaders().forEach(keyValue -> filters.add((new FilterDefinition("AddRequestHeader=" + keyValue.getKey() + "," + keyValue.getValue())));
definition.setFilters(filters);
definition.getMetadata().put("response-timeout", route.getResponseTimeout());
definition.getMetadata().put("connect-timeout", route.getConnectTimeout());
}
}
В заключение
Теперь, используя Spring Cloud Gateway, мы отошли от существующего статического конфигурационного метода YAML и перешли на основанный на базе данных Безостановочная динамическая маршрутизация архитектурыМы рассмотрели процесс создания. Этот процесс модернизации, который повысил настройку инфраструктуры до уровня бизнес-данных, принес следующие явные преимущества на практике.
-
Полное разделение операций и распространения:Устранены неэффективности, связанные с необходимостью полностью пересобирать и развертывать систему для добавления единственного API-метода или настройки параметров таймаута. Теперь можно применять правила маршрутизации в реальном времени без прерывания сервиса даже во время работы.
-
Снижение затрат на коммуникацию в много поставщической среде:создана буферная зона, которая позволяет быстро реагировать, просто изменяя данные на уровне шлюза, даже если микросервисы различных разработчиков разворачиваются в разное время или меняют конечные точки.
-
Гибкая архитектурная масштабируемость:Управляя метаданными маршрутизации через нормализованную модель домена, мы завершили создание прочной основы, которая органически объединяет безопасность и управление трафиком, выходя за рамки простого пересылки трафика.
Однако надежное построение динамичной маршрутизируемой среды – это всего лишь развертывание огромного холста. Настоящие практические измышления направлены на то, как поддерживать стандарты безопасности на этой распределенной инфраструктуре и безопасно управлять нарастающим массовым трафиком. В следующей части мы планируем рассмотреть архитектуру безопасности и управления, работающую на основе динамичной маршрутизируемой инфраструктуры, завершенной в этой части.
Hustle Paul