gateway使用及負載均衡原理


引入

pom.xml文件添加依賴,如果引入了gateway的依賴,但是不想使用,可以在application.yml文件中設置spring.cloud.gateway.enabled=false

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

Gateway組成

  • Route

    網關的基礎,由一個唯一ID、一個目標URL(請求的服務端具體地址)、一組斷言(判斷是否會進行路由)、一組過濾器。

    只有斷言全部返回true,才會進行路由。

  • Predicate

    斷言,使用的是Java8函數,參數類型為ServlerWebExchange,可以匹配任何的http請求,並且可以拿到請求中的請求頭、參數等信息。

  • Filter

    使用特定工廠構建的GatewayFilter的實例,可以在此Filter中修改請求和響應信息。

Geyeway工作原理

  1. 客戶端發送請求到Gateway
  2. Gateway Handler Mapping判斷請求是否匹配某個路由
  3. 發送請求到Gateway Web Handler,執行該請求的過濾器。過濾器可以在請求之前和之后執行不同邏輯。
  4. 當所有的預(pre)過濾請求執行完后,創建代理請求,創建好了代理請求后,才執行post請求

核心GlobalFilter

ForwardRoutingFilter

Order: Integer.MAX_VALUE
請求轉發過濾器。
從exchange對象中獲取gatewayRequestUrl,如果這個url中有forward的scheme,則使用Spring的DispatcherHandler 進行請求轉發。

LoadBalancerClientFilter

Order: 10100
負載均衡過濾器。(核心)
從exchange對象中獲取gatewayRequestUrl、gatewaySchemePrefix,如果url為空或者url的schema不是lb並且gatewaySchemePrefix前綴不是lb,則進入下個過濾器;否則去獲取真正的url。
  • 過濾器完整內容
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    	// 拿到請求url
		URI url = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);
    	// 拿到匹配的路由中url的前綴
		String schemePrefix = exchange.getAttribute(GATEWAY_SCHEME_PREFIX_ATTR);
    	// 如果url為null或者url的schema不是lb並且請求Route中配置的url不是lb,進入下個過濾器
		if (url == null
				|| (!"lb".equals(url.getScheme()) && !"lb".equals(schemePrefix))) {
			return chain.filter(exchange);
		}
		// 保留原始的Url,就是將原始的url放入exchange對象的gatewayOriginalRequestUrl參數值中
		addOriginalRequestUrl(exchange, url);
    	// 獲取真正的請求url並組裝成ServiceInstance對象
		final ServiceInstance instance = choose(exchange);

		if (instance == null) {
			throw NotFoundException.create(properties.isUse404(),
					"Unable to find instance for " + url.getHost());
		}

		URI uri = exchange.getRequest().getURI();

		// if the `lb:<scheme>` mechanism was used, use `<scheme>` as the default,
		// if the loadbalancer doesn't provide one.
		String overrideScheme = instance.isSecure() ? "https" : "http";
		if (schemePrefix != null) {
			overrideScheme = url.getScheme();
		}

		URI requestUrl = loadBalancer.reconstructURI(
				new DelegatingServiceInstance(instance, overrideScheme), uri);

		if (log.isTraceEnabled()) {
			log.trace("LoadBalancerClientFilter url chosen: " + requestUrl);
		}

		exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl);
		return chain.filter(exchange);
	}
  • 使用Ribbon負載時,choose(exchange)調用邏輯

    • 拿到對應的服務名,因為是lb模式,對應的host即服務名
    protected ServiceInstance choose(ServerWebExchange exchange) {
       return loadBalancer.choose(
             ((URI) exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR)).getHost());
    }
    
    • 調用RibbonLoadBalancerClient的choose()方法
    public ServiceInstance choose(String serviceId, Object hint) {
       // getLoadBalancer(serviceId)根據服務id獲取服務列表
       // getServer(ILoadBalancer loadBalancer, Object hint) 根據負載均衡策略選擇服務
       Server server = getServer(getLoadBalancer(serviceId), hint);
       if (server == null) {
          return null;
       }
       // 實例化RibbonServer對象
       return new RibbonServer(serviceId, server, isSecure(server, serviceId),
             serverIntrospector(serviceId).getMetadata(server));
    }
    
    • getLoadBalancer(serviceId)根據服務id獲取負載均衡器,調用SpringClientFactory的getInstance()方法
    public <C> C getInstance(String name, Class<C> type) {
    	C instance = super.getInstance(name, type);
    	if (instance != null) {
    		return instance;
    	}
    	IClientConfig config = getInstance(name, IClientConfig.class);
    	return instantiateWithConfig(getContext(name), type, config);
    }
    
    • 先從父類NamedContextFactory獲取緩存容器,如果沒有,則創建並放入緩存
    // 緩存的內置容器
    private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();
    // 獲取應用上下文的方法
    protected AnnotationConfigApplicationContext getContext(String name) {
       // 如果緩存中沒有該應用,則創建應用
       // 雙重檢查鎖,保證線程安全
       if (!this.contexts.containsKey(name)) {
          synchronized (this.contexts) {
             if (!this.contexts.containsKey(name)) {
                this.contexts.put(name, createContext(name));
             }
          }
       }
       // 返回應用實例
       return this.contexts.get(name);
    }
    
    • 創建容器
    // 所有帶有@RibbonClients 的配置類,key為類名全路徑
    private Map<String, C> configurations = new ConcurrentHashMap<>();
    
    // 根據注解配置創建內置容器
    protected AnnotationConfigApplicationContext createContext(String name) {
       AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
       /* 加載配置start */
       // 如果有該服務需要加載的配置類,將自定義的配置注冊到容器中
       if (this.configurations.containsKey(name)) {
          for (Class<?> configuration : this.configurations.get(name)
                .getConfiguration()) {
             context.register(configuration);
          }
       }
       // 如果配置的key是以default.開頭,則默認注冊的容器中
       // RibbonNacosAutoConfiguration和RibbonAutoConfiguration都是以defaule.開頭的
       for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
          if (entry.getKey().startsWith("default.")) {
             for (Class<?> configuration : entry.getValue().getConfiguration()) {
                context.register(configuration);
             }
          }
       }
       // 注冊默認的配置,即RibbonClientConfiguration
       context.register(PropertyPlaceholderAutoConfiguration.class,
             this.defaultConfigType);
       // 添加環境變量
       context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
             this.propertySourceName,
             Collections.<String, Object>singletonMap(this.propertyName, name)));
        /* 加載配置end */
       if (this.parent != null) {
          // Uses Environment from parent as well as beans
          context.setParent(this.parent);
          // jdk11 issue
          // https://github.com/spring-cloud/spring-cloud-netflix/issues/3101
          context.setClassLoader(this.parent.getClassLoader());
       }
       // 設置容器名稱 
       context.setDisplayName(generateDisplayName(name));
       // 刷新容器
       context.refresh();
       return context;
    }
    
  • 從容器中獲取服務getServer():根據負載均衡算法獲取服務,Ribbon默認加載的ZoneAvoidanceRule負載策略

    public Server choose(Object key) {
        // 當前服務對應的負載均衡器,包含NacosRibbonClientConfiguration注入的Server列表
        ILoadBalancer lb = getLoadBalancer();
        // 獲取服務,默認輪詢策略
        Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key);
        if (server.isPresent()) {
            return server.get();
        } else {
            return null;
        }       
    }
    
    // 輪詢方法,參數為可用服務的數量
    private int incrementAndGetModulo(int modulo) {
        for (;;) {
            int current = nextIndex.get();
            int next = (current + 1) % modulo;
            if (nextIndex.compareAndSet(current, next) && current < modulo)
                return current;
        }
    }
    

服務來源

首先看一個接口ServerListUpdater自己核心方法

有個內部接口UpdayeAction,真正執行服務列表更新的接口。

public interface ServerListUpdater {

    /**
     * an interface for the updateAction that actually executes a server list update
     */
    public interface UpdateAction {
        void doUpdate();
    }


    /**
     * start the serverList updater with the given update action
     * This call should be idempotent.
     *
     * @param updateAction
     */
    void start(UpdateAction updateAction);
}

再看看ServerListUpdater接口的實現類PollingServerListUpdater,只貼核心方法start()。

參數為UpdateAction 對象,定義了一個線程類,線程里調用updateAction.doUpdate()方法。

啟動一個定時任務線程池,默認每隔30S執行一次,也就是說每隔30從注冊中心獲取一次最新的服務列表。

public class PollingServerListUpdater implements ServerListUpdater {
    @Override
    public synchronized void start(final UpdateAction updateAction) {
        if (isActive.compareAndSet(false, true)) {
            final Runnable wrapperRunnable = new Runnable() {
                @Override
                public void run() {
                    if (!isActive.get()) {
                        if (scheduledFuture != null) {
                            scheduledFuture.cancel(true);
                        }
                        return;
                    }
                    try {
                        updateAction.doUpdate();
                        lastUpdated = System.currentTimeMillis();
                    } catch (Exception e) {
                        logger.warn("Failed one update cycle", e);
                    }
                }
            };

            scheduledFuture = getRefreshExecutor().scheduleWithFixedDelay(
                    wrapperRunnable,
                    initialDelayMs,
                    refreshIntervalMs,
                    TimeUnit.MILLISECONDS
            );
        } else {
            logger.info("Already active, no-op");
        }
    }
}

NettyWriteResponseFilter

Order:-1
在其它過濾器執行完后執行,並將服務端響應的結果寫回客戶端,如果需要重新處理響應結果,則新的過濾器必須在此過濾器之后執行,也就是order比-1小。

GatewayMetricsFilter

Order:0
gateway性能監控核心過濾器,用於采集請求數據,主要包含
routeId: 路由id
routeUri: 路由的url
outcome: 
status: 請求狀態
httpStatusCode: 響應狀態碼
httpMethod: 請求方法

用戶自定義過濾器

全局過濾器

實現GlobalFilter, Ordered,並重寫getOrder()和filter()方法

public class CustomGlobalFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("custom global filter");
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return -1;
    }
}

Route過濾器

Route過濾器命名必須以GatewayFilterFactory結尾,並且在application.yml文件中只需配置前綴

public class PreGatewayFilterFactory extends AbstractGatewayFilterFactory<PreGatewayFilterFactory.Config> {

    public PreGatewayFilterFactory() {
        super(Config.class);
    }

    @Override
    public GatewayFilter apply(Config config) {
        // grab configuration from Config object
        return (exchange, chain) -> {
            //If you want to build a "pre" filter you need to manipulate the
            //request before calling chain.filter
            ServerHttpRequest.Builder builder = exchange.getRequest().mutate();
            //use builder to manipulate the request
            return chain.filter(exchange.mutate().request(builder.build()).build());
        };
    }

    public static class Config {
        //Put the configuration properties for your filter here
    }

}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM