Глубокое понимание высокопроизводительного шлюза микросервисов — Spring Cloud Gateway

Spring Cloud

предисловие

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

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

Роль шлюза API

Для решения этой проблемы можно использовать шлюзы, добавив шлюз API между клиентом и сервером.

Шлюз не только выполняет переадресацию запросов и интеграцию услуг, но и с унифицированным входом шлюза также может выполнять следующие функции:

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

выпуск оттенков серого

Скорость итерации продукта у многих компаний очень высока.В режиме высокочастотной итерации часто возникают некоторые риски, такие как:

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

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

Так называемый выпуск в градациях серого означает, что функции, которые должны быть выпущены, сначала открываются для небольшого числа пользователей, а область влияния контролируется в очень узком диапазоне.Например, A / B Test - это способ выпуска в градациях серого, то есть некоторые пользователи продолжают использовать функцию А, а другая небольшая группа пользователей использует новую функцию Б. Проведя опрос удовлетворенности пользователей, использующих функцию B, и оценив показатели производительности и стабильности вновь выпущенного кода, выпуск новой версии будет постепенно увеличиваться до тех пор, пока не будет выполнен полный объем или версия не будет откатана.

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

1595748184114

Введение в Spring Cloud Gateway

Spring Cloud Gateway — это технология шлюза API, разработанная официальной командой Spring. Его цель — заменить Zuul и предоставить простой и эффективный шлюз API для микросервисов.

Spring Cloud Gateway — это шлюз, разработанный с использованием таких технологий, как Spring Boot 2.0, Spring WebFlux и Project Reactor, Он не только обеспечивает унифицированный способ маршрутизации запросов, но и предоставляет самые основные функции шлюза на основе цепочки фильтрации, такие как : безопасность, мониторинг, закапывание и ограничение тока и т.д.

преимущество

  • Высокая производительность, в 1,6 раза выше, чем у Zuul
  • Мощный, со встроенным множеством практичных функций, таких как переадресация, мониторинг, ограничение тока и т. д.
  • Элегантный дизайн и простое расширение

недостаток

  • Использование Netty и WebFlux, а не традиционной модели программирования сервлетов, требует определенных затрат на обучение.
  • Он не может работать в контейнере сервлетов и не может быть встроен в пакет WAR, то есть его нельзя развернуть в контейнерах сервлетов, таких как Tomcat и Jetty, а можно выполнить только как пакет jar.
  • Spring Boot 1.x не поддерживается, требуется 2.0 и выше

Анализ принципа Spring Cloud Gateway

Процесс запроса Spring Cloud Gateway показан на рисунке:

1595756235582

Есть несколько очень важных понятий:

  • Маршрут: это основной компонент шлюза, состоящий из идентификатора, целевого URL-адреса, коллекции предикатов и коллекции фильтров.
  • Predicate: это функциональный интерфейс, представленный в Java 8, который обеспечивает возможность утверждения. Он может соответствовать чему угодно в HTTP-запросе. Если совокупный результат оценки Predicate истинен, это означает, что запрос будет переадресован текущим маршрутизатором.
  • Фильтр: обеспечивает предварительную и постфильтрацию запросов.

Битва шлюза Spring Cloud Gateway

Сначала подготовьте два приложения Spring Boot.

  • spring-cloud-gateway-service имитирует микросервис.
  • spring-cloud-gateway-sample — отдельная служба шлюза.

spring-cloud-gateway-service

Создайте приложение на основе каркаса Spring Boot и добавьте зависимость spring-boot-starter-web. СоздаватьHelloControllerКласс публикует интерфейс и запускает приложение.

@RestController
public class HelloController {
    
    @GetMapping("/say")
    public String sayHello() {
        return "[spring-cloud-gateway-service]:say Hello";
    }
}

spring-cloud-gateway-sample

Создайте приложение Spring Boot и добавьте зависимости Spring Cloud Gateway.

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

существуетapplication.ymlДобавьте конфигурацию маршрутизации шлюза в файл.

spring:
  cloud:
    gateway:
      routes:
        - id: config_route
          predicates:
            - Path=/gateway/** # 路径匹配
          filters:
            - StripPrefix=1 # 跳过前缀
          uri: http://localhost:8080/say # spring-cloud-gateway-service 的访问地址
server:
  port: 8088

Значения полей в приведенной выше конфигурации объясняются следующим образом:

  • id: Идентификатор пользовательского маршрута, пусть он будет уникальным.
  • uri: адрес целевого сервера, поддерживает общий URI и имя службы регистрации приложений lb://, последнее указывает на то, что адрес службы кластера получен из центра регистрации.
  • predicates: условие маршрутизации, решить, следует ли выполнять маршрутизацию запроса в соответствии с результатом сопоставления.
  • filters: правила фильтрации, включаяpreа такжеpostфильтр. вStripPrefix=1, указывающее, что шлюз удаляет часть префикса в пути URL-адреса в соответствии со значением конфигурации (здесь удалите префикс, то есть удалите шлюз из переадресованного целевого URL-адреса).

Запустите приложение, и вы можете получить следующую информацию в консоли.Вы можете видеть, что оно не зависит от Tomcat, а используетNettyWebServerчтобы запустить прослушиватель службы.

2020-07-26 16:32:01.375  INFO 13544 --- [           main] o.s.b.web.embedded.netty.NettyWebServer  : Netty started on port(s): 8088

1595752523729

Введите в браузереhttp://localhost:8088/gateway/say,результат:

1595753192469

Введите в браузереhttp://localhost:8080/say,результат:

1595753246339

Мы обнаружили, что результаты были одинаковыми.

Route Predicate Factories

PredicateдаJava 8Предоставляет функциональный интерфейс, который принимает параметр и возвращает логическое значение, которое можно использовать для условной фильтрации и проверки параметра запроса.

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
}

Spring Cloud GatewayМногие из них предоставляются по умолчаниюRoute Predicate Factory,ЭтиPredicateбудет соответствоватьHTTPразличные свойства запроса и несколькоPredicateв состоянии пройтиandЛогика объединения.

НижеHTTPЗапрашиваемый атрибут соответствуетPredicate:

1595760050850

After Route Predicate

Запросы после указанного времени будут соответствовать этому маршруту.

spring:
  cloud:
    gateway:
      routes:
        - id: after_route
          uri: http://localhost:8080/say
          predicates:
            - After=2020-07-26T16:30:00+08:00[Asia/Shanghai]

Before Route Predicate

Запросы до указанного времени будут соответствовать этому маршруту.

spring:
  cloud:
    gateway:
      routes:
        - id: before_route
          uri: http://localhost:8080/say
          predicates:
            - Before=2020-07-26T16:30:00+08:00[Asia/Shanghai]

Between Route Predicate

Запросы в течение указанного интервала времени будут соответствовать этому маршруту.

spring:
  cloud:
    gateway:
      routes:
        - id: between_route
          uri: http://localhost:8080/say
          predicates:
            - Between=2020-07-26T16:30:00+08:00[Asia/Shanghai], 2020-07-27T16:30:00+08:00[Asia/Shanghai]

Cookie Route Predicate

Запросы с указанным файлом cookie будут соответствовать этому маршруту.

spring:
  cloud:
    gateway:
      routes:
        - id: cookie_route
          uri: http://localhost:8080/say
          predicates:
            - Cookie=username,macro

Используйте инструмент curl для отправки файла cookie какusername=macroзапросы могут соответствовать этому маршруту.

curl http://localhost:8088/gateway/say --cookie "username=macro"

Header Route Predicate

Запросы с указанными заголовками будут соответствовать этому маршруту.

spring:
  cloud:
    gateway:
      routes:
      - id: header_route
        uri: http://localhost:8080/say
        predicates:
        - Header=X-Request-Id, \d+

Используйте инструмент curl для отправки заголовков запроса в видеX-Request-Id:123запросы могут соответствовать этому маршруту.

curl http://localhost:8088/gateway/say -H "X-Request-Id:123" 

Host Route Predicate

Запросы с указанным Хостом будут соответствовать этому маршруту.

spring:
  cloud:
    gateway:
      routes:
        - id: host_route
          uri: http://localhost:8080/say
          predicates:
            - Host=**.macro.com

Используйте инструмент curl для отправки заголовков запроса в видеHost:www.macro.comзапросы могут соответствовать этому маршруту.

curl http://localhost:8088/gateway/say -H "Host:www.macro.com" 

Method Route Predicate

Отправка запроса на указанный метод будет соответствовать этому маршруту.

spring:
  cloud:
    gateway:
      routes:
      - id: method_route
        uri: http://localhost:8080/say
        predicates:
        - Method=GET

Отправить с помощью инструмента завитокGETЗапросы могут соответствовать этому маршруту.

curl http://localhost:8088/gateway/say

Отправить с помощью инструмента завитокPOSTЗапрос не может соответствовать маршруту.

curl -X POST http://localhost:8088/gateway/say

Path Route Predicate

Отправка запроса по указанному пути будет соответствовать этому маршруту.

spring:
  cloud:
    gateway:
      routes:
        - id: path_route
          uri: http://localhost:8080/say
          predicates:
            - Path=/gateway/**

Отправить с помощью инструмента завиток/gateway/Запросы пути могут соответствовать этому маршруту.

curl http://localhost:8088/gateway/say

Отправить с помощью инструмента завиток/abc/Запрос маршрута не может соответствовать маршруту.

curl http://localhost:8088/abc/say

Query Route Predicate

Запросы с указанными параметрами запроса могут соответствовать этому маршруту.

spring:
  cloud:
    gateway:
      routes:
      - id: query_route
        uri: http://localhost:8080/say/getByUsername
        predicates:
        - Query=username

Отправьте ленту с помощью инструмента завитокusername=macroЗапросы с параметрами запроса могут соответствовать этому маршруту.

curl http://localhost:8088/gateway/say/getByUsername?username=macro

Отправка запроса с параметрами запроса или без них с помощью инструмента curl не может соответствовать этому маршруту.

curl http://localhost:8088/gateway/say/getByUsername

RemoteAddr Route Predicate

Запросы, исходящие с указанного удаленного адреса, могут соответствовать этому маршруту.

spring:
  cloud:
    gateway:
      routes:
      - id: remoteaddr_route
        uri: http://localhost:8080/say/
        predicates:
        - RemoteAddr=192.168.1.1/24

Использование инструмента curl для инициирования запроса от 192.168.1.1 может соответствовать этому маршруту.

curl http://localhost:8088/gateway/say/

Weight Route Predicate

Используйте вес для маршрутизации соответствующего запроса, следующее означает, что 80% запросов будут направлены наlocalhost:8080, 20% будут направлены наlocalhost:8081.

spring:
  cloud:
    gateway:
      routes:
      - id: weight_high
        uri: http://localhost:8080
        predicates:
        - Weight=group1, 8
      - id: weight_low
        uri: http://localhost:8081
        predicates:
        - Weight=group1, 2

Gateway Filter Factories

Фильтр разделен наPreтип фильтра иPostтип фильтра.

  • PreФильтр типа выполняется до того, как запрос будет перенаправлен на внутренний сервер, вPerТип цепочки фильтров может сделатьАутентификация,Ограничениеи так далее.

  • PostФильтры типов выполняются после завершения запроса, но до того, как результат будет возвращен клиенту.

В Spring Cloud Gateway встроено много фильтров, есть две реализации фильтра, а именноGatewayFilterа такжеGlobalFilter.GlobalFilterприменяется ко всем маршрутизаторам иGatewayFilterПрименяется только к одному маршрутизатору или группе маршрутизаторов.

AddRequestParameter GatewayFilter

Фильтр, добавляющий параметры к запросу.

spring:
  cloud:
    gateway:
      routes:
        - id: add_request_parameter_route
          uri: http://localhost:8080
          filters:
            - AddRequestParameter=username, macro
          predicates:
            - Method=GET

Приведенная выше конфигурация добавит к запросу GETusername=macroПараметры запроса проверяются инструментом curl с помощью следующей команды.

curl http://localhost:8088/gateway/say/getByUsername

Эквивалентно инициированию запроса:

curl http://localhost:8088/gateway/say/getByUsername?username=macro

StripPrefix GatewayFilter

Фильтр, который удаляет указанное количество префиксов пути.

spring:
  cloud:
    gateway:
      routes:
      - id: strip_prefix_route
        uri: http://localhost:8080
        predicates:
        - Path=/gateway/**
        filters:
        - StripPrefix=1

Вышеупомянутая конфигурация начнется с/gateway/Удалите один бит из пути запроса в начале и используйте следующую команду для проверки с помощью инструмента curl.

curl http://localhost:8088/gateway/say/

Эквивалентно инициированию запроса:

curl http://localhost:8080/say/

PrefixPath GatewayFilter

В отличие от фильтра StripPrefix, фильтр, добавляющий операции к исходному пути.

spring:
  cloud:
    gateway:
      routes:
      - id: prefix_path_route
        uri: http://localhost:8080
        predicates:
        - Method=GET
        filters:
        - PrefixPath=/user

Приведенная выше конфигурация добавит ко всем запросам GET/userПрефикс пути, проверенный с помощью следующей команды с помощью инструмента curl.

curl http://localhost:8088/gateway/say

Эквивалентно инициированию запроса:

curl http://localhost:8080/user/gateway/say/

Hystrix GatewayFilter

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

  • Чтобы включить функцию автоматического выключателя, нам нужноpom.xmlДобавьте связанные зависимости Hystrix в:

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    </dependency>
    
  • Затем добавьте соответствующий класс обработки перехода на более раннюю версию службы:

    @RestController
    public class FallbackController {
    
        @GetMapping("/fallback")
        public Object fallback() {
            Map<String,Object> result = new HashMap<>();
            result.put("data",null);
            result.put("message","Get request fallback!");
            result.put("code",500);
            return result;
        }
    }
    
  • существуетapplication-filter.ymlДобавьте соответствующую конфигурацию в , и при возникновении ошибки маршрутизации она будет перенаправлена ​​на контроллер для обработки перехода на более раннюю версию:

    spring:
      cloud:
        gateway:
          routes:
            - id: hystrix_route
              predicates:
                - Method=GET
              filters:
                - name: Hystrix
                  args:
                    name: fallbackcmd
                    fallbackUri: forward:/fallback
              uri: http://localhost:8080/say
    
  • Закройте spring-cloud-gateway-service и вызовите этот адрес для тестирования:http://localhost:8088/sayобнаружено, что была возвращена информация об обработке ухудшения обслуживания.

    1595764410272

RequestRateLimiter GatewayFilter

Фильтр RequestRateLimiter можно использовать дляОграничение,использоватьRateLimiterРеализовано, чтобы определить, следует ли разрешить выполнение текущего запроса, по умолчанию используется состояние HTTP 429 — слишком много запросов, если запрос слишком велик.

  • существуетpom.xmlДобавьте связанные зависимости к:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
    </dependency>
    
  • Добавьте текущую политику ограничения, ограничьте ток в соответствии с IP-адресом доступа и определите текущий класс политики ограничения.IpKeyResolver, и вводится в пружинный контейнер.

    @Component
    public class IpKeyResolver implements KeyResolver {
        @Override
        public Mono<String> resolve(ServerWebExchange exchange) {
            return Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
        }
    }
    
  • Мы используем Redis для ограничения тока, поэтому нам нужно добавить конфигурацию Redis и RequestRateLimiter, здесь все GET-запросы ограничены IP.

    server:
      port: 8088
    spring:
      redis:
        host: 123.57.45.66
        port: 6379
      cloud:
        gateway:
          routes:
            - id: requestratelimiter_route
              uri: http://localhost:8080/
              filters:
                - name: RequestRateLimiter
                  args:
                    redis-rate-limiter.replenishRate: 1 # 每秒允许处理的请求数量
                    redis-rate-limiter.burstCapacity: 2 # 每秒最大处理的请求数量
                    key-resolver: "#{@ipKeyResolver}" # 限流策略,对应策略的Bean
              predicates:
                - Method=GET
    
  • Запросить адрес несколько раз:http://localhost:8088/say, будет возвращена ошибка с кодом состояния 429;

    1595852606905

Retry GatewayFilter

Фильтр для повторной попытки запроса маршрутизации может определить, следует ли повторить попытку, в соответствии с кодом состояния HTTP, возвращенным запросом маршрутизации.

  • Измените файл конфигурации:

    server:
      port: 8088
    spring:
      cloud:
        gateway:
          routes:
            - id: retry_route
              uri: http://localhost:8080
              predicates:
                - Method=GET
              filters:
                - name: Retry
                  args:
                    retries: 1 # 需要进行重试的次数
                    statuses: BAD_GATEWAY # 返回哪个状态码需要进行重试,返回状态码为5XX进行重试
                    backoff:
                      firstBackoff: 10ms
                      maxBackoff: 50ms
                      factor: 2
                      basedOnPreviousValue: false
    
  • Исправлятьspring-cloud-gateway-serviceсерединаHelloControllerиз/sayметод, который вручную генерирует исключение.

    @GetMapping("/say")
    public String sayHello() {
        if (true) {
            throw new RuntimeException("/say, 异常");
        }
        return "[spring-cloud-gateway-service]:say Hello";
    }
    
  • Когда вызов вернет 500, он повторит попытку, посетите тестовый адрес:http://localhost:8088/say

  • Его можно найтиspring-cloud-gateway-serviceКонсоль сообщает о 2 ошибках, указывающих на повторную попытку.

    2020-07-27 21:50:31.910 ERROR 11200 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException: /say, 异常] with root cause
    
    java.lang.RuntimeException: /say, 异常
    	at com.autu.example.springcloudgatewayservice.HelloController.sayHello(HelloController.java:16) ~[classes/:na]
    

Пользовательская фабрика предикатов

Например, есть служба, которая ограничивает доступ пользователей только в период с 06:00 до 13:00.Predicate FatoryВыполнить это требование невозможно, поэтому в настоящее время нам нужно настроить тот, который может выполнить это требование.Predicate Fatory.

  • СоздайтеTimeBetweenRoutePredicateFactoryнаследование классовAbstractRoutePredicateFactory

  • существуетTimeBetweenRoutePredicateFactoryНаписать статический внутренний классConfig

    @Component
    public class TimeBetweenRoutePredicateFactory extends AbstractRoutePredicateFactory<TimeBetweenRoutePredicateFactory.Config> {
    
        private static final String START_KEY = "start";
        private static final String END_KEY = "end";
        
        public TimeBetweenRoutePredicateFactory() {
            super(TimeBetweenRoutePredicateFactory.Config.class);
        }
    
        @Override
        public Predicate<ServerWebExchange> apply(Config config) {
            LocalTime start = config.getStart();
            LocalTime end = config.getEnd();
            return serverWebExchange -> {
                LocalTime now = LocalTime.now();
                return now.isAfter(start) && now.isBefore(end);
            };
        }
    
        /**
         * 设置配置类与配置文件的关系
         */
        @Override
        public List<String> shortcutFieldOrder() {
            /*
             * 例如我们的配置项是:TimeBetween=上午6:00, 下午1:00
             * 那么按照顺序,start对应的是上午6:00;end对应的是下午1:00
             */
            return Arrays.asList(START_KEY, END_KEY);
        }
    
        static class Config{
            private LocalTime start;
            private LocalTime end;
    
            LocalTime getStart() {
                return start;
            }
    
            void setStart(LocalTime start) {
                this.start = start;
            }
    
            LocalTime getEnd() {
                return end;
            }
    
            void setEnd(LocalTime end) {
                this.end = end;
            }
        }
    }
    
  • конфигурационный файл

    spring:
      cloud:
        gateway:
          routes:
            - id: customizer_predicate
              uri: http://localhost:8080/say/
              predicates:
                - TimeBetween=上午6:00,下午1:00
    
  • настроитьPredicate Fatoryкласс, судя поSpring Cloud Stream, имя класса должно быть "PredicateНазвание фабрики (в этом примере:TimeBetween)" +RoutePredicateFactory

  • Формат времени не настраивается произвольно, а является форматом времени по умолчанию для Spring Cloud Gateway.

Пока что обычайPredicate Fatory, если это не в пределах разрешенного периода времени доступа, доступ сообщит 404, и доступ:http://localhost:8088/say, результат показан на следующем рисунке:

1595860335971

Фабрика пользовательских фильтров

Требование: записывать журналы доступа

  • настроитьFilter FactoryДобрый

    @Component
    public class LogCustomizerGatewayFilterFactory extends AbstractGatewayFilterFactory<LogCustomizerGatewayFilterFactory.Config> {
    
        private Logger logger= LoggerFactory.getLogger(LogCustomizerGatewayFilterFactory.class);
    
        private static final String NAME_KEY = "name";
    
        public LogCustomizerGatewayFilterFactory() {
            super(Config.class);
        }
    
        @Override
        public List<String> shortcutFieldOrder() {
            return Arrays.asList(NAME_KEY);
        }
    
        @Override
        public GatewayFilter apply(Config config) {
            //Filter pre  post
            return ((exchange,chain)->{
                logger.info("[pre] Filter Request, name:"+config.getName());
                //TODO
                return chain.filter(exchange).then(Mono.fromRunnable(()->{
                    //TODO
                    logger.info("[post]: Response Filter");
                }));
            });
        }
    
        static class Config{
            private String name;
    
            String getName() {
                return name;
            }
    
            void setName(String name) {
                this.name = name;
            }
        }
    }
    
  • Добавить связанную конфигурацию

    spring:
      cloud:
        gateway:
          routes:
            - id: log_customizer
              uri: http://localhost:8080/say/
              predicates:
                - Method=GET
              filters:
                - LogCustomizer=Hello Log Customizer
    
  • доступ:http://localhost:8088/say,spring-cloud-gateway-sampleФормируется следующий журнал:

    2020-07-27 22:45:54.606  INFO 11172 --- [ctor-http-nio-3] .a.e.s.LogCustomizerGatewayFilterFactory : [pre] Filter Request, name:Hello Log Customizer
    2020-07-27 22:45:54.776  INFO 11172 --- [ctor-http-nio-4] .a.e.s.LogCustomizerGatewayFilterFactory : [post]: Response Filter
    

    1595861334026

Оригинальный адрес:Осень200.com/2020/07/26/…