目录
一、序言
二、代码示例
1、父工程spring-cloud-gateway-learning
2、子工程spring-cloud-api-gateway
(1) pom.xml
(2) 配置文件和代码示例
3、子工程spring-cloud-user-service
(1) pom.xml
(2) 配置文件
4、子工程spring-cloud-message-service
(1) pom.xml
(2) 配置文件和代码示例
三、测试结果
1、集群负载均衡测试
2、服务路由测试
一、序言
我们都知道Spring Cloud Gateway
是一个基于Spring Boot、Spring WebFlux、Project Reactor构建的高性能网关,旨在提供简单、高效的API路由。
Spring Cloud Gateway基于Netty
运行,因此在传统Servlet容器中或者打成war包是不能正常运行的。
二、代码示例
这里我们注册中心选型的是Nacos
,如果还没有安装Nacos,请参考:Nacos快速安装部署。
1、父工程spring-cloud-gateway-learning
<modules> <module>spring-cloud-api-gateway</module> <module>spring-cloud-user-service</module> <module>spring-cloud-message-service</module> </modules> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <spring.boot.version>2.3.7.RELEASE</spring.boot.version> <spring.cloud.version>Hoxton.SR12</spring.cloud.version> <spring.cloud.alibaba.version>2.2.6.RELEASE</spring.cloud.alibaba.version> <commons.lang3.version>3.12.0</commons.lang3.version> </properties> <dependencies> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>${commons.lang3.version}</version> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>${spring.boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring.cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>${spring.cloud.alibaba.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
备注:具体Spring Cloud各版本说明请参考Spring Cloud Alibaba版本说明。
2、子工程spring-cloud-api-gateway
(1) pom.xml
<parent> <groupId>com.universe</groupId> <artifactId>spring-cloud-gateway-learning</artifactId> <version>1.0-SNAPSHOT</version> </parent> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> </dependencies>
(2) 配置文件和代码示例
- bootstrap.yml
spring: application: name: api-gateway server: port: 9000
- 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-service,Spring Cloud Gateway会用ReactiveLoadBalancerClientFilter 解析服务名为user-service的实例对应的实际host和端口,并做集群负载均衡。
这项功能通过全局过滤器ReactiveLoadBalancerClientFilter 实现,官网描述如下:
- 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的属性保存。
关于RouteToRequestUrlFilter
的部分源码如下:
@Override public Mono<Void> 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
<parent> <groupId>com.universe</groupId> <artifactId>spring-cloud-gateway-learning</artifactId> <version>1.0-SNAPSHOT</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> </dependencies>
(2) 配置文件
- 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
- application.yml
spring: cloud: nacos: discovery: server-addr: localhost:8848
- UserController
@RestController public class UserController { @GetMapping("/info") public Map<String, Object> getUserInfo() { Random random = new Random(); int waitTime = random.nextInt(1000); LockSupport.parkNanos(1000 * 1000 * waitTime); Map<String, Object> result = new HashMap<>(); result.put("name", "Nick"); result.put("age", 25); return result; } }
4、子工程spring-cloud-message-service
(1) pom.xml
<parent> <groupId>com.universe</groupId> <artifactId>spring-cloud-gateway-learning</artifactId> <version>1.0-SNAPSHOT</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> </dependencies>
(2) 配置文件和代码示例
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
- application.yml
spring: cloud: nacos: discovery: server-addr: localhost:8848
- MessageController
@RestController public class MessageController { @GetMapping("/info") public Map<String, Object> getMessageInfo() { Random random = new Random(); int waitTime = random.nextInt(1000); LockSupport.parkNanos(1000 * 1000 * waitTime); Map<String, Object> result = new HashMap<>(); result.put("id", 1); result.put("title", "我爱中国"); return result; } }
三、测试结果
分别启动api-gateway、指定概要文件启动两个user-service服务实例、和两个message-service服务实例,查看Nacos控制台。
可以看到,api-gateway启动了一个服务实例,user-service和message-service都启动了两个服务实例。
备注:IDEA运行时指定Active Profiles
即可。
1、集群负载均衡测试
连续访问http://localhost:9000/user/info
,可以看到user-service集群服务实例被轮询调用。
2、服务路由测试
分别访问 http://localhost:9000/user/info
、http://localhost:9000/message/info
,我们可以看到基于路径匹配的服务路由分发是成功的。