Spring Cloud Gateway убивает 10 вопросов?

Java
Spring Cloud Gateway убивает 10 вопросов?

Всем привет, я не Кай Чен~

В последнее время многие друзья написали мне в личных сообщениях, чтобы призвать больше«Продвинутое весеннее облако», Чен Моу также резюмировал это.Последняя причина заключается в том, что Чен Моу пытался объяснить важные моменты знания компонента в статье ранее, что привело к длинной статье, долгому времени написания и утомительному чтению.

Поэтому Чен решил объединить каждый компонент в последующих статьях.расколотьОн разделен на разные части, и каждая статья вводит один или два пункта знаний, которые могут не только повысить эффективность письма, но и позволить друзьям учиться быстрее и своевременно.

Это«Продвинутое весеннее облако»первоеСемьСтатьи, предыдущие статьи следующие:

Эта статья представляет важную роль в микросервисах: шлюз, Что касается того, как выбрать шлюз, поскольку Alibaba еще не запустила шлюз, конечно, выбран облачный шлюз Spring, в конце концов, это сын.

Каталог статей выглядит следующим образом:

Зачем нужен шлюз?

В традиционной монолитной архитектуре клиенту открыта только одна служба для вызова, но в микросервисной архитектуре система разделена на несколько микросервисов, так как же вызывать эти микросервисы в качестве клиента? При отсутствии шлюза адрес вызова каждой микрослужбы может быть записан только локально.

Архитектуры микросервисов без шлюза часто имеют следующие проблемы:

  • Клиент несколько раз запрашивает разные микросервисы, что увеличивает сложность написания клиентского кода или конфигурации.
  • Аутентификация сложна, и каждая служба требует независимой аутентификации.
  • Существуют междоменные запросы, которые относительно сложно обрабатывать в определенных сценариях.

Основная функция шлюза?

Шлюз — это портал всех микросервисов, маршрутизация и переадресация — это только самые основные функции, кроме того, есть и другие функции, такие как:Сертификация,Аутентификация,предохранитель,Ограничение,мониторинг журналатак далее.........

Вышеупомянутые сценарии применения будут подробно представлены в последующих статьях, а не в центре внимания сегодняшнего дня.

Почему стоит выбрать Spring Cloud Gateway?

В версии 1.x использовался шлюз Zuul, но в версии 2.x обновление zuul продолжало отскакивать, и Spring Cloud, наконец, разработала шлюз для замены Zuul, то есть Spring Cloud Gateway.

Я должен выбрать своему сыну Spring Cloud Gateway.Многие его идеи позаимствованы у zuul.Так называемый синий лучше синего.Функционал и производительность однозначно лучше zuul.Иначе зачем Spring Cloud его публиковать?

Важная причина:

Spring Cloud Gateway построен на Spring Boot 2.x, Spring WebFlux и [Project Reactor.

Не беспокойтесь об интеграции, совместимости и производительности Spring Boot.

Какие термины необходимо знать для Spring Cloud Gateway?

  1. маршрут: основной строительный блок для шлюзов. Он состоит из идентификатора, целевого URI, набора утверждений и набора фильтров. Если совокупный результат утверждения верен, маршрут сопоставляется.
  2. Утверждать (Предикат): Обратитесь к новой функции Predicate в Java8, которая позволяет разработчикам сопоставлять любой контент в HTTP-запросах, например заголовки или параметры.
  3. фильтр: содержимое запросов и ответов можно изменить до или после возврата запроса.

Как построить шлюз?

Зачем вывешивать эту картинку?

Его необходимо адаптировать согласно версии на картинке выше, иначе будут непредвиденные баги.

новыйcloud-gateway9023, добавьте следующие зависимости:

<!--gateway-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

Уведомление: нужно удалитьspring-boot-starter-webЗависимость, иначе запустить ошибку

Что ж, проект построен, собственно такая зависимость добавлена, подробная настройка описана ниже.

Что такое Предсказать?

Предикат исходит из интерфейса java8. Predicate принимает один входной параметр и возвращает логический результат. Этот интерфейс содержит несколько методов по умолчанию для объединения предикатов в другую сложную логику (например: И, ИЛИ, НЕ).

Его можно использовать для проверки параметров запроса интерфейса и для определения того, изменились ли новые и старые данные и нуждаются ли они в обновлении.

Spring Cloud Gateway имеет множество встроенных прогнозов, и исходный код этих прогнозов находится вorg.springframework.cloud.gateway.handler.predicateВ упаковке вы можете прочитать это, если вам интересно. Вот некоторые встроенные утверждения:

内置的断言

Вышеупомянутые 11 утверждений Чен не будет вводить здесь, как их настроить, и официальные документы очень ясны.

Официальная документация:docs.spring.IO/spring - уродливо...

Давайте возьмем последнее утверждение веса в качестве примера, чтобы представить, как его настроить. Конфигурация выглядит следующим образом:

spring:
  cloud:
    gateway:
      ## 路由
      routes:
        ## id只要唯一即可,名称任意
        - id: gateway-provider_1
          uri: http://localhost:9024
          ## 配置断言
          predicates:
            ## Path Route Predicate Factory断言,满足/gateway/provider/**这个请求路径的都会被路由到http://localhost:9024这个uri中
            - Path=/gateway/provider/**
            ## Weight Route Predicate Factory,同一分组按照权重进行分配流量,这里分配了80%
            ## 第一个group1是分组名,第二个参数是权重
            - Weight=group1, 8
            
        ## id必须唯一
        - id: gateway-provider_2
          ## 路由转发的uri
          uri: http://localhost:9025
          ## 配置断言
          predicates:
            ## Path Route Predicate Factory断言,满足/gateway/provider/**这个请求路径的都会被路由到http://localhost:9024这个uri中
            - Path=/gateway/provider/**
            ## Weight Route Predicate Factory,同一分组按照权重进行分配流量,这里分配了20%
            ## 第一个group1是分组名,第二个参数是权重
            - Weight=group1, 2

routesНиже приведена настроенная политика маршрутизации, компоненты которой следующие:

  • id: уникальный идентификатор маршрута, имя произвольное
  • uri: uri, пересылаемый по маршруту
  • predicates: Конфигурация утверждений, вы можете настроить несколько

Имена утверждений в Spring Cloud Gateway стандартизированы в формате:xxxRoutePredicateFactory.

Например, взвешенное утверждение:WeightRoutePredicateFactory, затем сразу взять предыдущееWeight.

Если переадресация маршрута по умолчанию направлена ​​​​на два, она перенаправляется в порядке конфигурации, а пути настраиваются выше:Path=/gateway/provider/**, если вес не настроен, его необходимо перенаправить наhttp://localhost:9024.

Но так как конфигурация настроена с весами и одинаковой группировкой, трафик распределяется по соотношению весов.

Что такое фильтр?

Концепция фильтра очень знакома, и она была затронута в Spring mvc Функция и жизненный цикл фильтра Gateway аналогичны.

Жизненный цикл шлюза:

  • PRE: этот фильтр вызывается перед маршрутизацией запроса. Мы можем использовать этот фильтр для реализации аутентификации, выбора запрошенных микросервисов в кластере, регистрации отладочной информации и многого другого.
  • POST: этот фильтр выполняется после маршрутизации в микросервис. Такие фильтры можно использовать для добавления стандартных заголовков HTTP к ответам, сбора статистики и метрик, отправки ответов от микросервисов клиентам и многого другого.

Фильтр шлюза можно разделить на два типа по сфере действия:

  • GatewayFilter: Применить к одному маршруту или сгруппированному маршруту (необходимо настроить в файле конфигурации).
  • GlobalFilter: применяется ко всем маршрутам (настройка не требуется, действует глобально)

GatewayFilter (локальный фильтр)

В Spring Cloud Gateway встроено множество локальных фильтров, как показано ниже:

Локальный фильтр должен быть настроен на указанном маршруте, чтобы он вступил в силу, и по умолчанию он не действует.

кAddResponseHeaderGatewayFilterFactoryВозьмите этот фильтр в качестве примера, добавьте заголовок к исходному ответу и настройте его следующим образом:

spring:
  cloud:
    gateway:
      ## 路由
      routes:
        ## id只要唯一即可,名称任意
        - id: gateway-provider_1
          uri: http://localhost:9024
          ## 配置断言
          predicates:
            ## Path Route Predicate Factory断言,满足/gateway/provider/**这个请求路径的都会被路由到http://localhost:9024这个uri中
            - Path=/gateway/provider/**
          ## 配置过滤器(局部)
          filters:
            - AddResponseHeader=X-Response-Foo, Bar

Запрос браузера обнаружил, что в заголовке ответа уже естьX-Response-Foo=BarЭта пара ключ-значение, как показано ниже:

Уведомление: имя фильтра должно содержать только префикс, а имя фильтра должно бытьxxxGatewayFilterFactory(в том числе на заказ).

Дополнительные настройки фильтров см. в официальной документации:docs.spring.IO/spring - уродливо...

Несмотря на то, что встроенные фильтры могут решить многие сценарии, неизбежно, что некоторые специальные требования должны быть настроены для фильтра.Давайте представим, как настроить локальные фильтры.

Сцены: Моделирование процесса проверки авторизации, если заголовок запроса или параметры запроса содержатtokenзатем отпустите, иначе сразу перехватите и верните401, код показан ниже:

/**
 * 名称必须是xxxGatewayFilterFactory形式
 * todo:模拟授权的验证,具体逻辑根据业务完善
 */
@Component
@Slf4j
public class AuthorizeGatewayFilterFactory extends AbstractGatewayFilterFactory<AuthorizeGatewayFilterFactory.Config> {

    private static final String AUTHORIZE_TOKEN = "token";

    //构造函数,加载Config
    public AuthorizeGatewayFilterFactory() {
        //固定写法
        super(AuthorizeGatewayFilterFactory.Config.class);
        log.info("Loaded GatewayFilterFactory [Authorize]");
    }

    //读取配置文件中的参数 赋值到 配置类中
    @Override
    public List<String> shortcutFieldOrder() {
        //Config.enabled
        return Arrays.asList("enabled");
    }

    @Override
    public GatewayFilter apply(AuthorizeGatewayFilterFactory.Config config) {
        return (exchange, chain) -> {
            //判断是否开启授权验证
            if (!config.isEnabled()) {
                return chain.filter(exchange);
            }

            ServerHttpRequest request = exchange.getRequest();
            HttpHeaders headers = request.getHeaders();
            //从请求头中获取token
            String token = headers.getFirst(AUTHORIZE_TOKEN);
            if (token == null) {
                //从请求头参数中获取token
                token = request.getQueryParams().getFirst(AUTHORIZE_TOKEN);
            }

            ServerHttpResponse response = exchange.getResponse();
            //如果token为空,直接返回401,未授权
            if (StringUtils.isEmpty(token)) {
                response.setStatusCode(HttpStatus.UNAUTHORIZED);
                //处理完成,直接拦截,不再进行下去
                return response.setComplete();
            }
            /**
             * todo chain.filter(exchange) 之前的都是过滤器的前置处理
             *
             * chain.filter().then(
             *  过滤器的后置处理...........
             * )
             */
            //授权正常,继续下一个过滤器链的调用
            return chain.filter(exchange);
        };
    }

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class Config {
        // 控制是否开启认证
        private boolean enabled;
    }
}

Локальный фильтр должен быть настроен в маршруте, чтобы он вступил в силу.Конфигурация выглядит следующим образом:

spring:
  cloud:
    gateway:
      ## 路由
      routes:
        ## id只要唯一即可,名称任意
        - id: gateway-provider_1
          uri: http://localhost:9024
          ## 配置断言
          predicates:
            ## Path Route Predicate Factory断言,满足/gateway/provider/**这个请求路径的都会被路由到http://localhost:9024这个uri中
            - Path=/gateway/provider/**
          ## 配置过滤器(局部)
          filters:
            - AddResponseHeader=X-Response-Foo, Bar
            ## AuthorizeGatewayFilterFactory自定义过滤器配置,值为true需要验证授权,false不需要
            - Authorize=true

Прямой доступ в этот момент:http://localhost:9023/gateway/provider/port без токена возвращает результат, как показано ниже:

Параметры запроса приносят токен:http://localhost:9023/gateway/provider/port?token=abcdcdecd-ddcdeicd12, возвращен успешно, как показано ниже:

вышеупомянутыйAuthorizeGatewayFilterFactoryЭто включает только предварительную обработку фильтра, а постобработка находится вchain.filter().then()серединаthen()Завершено в методе, вы можете увидеть подробности в исходном коде проектаTimeGatewayFilterFactory, код больше не публикуется, как показано ниже:

Глобальный фильтр

Глобальные фильтры применяются ко всем маршрутам без настройки разработчика. Spring Cloud Gateway также имеет некоторые встроенные глобальные фильтры, как показано ниже:

GlobalFilterФункция на самом деле такая же, какGatewayFilterто же самое, простоGlobalFilterОбласть действия — все конфигурации маршрутизации, не привязанные к указанной конфигурации маршрутизации. несколькоGlobalFilterв состоянии пройти@OrderилиgetOrder()способ указать каждыйGlobalFilterПорядок выполнения , чем меньше значение ордера, темGlobalFilterЧем выше приоритет выполнения.

Обратите внимание, что поскольку существует два типа фильтров, предварительный и пост-фильтр, если порядковое значение фильтра предварительного типа меньше, то оно должно быть на верхнем уровне цепочки предварительного фильтра, а если порядковое значение фильтра пост-типа меньше, то он должен быть в верхней части цепочки предварительных фильтров.Нижний слой цепочки предварительных фильтров. Схематическая диаграмма выглядит следующим образом:

Конечно, помимо встроенных глобальных фильтров, в реальной работе необходимы и пользовательские фильтры, давайте познакомимся с тем, как их настроить.

Сценарий: смоделируйте функцию журнала доступа Nginx и запишите соответствующую информацию о каждом запросе. код показывает, как показано ниже:

/**
 * 实现GlobalFilter
 */
@Slf4j
@Component
@Order(value = Integer.MIN_VALUE)
public class AccessLogGlobalFilter implements GlobalFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //filter的前置处理
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getPath().pathWithinApplication().value();
        InetSocketAddress remoteAddress = request.getRemoteAddress();
        return chain
                //继续调用filter
                .filter(exchange)
                //filter的后置处理
                .then(Mono.fromRunnable(() -> {
            ServerHttpResponse response = exchange.getResponse();
            HttpStatus statusCode = response.getStatusCode();
            log.info("请求路径:{},远程IP地址:{},响应码:{}", path, remoteAddress, statusCode);
        }));
    }
}

Что ж, глобальный фильтр не нужно настраивать на маршруте, его можно внедрить в контейнер IOC, чтобы он действовал глобально.

В этот момент выдается запрос, и консоль выводит следующую информацию:

请求路径:/gateway/provider/port,远程IP地址:/0:0:0:0:0:0:0:1:64114,响应码:200 OK

Как интегрировать реестр?

В приведенной выше демонстрации нет встроенного реестра, и каждая конфигурация маршрутизации указывает фиксированный URI службы, как показано ниже:

Какой вред в этом?

  • После изменения IP-адреса службы необходимо изменить uri в конфигурации маршрутизации.
  • Балансировка нагрузки не может быть достигнута в сервисном кластере

На этом этапе требуется интегрированный реестр, чтобы шлюз мог автоматически получать URI (балансировку нагрузки) из реестра.

Центр регистрации здесь, конечно же, выбирает Nacos, а незнакомые друзья, пожалуйста, прочитайте первую статью Чена «Spring Cloud Advanced»:Пятьдесят пять картинок расскажут вам, насколько силен Nacos, перевозчик душ микросервисов?

Зависимости Nacos добавляются в файл pom следующим образом:

<!--nacos注册中心-->
    <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>

Включите функцию реестра в классе запуска, как показано ниже:

Указываем адрес реестра nacos в конфигурационном файле:

spring:
  application:
    ## 指定服务名称,在nacos中的名字
    name: cloud-gateway
  cloud:
    nacos:
      discovery:
        # nacos的服务地址,nacos-server中IP地址:端口号
        server-addr: 127.0.0.1:8848

Единственная разница в конфигурации маршрутизации — это маршрутизация.uri,Формат:lb://service-name, это фиксированный способ записи:

  • lb: фиксированный формат, который относится к получению микросервисов по имени от nacos и следованию стратегии балансировки нагрузки.
  • service-name: Имя службы реестра nacos, это не в форме IP-адреса.

Полная демонстрация конфигурации для интеграции реестра Nacos выглядит следующим образом:

spring:
  application:
    ## 指定服务名称,在nacos中的名字
    name: cloud-gateway
  cloud:
    nacos:
      discovery:
        # nacos的服务地址,nacos-server中IP地址:端口号
        server-addr: 127.0.0.1:8848
    gateway:
      ## 路由
      routes:
        ## id只要唯一即可,名称任意
        - id: gateway-provider_1
        ## 使用了lb形式,从注册中心负载均衡的获取uri
          uri: lb://gateway-provider
          ## 配置断言
          predicates:
            ## Path Route Predicate Factory断言,满足/gateway/provider/**这个请求路径的都会被路由到http://localhost:9024这个uri中
            - Path=/gateway/provider/**
          ## 配置过滤器(局部)
          filters:
            - AddResponseHeader=X-Response-Foo, Bar

почему указаноlbВы можете включить балансировку нагрузки.Как упоминалось ранее, глобальный фильтрLoadBalancerClientFilterОн отвечает за маршрутизацию и балансировку нагрузки.Вы можете увидеть следующий исходный код:

Как реализовать динамическую маршрутизацию?

Все приведенные выше примеры записывают ряд конфигураций шлюза в файл конфигурации проекта.После изменения маршрутизации проект необходимо перепроецировать, что очень дорого в обслуживании.

Фактически, мы можем настроить шлюз, хранящийся в центре распределения, чтобы унифицированное управление центральной конфигурацией после изменения маршрута требовалось только изменить центр конфигурации, который сможет достичьОдна модификация, более эффективнаяцель.

Конечно, мы должны использовать Nacos в качестве центра конфигурации.Добавьте зависимости следующим образом:

<!--    nacos配置中心的依赖-->
    <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    </dependency>

существуетbootstrap.ymlФайл указывает Nacos как некоторые связанные конфигурации центра конфигурации:

spring:
  application:
    ## 指定服务名称,在nacos中的名字
    name: cloud-gateway
  cloud:
    nacos:
      ## todo 此处作为演示,仅仅配置了后缀,其他分组,命名空间根据需要自己配置
      config:
        server-addr: 127.0.0.1:8848
        ## 指定文件后缀未yaml
        file-extension: yaml

Создано в общедоступном пространстве имен в nacosdataIdзаcloud-gateway.yamlконфигурации (без указания среды), содержание конфигурации следующее:

На этом настройка завершена. Что касается эффекта, попробуйте своими руками. . . . . . .

Как настроить глобальную обработку исключений?

В предыдущем тесте мы можем увидеть явление: когда маршрутизируемый микросервис находится в автономном режиме или отключен, Spring Cloud Gateway напрямую возвращает страницу с ошибкой, как показано ниже:

Очевидно, что такая информация об исключении неудобна, и возвращаемая информация об исключении должна быть настроена в архитектуре разделения внешнего и внутреннего интерфейса.

Используются традиционные сервисы Spring Boot@ControllerAdvice чтобы обернуть глобальную обработку исключений, но запрос не пришел, потому что служба была отключена.

Поэтому в шлюзе необходимо настроить уровень глобальной обработки исключений, чтобы взаимодействие с клиентом было более дружественным.

Spring Cloud Gateway предоставляет множество глобальных методов обработки, сегодня Чен представляет только один из них, и его реализация довольно элегантна.

Создать класс напрямуюGlobalErrorExceptionHandler,выполнитьErrorWebExceptionHandler, который переписываетhandleметод, код выглядит следующим образом:

/**
 * 用于网关的全局异常处理
 * @Order(-1):优先级一定要比ResponseStatusExceptionHandler低
 */
@Slf4j
@Order(-1)
@Component
@RequiredArgsConstructor
public class GlobalErrorExceptionHandler implements ErrorWebExceptionHandler {

	private final ObjectMapper objectMapper;

	@SuppressWarnings({"rawtypes", "unchecked", "NullableProblems"})
	@Override
	public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
		ServerHttpResponse response = exchange.getResponse();
		if (response.isCommitted()) {
			return Mono.error(ex);
		}

		// JOSN格式返回
		response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
		if (ex instanceof ResponseStatusException) {
			response.setStatusCode(((ResponseStatusException) ex).getStatus());
		}

		return response.writeWith(Mono.fromSupplier(() -> {
			DataBufferFactory bufferFactory = response.bufferFactory();
			try {
				//todo 返回响应结果,根据业务需求,自己定制
				CommonResponse resultMsg = new CommonResponse("500",ex.getMessage(),null);
				return bufferFactory.wrap(objectMapper.writeValueAsBytes(resultMsg));
			}
			catch (JsonProcessingException e) {
				log.error("Error writing response", ex);
				return bufferFactory.wrap(new byte[0]);
			}
		}));
	}
}

Хорошо, глобальная обработка исключений была настроена. После тестирования данные JSON возвращаются нормально, как показано ниже:

Стиль JSON настраивается в соответствии с потребностями схемы.

Суммировать

Сегодня здесь представлен Spring Cloud Gateway, в основном представляющий следующие точки знаний:

  • Зачем нужен шлюз? Основные функции шлюза
  • Как создать шлюз микросервисов с нуля
  • Концепция предсказания
  • Концепция фильтров, фильтры, встроенные в Spring Cloud Gateway, и способы их настройки
  • Как интегрировать реестр Nacos и добиться балансировки нагрузки
  • Как интегрировать Nacos для достижения динамической маршрутизации, достижения одной модификации и более эффективных функций
  • глобальная обработка исключений

Как вы думаете, Spring Cloud Gateway закончился? Невозможно, в дальнейшем будет более подробное и актуальное введение, в следующей статье будет представлен ......

Исходный код проекта загружен на Github, а официальный аккаунт [Code Ape Technology Column] отвечает ключевым словам:9528Получать!

Последнее слово (пожалуйста, обратите внимание, не проституируйте меня по пустякам)

Каждая оригинальная статья Чен Моу тщательно выводится, особенно статьи в колонке "Spring Cloud Advanced". Слишком много знаний. Если вы хотите рассказать об этом подробно, вы должны потратить много времени на подготовку, начиная с Знание указывает на демонстрацию исходного кода.

Если эта статья была для вас полезной или просветительской, пожалуйста, помогитеподобно,заглянуть,Вперед,собирать, Ваша поддержка - самая большая мотивация для меня продолжать!