一、序言
我們都知道Spring Cloud Gateway
是一個(gè)基于Spring Boot、Spring WebFlux、Project Reactor構(gòu)建的高性能網(wǎng)關(guān)驰弄,旨在提供簡(jiǎn)單、高效的API路由速客。
Spring Cloud Gateway基于Netty
運(yùn)行戚篙,因此在傳統(tǒng)Servlet容器中或者打成war包是不能正常運(yùn)行的。
二溺职、代碼示例
這里我們注冊(cè)中心選型的是Nacos
已球,如果還沒(méi)有安裝Nacos,請(qǐng)參考: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==各版本說(shuō)明請(qǐng)參考Spring Cloud Alibaba版本說(shuō)明忆某。
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會(huì)用ReactiveLoadBalancerClientFilter
解析服務(wù)名為user-service
的實(shí)例對(duì)應(yīng)的實(shí)際host和端口癞埠,并做集群負(fù)載均衡。
這項(xiàng)功能通過(guò)全局過(guò)濾器ReactiveLoadBalancerClientFilter
實(shí)現(xiàn)聋呢,官網(wǎng)描述如下:
[圖片上傳失敗...(image-b13395-1655546589820)]
- RouteRecordGlobalFilter
@Slf4j
@Component
public class RouteRecordGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// RouteToRequestUrlFilter會(huì)把實(shí)際路由的URL通過(guò)該屬性保存
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("實(shí)際調(diào)用地址為:{}苗踪,調(diào)用耗時(shí)為:{}ms", proxyRequestUri, (end - start));
}));
}
@Override
public int getOrder() {
// 優(yōu)先級(jí)設(shè)為最低,先讓RouteToRequestUrlFilter先調(diào)用
return Ordered.LOWEST_PRECEDENCE;
}
}
RouteRecordGlobalFilter 這個(gè)全局過(guò)濾器我們主要用來(lái)記錄路由后的實(shí)際代理地址削锰,以及調(diào)用耗時(shí)通铲。
我們看下RouteToRequestUrlFilter
的描述會(huì)發(fā)現(xiàn)實(shí)際路由地址會(huì)通過(guò)ServerWebExchange
中名為ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR
的屬性保存。
[圖片上傳失敗...(image-a33940-1655546589820)]
關(guān)于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);
}
備注:更多關(guān)于全局過(guò)濾器的介紹請(qǐng)參考 Spring Cloud Gateway全局過(guò)濾器器贩。
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) 配置文件和代碼示例
- 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
- 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", "我愛(ài)中國(guó)");
return result;
}
}
三蛹稍、測(cè)試結(jié)果
分別啟動(dòng)api-gateway吧黄、指定概要文件啟動(dòng)兩個(gè)user-service服務(wù)實(shí)例、和兩個(gè)message-service服務(wù)實(shí)例唆姐,查看Nacos控制臺(tái)拗慨。
[圖片上傳失敗...(image-34bdc6-1655546589820)]
可以看到,api-gateway啟動(dòng)了一個(gè)服務(wù)實(shí)例,user-service和message-service都啟動(dòng)了兩個(gè)服務(wù)實(shí)例赵抢。
備注:IDEA運(yùn)行時(shí)指定
Active Profiles
即可剧蹂。
1、集群負(fù)載均衡測(cè)試
連續(xù)訪問(wèn)http://localhost:9000/user/info
昌讲,可以看到user-service集群服務(wù)實(shí)例被輪詢調(diào)用国夜。
2、服務(wù)路由測(cè)試
分別訪問(wèn) http://localhost:9000/user/info
短绸、http://localhost:9000/message/info
车吹,我們可以看到基于路徑匹配的服務(wù)路由分發(fā)是成功的。