Шлюз микросервисов Zuul перешел на Spring Cloud Gateway

задняя часть Микросервисы алгоритм Spring

задний план

В предыдущей статье мы представили шлюз микросервисов.Spring Cloud Netflix Zuul, некоторое время назад было две статьи посвященные новому проекту Spring CloudSpring Cloud Gateway, и из которыхзавод фильтров. В этой статье будет представлена ​​информация о переносе шлюза микросервисов с Zuul на Spring Cloud Gateway.

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

Zuul основан на сервлете 2.5 (с использованием 3.x) и использует блокирующий API. Он не поддерживает никаких постоянных соединений, таких как веб-сокеты. Принимая во внимание, что Gateway построен на Spring Framework 5, Project Reactor и Spring Boot 2 и использует неблокирующий API. Он отлично поддерживает асинхронное неблокирующее программирование.Большинство предыдущих систем Spring представляют собой модели синхронного блокирующего программирования, использующие модели обработки потока для каждого запроса. Даже если в метод Spring MVC Controller добавляется аннотация @Async или возвращаются результаты типов DeferredResult и Callable, фактически синхронный вызов метода все равно инкапсулируется в очередь задач пула потоков, которая по-прежнему является потоком. модель по запросу. Веб-сокеты поддерживаются в Gateway, и, поскольку он тесно интегрирован со Spring, его разработка будет более удобной.

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

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

Зуульские ворота

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

В Zuul легко настроить переадресацию динамической маршрутизации, например:

zuul:
  ribbon:
    eager-load:
      enabled: true     #zuul饥饿加载
  host:
    maxTotalConnections: 200
    maxPerRouteConnections: 20
  routes:
    user:
      path: /user/**
      ignoredPatterns: /consul
      serviceId: user
      sensitiveHeaders: Cookie,Set-Cookie

По умолчанию Zuul будет фильтровать некоторую конфиденциальную информацию в информации заголовка HTTP-запроса при запросе маршрутизации, мы не будем вводить здесь больше.

Аутентификация запроса также настраивается в шлюзе.В сочетании с сервисом Auth эта функция может быть реализована через фильтр Pre, который поставляется с Zuul. Конечно, вы также можете использовать пост-фильтры для адаптации и изменения результатов запроса.

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

Переход на Spring Cloud Gateway

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

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

полагаться

В этой статье используется версия Spring Cloud Gateway:2.0.0.RELEASE. Ниже перечислены основные добавленные зависимости.Подробности см. в проекте на Github.

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
            <!--<version>2.0.1.RELEASE</version>-->
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-gateway-webflux</artifactId>
        </dependency>
    </dependencies>
        
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Finchley.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>        

утверждение маршрута

Spring Cloud Gateway поддерживает определение утверждений маршрута, фильтров и маршрутов, а также поддерживает как ярлыки, так и API-интерфейсы Fluent для файлов конфигурации. Мы объясним с функциями, фактически используемыми в этом проекте.

Утверждение маршрута определяет конкретную услугу маршрута до того, как шлюз перенаправит запрос, обычно на основе пути запроса, тела запроса, метода запроса (GET/POST), адреса запроса, времени запроса, запрошенного HOST и другой информации. В основном мы используем метод, основанный на пути запроса, следующим образом:

spring:
  cloud:
    gateway:
      routes:
      - id: service_to_web
        uri: lb://authdemo
        predicates:
        - Path=/demo/**

Мы определяемservice_to_web, который запросит путь для начала/demo/**Запросы перенаправляются в экземпляр службы authdemo.

Наши требования к утверждениям маршрутизации в этом проекте несложны. Ниже описаны другие утверждения маршрутизации, настроенные с помощью Fluent API:

    @Bean
    public RouteLocator routeLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                .route(r -> r.host("**.changeuri.org").and().header("X-Next-Url")
                        .uri("http://blueskykong.com"))
                .route(r -> r.host("**.changeuri.org").and().query("url")
                        .uri("http://blueskykong.com"))
                .build();
    }

В приведенном выше определении маршрута мы настраиваем и запрашиваем HOST, заголовок запроса и параметры запроса. В определении маршрута можно настроить несколько утверждений для принятия решения об отношении И-или-НЕТ.

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

фильтр

Фильтры делятся на глобальные фильтры и локальные фильтры. мы достигаем путемGlobalFilter,GatewayFilterИнтерфейс, настраиваемый фильтр.

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

В этом проекте мы настроили следующие глобальные фильтры:

  • Фильтр ограничения тока на основе токенов
  • Фильтр ограничения тока на основе алгоритма дырявого ведра
  • глобальный автоматический выключатель
  • Глобальный фильтр аутентификации

Определите глобальные фильтры, которые можно добавить в файл конфигурации, добавивspring.cloud.gateway.default-filtersили реализоватьGlobalFilterинтерфейс.

Фильтр ограничения тока на основе токенов

По прошествии времени система будет добавлять токены в корзину с постоянным интервалом 1/QPS (если QPS=100, интервал равен 10 мс), и если корзина заполнена, она не будет добавлена. Когда приходит каждый запрос, токен будет забран, если нет токена, который можно взять, он заблокирует или откажет в обслуживании.

Еще одним преимуществом ведер с токенами является простота изменения скорости. Как только скорость необходимо увеличить, скорость токенов, помещаемых в корзину, увеличивается по требованию. Как правило, определенное количество токенов добавляется в корзину через равные промежутки времени (например, 100 миллисекунд), а некоторые варианты алгоритмов рассчитывают количество токенов, которые следует добавить, в режиме реального времени.

Реализация по умолчанию предоставляется в Spring Cloud Gateway, нам нужно ввести зависимости redis:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
        </dependency>

и настроить следующим образом:

spring:
  redis:
    host: localhost
    password: pwd
    port: 6378
  cloud:
    default-filters:
      - name: RequestRateLimiter
        args:
          key-resolver: "#{@remoteAddrKeyResolver}"
          rate-limiter: "#{@customRateLimiter}"   # token

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

    @Bean(name = "customRateLimiter")
    public RedisRateLimiter myRateLimiter(GatewayLimitProperties gatewayLimitProperties) {
        GatewayLimitProperties.RedisRate redisRate = gatewayLimitProperties.getRedisRate();
        if (Objects.isNull(redisRate)) {
            throw new ServerException(ErrorCodes.PROPERTY_NOT_INITIAL);
        }
        return new RedisRateLimiter(redisRate.getReplenishRate(), redisRate.getBurstCapacity());
    }
    
        @Bean(name = RemoteAddrKeyResolver.BEAN_NAME)
    public RemoteAddrKeyResolver remoteAddrKeyResolver() {
        return new RemoteAddrKeyResolver();
    }

В приведенной выше реализации инициализируйтеRedisRateLimiterиRemoteAddrKeyResolverдва экземпляра Bean,RedisRateLimiterэто атрибут ограничения тока redis, определенный в шлюзе; иRemoteAddrKeyResolverПозволяет нашему пользовательскому адресу, основанному на запросе, действовать как ключ дроссельной заслонки. Определение текущего ограничивающего ключа выглядит следующим образом:

public class RemoteAddrKeyResolver implements KeyResolver {
    private static final Logger LOGGER = LoggerFactory.getLogger(RemoteAddrKeyResolver.class);

    public static final String BEAN_NAME = "remoteAddrKeyResolver";

    @Override
    public Mono<String> resolve(ServerWebExchange exchange) {
        LOGGER.debug("token limit for ip: {} ", exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
        return Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
    }

}

RemoteAddrKeyResolverДостигнутоKeyResolverИнтерфейс, перезаписать определенный в нем интерфейс, а возвращаемым значением будет адрес в запросе.

Как и выше, реализован фильтр ссылок на основе алгоритма корзины токенов, и конкретные детали не будут раскрываться.

Фильтр ограничения тока на основе алгоритма дырявого ведра

Идея алгоритма Leaky Bucket очень проста, вода (запрос) сначала поступает в дырявое ведро, а дырявое ведро выходит из воды с определенной скоростью (у интерфейса есть скорость отклика), а когда скорость поступления воды слишком высок, он будет переполняться напрямую (частота доступа превышает скорость отклика интерфейса), а затем отклонять запрос, видно, что алгоритм дырявого ведра может принудительно ограничивать скорость передачи данных.

Читатели этой части реализации могут обратиться к проекту GitHub и вспомогательной книге в конце статьи, которая здесь будет пропущена.

глобальный автоматический выключатель

Что касается автоматического выключателя Hystrix, это мера защиты для обеспечения отказоустойчивости при обслуживании.断路器Это само коммутационное устройство, которое используется для защиты цепи от перегрузки.При возникновении короткого замыкания в цепи,断路器Он может вовремя отключить неисправную цепь, чтобы предотвратить перегрузку и пожар.

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

Сюда нужно импортироватьspring-cloud-starter-netflix-hystrixполагаться:

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
            <optional>true</optional>
        </dependency>

И добавьте следующую конфигурацию:

      default-filters:
      - name: Hystrix
        args:
          name: fallbackcmd
          fallbackUri: forward:/fallbackcontroller

Вышеупомянутая конфигурация будет использоватьHystrixCommandУпакуйте оставшиеся фильтры и назовите ихfallbackcmd, так же настраиваем необязательные параметрыfallbackUri, вызывается логика перехода на более раннюю версию, и запрос будет перенаправлен на URI как/fallbackcontrollerобработка контроллера. Определить обработку перехода на более раннюю версию следующим образом:

    @RequestMapping(value = "/fallbackcontroller")
    public Map<String, String> fallBackController() {
        Map<String, String> res = new HashMap();
        res.put("code", "-100");
        res.put("data", "service not available");
        return res;
    }
Глобальный фильтр аутентификации

Мы реализуем аутентификацию легитимности запроса, настроив глобальный фильтр. Конкретные функции здесь не повторяются.GlobalFilterинтерфейс, разница в том, что Webflux передаетServerWebExchange, определив, является ли он внешним интерфейсом (внешний интерфейс не требует аутентификации при входе в систему), и выполните логику обработки, реализованную ранее.

public class AuthorizationFilter implements GlobalFilter, Ordered {

	//....

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        if (predicate(exchange)) {
            request = headerEnhanceFilter.doFilter(request);
            String accessToken = extractHeaderToken(request);

            customRemoteTokenServices.loadAuthentication(accessToken);
            LOGGER.info("success auth token and permission!");
        }

        return chain.filter(exchange);
    }
	//提出头部的token
    protected String extractHeaderToken(ServerHttpRequest request) {
        List<String> headers = request.getHeaders().get("Authorization");
        if (Objects.nonNull(headers) && headers.size() > 0) { // typically there is only one (most servers enforce that)
            String value = headers.get(0);
            if ((value.toLowerCase().startsWith(OAuth2AccessToken.BEARER_TYPE.toLowerCase()))) {
                String authHeaderValue = value.substring(OAuth2AccessToken.BEARER_TYPE.length()).trim();
                // Add this here for the auth details later. Would be better to change the signature of this method.
                int commaIndex = authHeaderValue.indexOf(',');
                if (commaIndex > 0) {
                    authHeaderValue = authHeaderValue.substring(0, commaIndex);
                }
                return authHeaderValue;
            }
        }

        return null;
    }

}

После определения глобального фильтра вам нужно только настроить его:

    @Bean
    public AuthorizationFilter authorizationFilter(CustomRemoteTokenServices customRemoteTokenServices,
                                                   HeaderEnhanceFilter headerEnhanceFilter,
                                                   PermitAllUrlProperties permitAllUrlProperties) {
        return new AuthorizationFilter(customRemoteTokenServices, headerEnhanceFilter, permitAllUrlProperties);
    }

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

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

      - id: service_to_user
        uri: lb://user
        order: 8000
        predicates:
        - Path=/user/**
        filters:
        - AddRequestHeader=X-Request-Foo, Bar
        - StripPrefix=1

Вы также можете передать Fluent API следующим образом:

    @Bean
    public RouteLocator retryRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                .route("retry_java", r -> r.path("/test/**")
                        .filters(f -> f.stripPrefix(1)
                                .retry(config -> config.setRetries(2).setStatuses(HttpStatus.INTERNAL_SERVER_ERROR)))
                        .uri("lb://user"))
                .build();
    }

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

конфигурация маршрутизации

Определения маршрутов перечислены в приведенном выше примере и могут быть определены файлами конфигурации иRouteLocatorОбъект. Здесь следует отметить, что конфигурация вuriАтрибутом может быть конкретный адрес службы (IP + номер порта) или он может быть определен через обнаружение службы и балансировку нагрузки:lb://user, указывающий экземпляр службы, перенаправленный пользователю. Конечно, это требует от нас некоторой настройки.

Введите зависимости для обнаружения службы:

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

открыть в шлюзеspring.cloud.gateway.discovery.locator.enabled=trueВот и все.

CORS-конфигурация

В Spring 5 Webflux настройте CORS, который можно настроить с помощьюWebFilterвыполнить:

    private static final String ALLOWED_HEADERS = "x-requested-with, authorization, Content-Type, Authorization, credential, X-XSRF-TOKEN";
    private static final String ALLOWED_METHODS = "GET, PUT, POST, DELETE, OPTIONS";
    private static final String ALLOWED_ORIGIN = "*";
    private static final String MAX_AGE = "3600";

    @Bean
    public WebFilter corsFilter() {
        return (ServerWebExchange ctx, WebFilterChain chain) -> {
            ServerHttpRequest request = ctx.getRequest();
            if (CorsUtils.isCorsRequest(request)) {
                ServerHttpResponse response = ctx.getResponse();
                HttpHeaders headers = response.getHeaders();
                headers.add("Access-Control-Allow-Origin", ALLOWED_ORIGIN);
                headers.add("Access-Control-Allow-Methods", ALLOWED_METHODS);
                headers.add("Access-Control-Max-Age", MAX_AGE);
                headers.add("Access-Control-Allow-Headers",ALLOWED_HEADERS);
                if (request.getMethod() == HttpMethod.OPTIONS) {
                    response.setStatusCode(HttpStatus.OK);
                    return Mono.empty();
                }
            }
            return chain.filter(ctx);
        };
    }

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

Суммировать

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

Spring Cloud Gateway основан на Webflux, который отлично поддерживает асинхронное неблокирующее программирование, а многие функции более удобны в реализации. Spring5 должен использовать java 8, функциональное программирование — одна из важных особенностей java8, а WebFlux поддерживает функциональное программирование для определения конечных точек маршрутизации для обработки запросов.

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

Для более подробных принципов и деталей реализации вы можете обратить внимание на «Продвинутую архитектуру Spring Cloud Microservice», которая будет опубликована автором в конце этого месяца.Finchley.RELEASEОсновные компоненты версии объясняются в принципе и применяются на практике, а шлюз основан на последней версии Spring Cloud Gateway.

Spring Cloud 微服务架构进阶

Адрес источника этой статьи:
Гитхаб:GitHub.com/Доступно на ETS2012/Нет…или Облако кода:git ee.com/canets/micro…

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

微信公众号

Ссылаться на

Spring Cloud Gateway (текущее ограничение)