今天是:
带着程序的旅程,每一行代码都是你前进的一步,每个错误都是你成长的机会,最终,你将抵达你的目的地。
title

spring-cloud-gateway

1.Spring Cloud Gateway 是什么

Spring Cloud Gateway是 Spring Cloud 的一个全新项目,该项目是基于
Spring 5.0. Spring Boot 2.0 和Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效统一的 AP! 路由管理方式;
为了提升网关的性能,

Spring Cloud Gateway 底层使用了高性能的通信框架Netty;

Spring Cloud Gateway 的目标,不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。

2.名字解释

  1. 路由(Route):任何一个来自于客户端的请求都会经过路由,然后到对应的微服务中。每个路由会有一个唯一的 1口 和对应的目的 URL。同时包含若干个断言和过滤器。
  2. 断言 (Predicate):当客户端通过 Http Request 请求进入 Spring Cloud Gateway 的时候,断言会根据配置的路由规则対 Htto Request 清求迸行断言匹配。
  3. 过滤器( Filter:简单来说就是对流经的请求进行过滤,,或者说对其进行获取以及修改的操作。注意过滤器的功能是双向的,也就是对请求和响应都会进行修改处

3.gateway的工作原理

客户端发送请求到 Spring Cloud Gateway。如果网关处理映射(Gateway Handler Mapping)确定请求匹配了一个路由(route),则将请求发送到网关 Web 处理程序(Gateway Web Handler)。该处理程序将请求通过一个与请求相关的过滤器链处理。过滤器之所以被分成前后两部分,是因为它们可以在代理请求发送前后运行逻辑。首先执行所有的“pre”过滤器逻辑,然后进行代理请求。代理请求发送后,将运行“post”过滤器逻辑。

Spring Cloud Gateway Diagram

 

已Tomcat 为例,请求到达时会调用 HttpWebHandlerAdapter的handle方法,之后在DispatcherHandler的hander方法通过handlerMappings找到对应处理器,RoutePredicateHandlerMapping会找到对应的处理器FilteringWebHandler,即包含所有配置的过滤器的处理器,然后请求经过一系列的过滤器。

4.路由配置

yml配置文件中配置路由

下面的路由定义中,如果方法以test开头则会路由到https://www.zlennon.com,访问以chatgpt开头则会路由到chatgpt-model-service 服务,访问whitelist则会路由到指定的地址,并且先经过特定的filter whitelistFilter

spring:
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
      routes:
      - id: path_route
        uri: https://www.zlennon.com
        predicates:
        - Path=/test/*

 RouteLocatorBuilder 构建路由

    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                .route("path_route", r -> r.path("/test/*")
                        .uri("http://127.0.0.1:5006/okhttp/asyncGetRequest"))

                .route("demoi18n", r -> r.path("/demoi18n/**")
                        //.uri("lb://chatgpt-model-service"))
                        .uri("http://127.0.0.1:7888"))
                .route("whitelist_route", r -> r
                        .path("/whitelist")
                        .filters(f -> f.filter(whitelistFilter))
                        .uri("http://whitelist.com"))
                .build();
    }

 

 

4.1动态路由

动态路由是针对静态路由而说的,一般我们通过java代码或配置文件设置的路由规则服务启动后就不能变了,除非修改后重启服务。

4.1.1 consul动态路由

配置监听配置中心路由变化,通过watch属性可监听配置改变

spring:
  cloud:
    consul:
      enabled: true
      host: localhost
      port: 8500
      discovery:
        enabled: true
        register: true
        heartbeat:
          enabled: true
        instance-id: ${spring.application.name}-${server.port}
        health-check-path: /actuator/health
        health-check-interval: 10s
        prefer-ip-address: true
      config:
        enabled: true #开启配置中心,默认是true
        default-context: gateway #应用文件夹,默认值 application
        profile-separator: ',' # 环境分隔符,默认值 ","
        format: yaml #配置格式,默认 key-value,其他可选:yaml/files/properties
        data-key: route #配置 key 值,value 对应整个配置文件
        #以上配置后,我们的配置文件在consul中的完整的key为 springcloud/application,dev/route
        prefixes: springcloud
        watch:
          enabled: true #启用配置自动刷新
          delay: 1000 # 刷新频率,单位:毫秒
          wait-time: 100
4.1.2 nacos 动态路由
通过RouteDefinitionWriter更新删除路由定义

package com.zlennon.gateway.dynamic;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;

import java.util.List;

/**
 */
@Service
@Slf4j
public class DynamicRouteService implements ApplicationEventPublisherAware {

	private final RouteDefinitionWriter routeDefinitionWriter;

	private ApplicationEventPublisher publisher;

	public DynamicRouteService(RouteDefinitionWriter routeDefinitionWriter) {
		this.routeDefinitionWriter = routeDefinitionWriter;
	}

	@Override
	public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
		this.publisher = applicationEventPublisher;
	}


	public String addList(List<RouteDefinition> routeDefinitions) {
		routeDefinitions.forEach(this::save);
		return "add done";
	}

	/**
	 * 增加路由
	 */
	public String save(RouteDefinition definition) {
		try {
			routeDefinitionWriter.save(Mono.just(definition)).subscribe();
			this.publisher.publishEvent(new RefreshRoutesEvent(this));
			return "save success";
		} catch (Exception e) {
			e.printStackTrace();
			return "save failure";
		}
	}

	/**
	 * 更新路由
	 */
	public String update(RouteDefinition definition) {
		try {
			this.routeDefinitionWriter.delete(Mono.just(definition.getId()));
			this.routeDefinitionWriter.save(Mono.just(definition)).subscribe();
			this.publisher.publishEvent(new RefreshRoutesEvent(this));
			log.info("Loaded Route: {}", definition.getId());
			return "update success";
		} catch (Exception e) {
			e.printStackTrace();
			return "update failure";
		}
	}

	/**
	 * 更新路由
	 */
	public String updateList(List<RouteDefinition> routeDefinitions) {
		routeDefinitions.forEach(this::update);
		return "update done";
	}

	/**
	 * 删除路由
	 */
	public String delete(String id) {
		try {
			this.routeDefinitionWriter.delete(Mono.just(id));
			return "delete success";
		} catch (Exception e) {
			e.printStackTrace();
			return "delete failure";
		}
	}


}
监听配置文件变更

package com.zlennon.gateway.dynamic;

import cn.hutool.core.text.CharSequenceUtil;
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.fastjson2.JSON;
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Properties;
import java.util.concurrent.Executor;

@Order
@Component
@Slf4j
@RefreshScope
public class DynamicRouteServiceListener {

    @Autowired
    private DynamicRouteService dynamicRouteService;


    @PostConstruct
    public void init() {
        try {
            Properties properties = new Properties();
            properties.put(PropertyKeyConst.SERVER_ADDR, "localhost:8848");
            properties.put(PropertyKeyConst.NAMESPACE, "nacos-routes");
            ConfigService configService = NacosFactory.createConfigService(properties);
            String dataId = "gateway-nacos-routes.json";
            String group = "nacos-routes";
            log.info("gateway init,dataId:{},group:{}", dataId, group);
            configService.addListener(dataId, group, new Listener() {
                @Override
                public void receiveConfigInfo(String configInfo) {
                    nachosListener(configInfo);
                }

                @Override
                public Executor getExecutor() {
                    return null;
                }
            });
            String configInfo = configService.getConfig(dataId, group, 5000);
            if (CharSequenceUtil.isNotBlank(configInfo)) {
                log.info("recevie config info:\r\n{}", configInfo);
                List<RouteDefinition> routeDefinitions = JSON.parseArray(configInfo, RouteDefinition.class);
                dynamicRouteService.addList(routeDefinitions);
                log.info("init finished");
            }
        } catch (NacosException nacosException) {
            log.error("init error!", nacosException);
        }
    }

    private void nachosListener(String configInfo) {
        if (CharSequenceUtil.isNotBlank(configInfo)) {
            try {
                log.info("gateway update config:\r\n{}", configInfo);
                List<RouteDefinition> routeDefinitions = JSON.parseArray(configInfo, RouteDefinition.class);
                dynamicRouteService.updateList(routeDefinitions);
            } catch (Exception e) {
                log.error("parse error", e);
            }
        } else {
            log.warn("no config info");
        }
    }


}

 

5. gateway初始化

springboot 初始化见 springboot-init

springboot finishBeanFactoryInitialization 创建bean时 加载GatewayAutoConfiguration 中创建对应的RouteDefinitionRouteLocator。

finishRefresh 后发送事件 调用RouteRefreshListener的onApplicationEvent方法,进而调用CachingRouteLocator.onApplicationEvent ,将路由信息放入map  this.cache.put("routes", signals);

GatewayAutoConfiguration

GatewayAutoConfiguration是gateway自动配置的关键类,几个注解如下:

@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnProperty(
    name = {"spring.cloud.gateway.enabled"}, 
    matchIfMissing = true
)
@EnableConfigurationProperties
@AutoConfigureBefore({HttpHandlerAutoConfiguration.class, WebFluxAutoConfiguration.class})//WebFlux相关的配置,before表是先于GatewayAutoConfiguration配置
@AutoConfigureAfter({GatewayReactiveLoadBalancerClientAutoConfiguration.class, GatewayClassPathWarningAutoConfiguration.class})//在负载均衡的自动配置之后配置
@ConditionalOnClass({DispatcherHandler.class})// 类路径下必须有DispatcherHandler,即需要引入spring webflux依赖

WebFluxAutoConfiguration中会配置相应的react类型的web服务Tomcat,Jetty,Undertow和Netty

GatewayClassPathWarningAutoConfiguration主要用来验证是否依赖了webflux 

完了之后就根据@Bean 实例化对应的类GatewayProperties,RouteDefinitionLocator,GlobalFilter等

 

 参考:Spring Cloud Gateway

分享到:

专栏

类型标签

网站访问总量