一、Spring Cloud Gateway

我們都知道Spring Cloud Gateway是一個基於Spring Boot、Spring WebFlux、Project Reactor構建的高性能網關,旨在提供簡單、高效的API路由。Spring Cloud Gateway基於Netty運行,因此在傳統Servlet容器中或者打成war包是不能正常運行的。

二、Spring Cloud Gateway兩種負載均衡器

2.1 官網説明兩種負載均衡器

Gateway有兩種客户端負載均衡器,LoadBalancerClientFilterReactiveLoadBalancerClientFilterLoadBalancerClientFilter使用一個Ribbon的阻塞式LoadBalancerClient,Gateway建議使用ReactiveLoadBalancerClientFilter。可以通過設置spring.cloud.loadbalancer.ribbon.enabled=false,切換到ReactiveLoadBalancerClientFilter。無論使用Ribbon還是LoadBalancer,在Route中配置的lb是一樣的

spring:
  cloud:
    gateway:
      routes:
        - id: user-service
          uri: lb://user-service
          predicates:
            - Path=/user/**
        - id: message-service
          uri: lb://message-service
          predicates:
            - Path=/message/**
    nacos:
      discovery:
        server-addr: localhost:8848

如果URI以lb開頭,比如如上配置中的lb://user-service,Spring Cloud Gateway會用ReactiveLoadBalancerClientFilter 解析服務名為user-service的實例對應的實際host和端口,並做集羣負載均衡。

2.2 跳坑

官網説用lb://lakerservice形式即可,但是配置完成後,並未生效。這個官網沒有詳細説明,查資料也沒有,最後發現必須加入依賴:


<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
  </dependency>
2.3 簡易流程説明

Client ----> gateway ----> Ribbion負載均衡 取一個服務A ---->轉發到服務A

2.4 Ribbon説明

Spring Cloud Ribbon 在高版本移除了

三、用RouteRecordGlobalFilter記錄路由後的實際代理地址


@Slf4j
@Component
public class RouteRecordGlobalFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // RouteToRequestUrlFilter會把實際路由的URL通過該屬性保存
        URI proxyRequestUri = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);
        long start = System.currentTimeMillis();

        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            long end = System.currentTimeMillis();
            log.info("實際調用地址為:{},調用耗時為:{}ms", proxyRequestUri, (end - start));
        }));
    }

    @Override
    public int getOrder() {
        // 優先級設為最低,先讓RouteToRequestUrlFilter先調用
        return Ordered.LOWEST_PRECEDENCE;
    }
}

RouteRecordGlobalFilter 這個全局過濾器我們主要用來記錄路由後的實際代理地址,以及調用耗時。我們看下RouteToRequestUrlFilter的描述會發現實際路由地址會通過ServerWebExchange中名為ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR的屬性保存。

四、源碼

4.1 基於Ribbon的LoadBalancerClientFilter

gateway中的自動配置類GatewayLoadBalancerClientAutoConfiguration。


package org.springframework.cloud.gateway.config;

import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.gateway.config.conditional.ConditionalOnEnabledGlobalFilter;
import org.springframework.cloud.gateway.filter.LoadBalancerClientFilter;
import org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter;
import org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.DispatcherHandler;

@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnClass({LoadBalancerClient.class, RibbonAutoConfiguration.class, DispatcherHandler.class})
@AutoConfigureAfter({RibbonAutoConfiguration.class})
@EnableConfigurationProperties({LoadBalancerProperties.class})
public class GatewayLoadBalancerClientAutoConfiguration {
    public GatewayLoadBalancerClientAutoConfiguration() {
    }

    @Bean
    @ConditionalOnBean({LoadBalancerClient.class})
    @ConditionalOnMissingBean({LoadBalancerClientFilter.class, ReactiveLoadBalancerClientFilter.class})
    @ConditionalOnEnabledGlobalFilter
    public LoadBalancerClientFilter loadBalancerClientFilter(LoadBalancerClient client, LoadBalancerProperties properties) {
        return new LoadBalancerClientFilter(client, properties);
    }
}

Spring Cloud Gateway負載均衡_spring

2

該自動配置類需要在RibbonAutoConfiguration自動配置類之後執行,該類是spring-cloud-netflix-ribbon的自動配置類,因此需要引入下面的jar包依賴


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

使用默認的ribbon,則ribbon的配置如下


#=======================ribbon配置(使用netflix的Ribbon負載均衡)=======================
#關閉nacos集成ribbon,否則ribbon客户端會從nacos註冊中心獲取服務列表
ribbon.nacos.enabled=false
#配置serviceId為providerService的服務List
providerService.ribbon.listOfServers=http://192.168.10.1:8080,http://192.168.10.2:8080
#配置serviceId為providerService的服務負載均衡
#providerService.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RoundRobinRule
providerService.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.AvailabilityFilteringRule
4.2 Spring Cloud Loadbalancer

官網Spring Cloud LoadBalancer

Spring Cloud Load Balancer並不是一個獨立的項目,而是spring-cloud-commons其中的一個模塊,因此很多配置和類可以在spring-cloud-common中找到。gateway中的自動配置類GatewayReactiveLoadBalancerClientAutoConfiguration

Spring Cloud提供了自己的客户端負載均衡器抽象和實現。對於負載平衡機制,ReactiveLoadBalancer已添加了接口,併為其提供了基於Round-Robin和Random的實現。為了獲得實例以從反應式中進行選擇ServiceInstanceListSupplier 。當前,我們支持基於服務發現的實現,ServiceInstanceListSupplier 該實現使用類路徑中可用的發現客户端從服務發現中檢索可用實例。


@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnClass({LoadBalancerClient.class, ReactiveLoadBalancer.class, LoadBalancerAutoConfiguration.class, DispatcherHandler.class})
@AutoConfigureBefore({GatewayLoadBalancerClientAutoConfiguration.class})
@AutoConfigureAfter({LoadBalancerAutoConfiguration.class})
@EnableConfigurationProperties({LoadBalancerProperties.class})
public class GatewayReactiveLoadBalancerClientAutoConfiguration {
    public GatewayReactiveLoadBalancerClientAutoConfiguration() {
    }

    @Bean
    @ConditionalOnBean({LoadBalancerClientFactory.class})
    @ConditionalOnMissingBean({ReactiveLoadBalancerClientFilter.class})
    @Conditional({GatewayReactiveLoadBalancerClientAutoConfiguration.OnNoRibbonDefaultCondition.class})
    @ConditionalOnEnabledGlobalFilter
    public ReactiveLoadBalancerClientFilter gatewayLoadBalancerClientFilter(LoadBalancerClientFactory clientFactory, LoadBalancerProperties properties) {
        return new ReactiveLoadBalancerClientFilter(clientFactory, properties);
    }

    private static final class OnNoRibbonDefaultCondition extends AnyNestedCondition {
        private OnNoRibbonDefaultCondition() {
            super(ConfigurationPhase.REGISTER_BEAN);
        }

        @ConditionalOnMissingClass({"org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient"})
        static class RibbonLoadBalancerNotPresent {
            RibbonLoadBalancerNotPresent() {
            }
        }

        @ConditionalOnProperty(
            value = {"spring.cloud.loadbalancer.ribbon.enabled"},
            havingValue = "false"
        )
        static class RibbonNotEnabled {
            RibbonNotEnabled() {
            }
        }
    }
}