目录

一、序言

我们都知道 Spring Cloud Gateway 是一个基于 Spring BootSpring WebFluxProject Reactor 构建的高性能网关,旨在提供简单、高效的API路由。

Spring Cloud Gateway基于 Netty 运行,因此在传统Servlet容器中或者打成war包是不能正常运行的。

二、代码示例

这里我们注册中心选型的是 Nacos ,如果还没有安装Nacos,请参考:Nacos快速安装部署。

1、父工程spring-cloud-gateway-learning

	        spring-cloud-api-gateway
	        spring-cloud-user-service
	        spring-cloud-message-service
	

    
        8
        8
        UTF-8
        2.3.7.RELEASE
        Hoxton.SR12
        2.2.6.RELEASE
        3.12.0
    

    
        
            org.projectlombok
            lombok
        
        
            org.apache.commons
            commons-lang3
            ${commons.lang3.version}
        
    

    
        
            
                org.springframework.boot
                spring-boot-starter-parent
                ${spring.boot.version}
                pom
                import
            

            
                org.springframework.cloud
                spring-cloud-dependencies
                ${spring.cloud.version}
                pom
                import
            
            
                com.alibaba.cloud
                spring-cloud-alibaba-dependencies
                ${spring.cloud.alibaba.version}
                pom
                import
            
        
    

备注:具体Spring Cloud各版本说明请参考 Spring Cloud Alibaba版本说明

2、子工程spring-cloud-api-gateway

(1) pom.xml

        com.universe
        spring-cloud-gateway-learning
        1.0-SNAPSHOT



        
            org.springframework.cloud
            spring-cloud-starter-gateway
        
        
            com.alibaba.cloud
            spring-cloud-starter-alibaba-nacos-discovery
        

(2) 配置文件和代码示例

  1. bootstrap.yml
spring:
  application:
    name: api-gateway
server:
  port: 9000
  1. application.yml
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-serviceSpring Cloud Gateway 会用 ReactiveLoadBalancerClientFilter 解析服务名为 user-service 的实例对应的实际host和端口,并做集群负载均衡。

这项功能通过全局过滤器 ReactiveLoadBalancerClientFilter 实现,官网描述如下:

alt

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

	@Override
	public  Mono  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 的属性保存。

alt

关于 RouteToRequestUrlFilter 的部分源码如下:

@Override
	public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { 
		Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
		if (route == null) { 
			return chain.filter(exchange);
		}
		log.trace("RouteToRequestUrlFilter start");
		URI uri = exchange.getRequest().getURI();
		boolean encoded = containsEncodedParts(uri);
		URI routeUri = route.getUri();

		if (hasAnotherScheme(routeUri)) { 
			// this is a special url, save scheme to special attribute
			// replace routeUri with schemeSpecificPart
			exchange.getAttributes().put(GATEWAY_SCHEME_PREFIX_ATTR,
					routeUri.getScheme());
			routeUri = URI.create(routeUri.getSchemeSpecificPart());
		}

		if ("lb".equalsIgnoreCase(routeUri.getScheme()) && routeUri.getHost() == null) { 
			// Load balanced URIs should always have a host. If the host is null it is
			// most
			// likely because the host name was invalid (for example included an
			// underscore)
			throw new IllegalStateException("Invalid host: " + routeUri.toString());
		}

		URI mergedUrl = UriComponentsBuilder.fromUri(uri)
				// .uri(routeUri)
				.scheme(routeUri.getScheme()).host(routeUri.getHost())
				.port(routeUri.getPort()).build(encoded).toUri();
		exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, mergedUrl);
		return chain.filter(exchange);
	}

备注:更多关于全局过滤器的介绍请参考 Spring Cloud Gateway全局过滤器

3、子工程spring-cloud-user-service

(1) pom.xml

        com.universe
        spring-cloud-gateway-learning
        1.0-SNAPSHOT



        
            org.springframework.boot
            spring-boot-starter-web
        
        
            com.alibaba.cloud
            spring-cloud-starter-alibaba-nacos-discovery
        

(2) 配置文件

  1. bootstrap.yml
spring:
 application:
 name: user-service
server:
 servlet:
 context-path: /user

---
spring:
 profiles: user-service-master
server:
 port: 9091

---
spring:
 profiles: user-service-slave
server:
 port: 9092
  1. application.yml
spring:
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
  1. UserController
@RestController
public class  UserController  { 

	@GetMapping("/info")
	public Map getUserInfo() { 
	Random random = new Random();
		int waitTime = random.nextInt(1000);
		LockSupport.parkNanos(1000 * 1000 * waitTime);

		Map result = new HashMap<>();
		result.put("name", "Nick");
		result.put("age", 25);
		return result;
	}
}

4、子工程spring-cloud-message-service

(1) pom.xml

        com.universe
        spring-cloud-gateway-learning
        1.0-SNAPSHOT



        
            org.springframework.boot
            spring-boot-starter-web
        
        
            com.alibaba.cloud
            spring-cloud-starter-alibaba-nacos-discovery
        

(2) 配置文件和代码示例

  1. bootstrap.yml
spring:
 application:
 name: message-service
server:
 servlet:
 context-path: /message

---
spring:
 profiles: message-service-master
server:
 port: 9093

---
spring:
 profiles: message-service-slave
server:
 port: 9094
  1. application.yml
spring:
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
  1. MessageController
@RestController
public class  MessageController  { 

	@GetMapping("/info")
	public Map getMessageInfo() { 
	Random random = new Random();
		int waitTime = random.nextInt(1000);
		LockSupport.parkNanos(1000 * 1000 * waitTime);

		Map result = new HashMap<>();
		result.put("id", 1);
		result.put("title", "我爱中国");
		return result;
	}
}

三、测试结果

分别启动api-gateway、指定概要文件启动两个user-service服务实例、和两个message-service服务实例,查看Nacos控制台。

alt

可以看到,api-gateway启动了一个服务实例,user-service和message-service都启动了两个服务实例。

备注:IDEA运行时指定 Active Profiles 即可。

1、集群负载均衡测试

连续访问 http://localhost:9000/user/info ,可以看到user-service集群服务实例被轮询调用。

alt

2、服务路由测试

分别访问 http://localhost:9000/user/infohttp://localhost:9000/message/info ,我们可以看到基于路径匹配的服务路由分发是成功的。

alt