我们都知道Spring Cloud Gateway
是一个基于Spring Boot、Spring WebFlux、Project Reactor构建的高性能网关,旨在提供简单、高效的API路由。
Spring Cloud Gateway基于Netty
运行,因此在传统Servlet容器中或者打成war包是不能正常运行的。
这里我们注册中心选型的是Nacos
,如果还没有安装Nacos,请参考:Nacos快速安装部署。
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版本说明。
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
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 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 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全局过滤器。
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
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 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;
}
}
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
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 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控制台。
可以看到,api-gateway启动了一个服务实例,user-service和message-service都启动了两个服务实例。
备注:IDEA运行时指定
Active Profiles
即可。
连续访问http://localhost:9000/user/info
,可以看到user-service集群服务实例被轮询调用。
分别访问 http://localhost:9000/user/info
、http://localhost:9000/message/info
,我们可以看到基于路径匹配的服务路由分发是成功的。
先自我介绍一下,小编13年上师交大毕业,曾经在小公司待过,去过华为OPPO等大厂,18年进入阿里,直到现在。深知大多数初中级java工程师,想要升技能,往往是需要自己摸索成长或是报班学习,但对于培训机构动则近万元的学费,着实压力不小。自己不成体系的自学效率很低又漫长,而且容易碰到天花板技术停止不前。因此我收集了一份《java开发全套学习资料》送给大家,初衷也很简单,就是希望帮助到想自学又不知道该从何学起的朋友,同时减轻大家的负担。添加下方名片,即可获取全套学习资料哦