服务网关是整个微服务架构中对外开放的统一入口,所有的客户端都通过统一的网关使用微服务,服务网关起到了对外隔离内部系统的作用,是微服务的一个标配组件。
服务网关有以下特点:
spring cloud gateway是网关服务的一个具体实现,基于异步非阻塞模型开发,性能上有很大的提升,提供了以下几个特点:
基于时间。比如在某个时间之前,某个时间之后,某两个时间之间
- Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]
- After=2017-01-20T17:42:47.789-07:00[America/Denver]
- Before=2017-01-20T17:42:47.789-07:00[America/Denver]
基于Cookie中的某一个key的值,支持正则表达式
- Cookie=chocolate, ch.p # 值支持正则表达式
基于请求头中的某一个key的值,支持正则表达式
- Header=X-Request-Id, \d+
基于域名
- Host=**.somehost.org,**.anotherhost.org
基于请求方式,GET请求或者POST请求或者其他
- Method=GET,POST
基于请求路劲,路径中包含某一个单词
- Path=/red/{segment},/blue/{segment}
基于IP地址
- RemoteAddr=192.168.1.1/24
必须被Spring管理
类名必须使用RoutePredicateFactory结尾
必须定义一个静态内部类,并定义一些属性来接受参数。
示例:
基于自定义断言工厂实现:
- CheckName=test
@Component
class CheckNameRoutePredicateFactory extends AbstractRoutePredicateFactory<CheckNameRoutePredicateFactory.Config> {
public CheckNameRoutePredicateFactory() {
super(CheckNameRoutePredicateFactory.Config.class);
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("name"); // config中定义的属性,需要从这里进行绑定
}
@Override
public Predicate<ServerWebExchange> apply(final Config config) {
return new GatewayPredicate() {
@Override
public boolean test(ServerWebExchange exchange) {
if (config.getName().equals("test")) {
return true;
}
return false;
}
};
}
@Validated
public static class Config {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
主要是请求的时候处理请求,响应的时候处理响应。具体参照官方文档。
必须被Spring管理
类名必须使用GatewayFilterFactory结尾
必须定义一个静态内部类,并定义一些属性来接受参数。
示例:
基于自定义过滤器工厂实现:
- CheckName=age,8
@Component
class CheckAgeGatewayFilterFactory extends AbstractGatewayFilterFactory<CheckAgeGatewayFilterFactory.Config> {
public CheckAgeGatewayFilterFactory() {
super(CheckAgeGatewayFilterFactory.Config.class);
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("age");
}
@Override
public GatewayFilter apply(CheckAgeGatewayFilterFactory.Config config) {
return this.apply(config.age);
}
public GatewayFilter apply(int age) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
int age1 = Integer.parseInt(Objects.requireNonNull(exchange.getRequest().getQueryParams().getFirst("age")));
int age1 = Integer.parseInt(Objects.requireNonNull(exchange.getRequest().getQueryParams().getFirst("age")));
if (age1 > 18 && age == age1) {
return chain.filter(exchange);
} else {
exchange.getResponse().setStatusCode(HttpStatus.valueOf(404));
return exchange.getResponse().setComplete();
}
}
};
}
public static class Config {
int age;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
}
@Component
class LogFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("全局过滤器");
return chain.filter(exchange);
}
}
spring:
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]':
allowedOrigins: "https://docs.spring.io"
allowedMethods:
- GET
@Component
@Configuration
class CorsWebConfig {
@Bean
public CorsWebFilter corsWebFilter() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowedOrigins(Collections.singletonList("*"));
corsConfiguration.setAllowedMethods(Collections.singletonList("*"));
corsConfiguration.setAllowedHeaders(Collections.singletonList("*"));
UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
corsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
return new CorsWebFilter(corsConfigurationSource);
}
}
spring:
application:
name: cloud-reading-gateway # 网关应用名称
cloud:
gateway:
discovery:
locator:
enabled: true # 开启网关的自动发现功能
lowerCaseServiceId: true
routes:
- id: cloud-reading-home-rpc # 路由的ID,名字不重复即可。
uri: lb://cloud-reading-home # 需要转发的目标服务器地址,lb://cloud-reading-home的写法需要和注册中心结合使用,代表在注册中心上找cloud-reading-home服务,lb://代表使用gateway默认的负载均衡策略向cloud-reading-home服务转发请求。
predicates:
- Path=/cloud-reading-home/** # 请求路劲中有cloud-reading-home的时候,就将请求转发到cloud-reading-home服务上
filters:
- StripPrefix=1 # 将请求路劲中的test-serv剪裁掉,假如:http://localhost:8090/cloud-reading-home/index,经过这里处理后就会变成http://localhost:port/index
- id: cloud-reading-book-rpc
uri: lb://cloud-reading-book
predicates:
- Path=/cloud-reading-book/** # 请求路劲中有cloud-reading-book的时候,就将请求转发到cloud-reading-book服务上
filters:
- StripPrefix=1 # 将请求路劲中的test-serv剪裁掉,假如:http://localhost:8090/cloud-reading-book/index,经过这里处理后就会变成http://localhost:port/index
- id: cloud-reading-accoud-rpc
uri: lb://cloud-reading-accoud
predicates:
- Path=/cloud-reading-accoud/** # 请求路劲中有cloud-reading-accoud的时候,就将请求转发到cloud-reading-accoud服务上
filters:
- StripPrefix=1 # 将请求路劲中的test-serv剪裁掉,假如:http://localhost:8090/cloud-reading-accoud/index,经过这里处理后就会变成http://localhost:port/index
# 跨域处理
globalcors:
cors-configurations:
'[/**]': # 允许跨域访问的资源
allowedOrigins: "*" # 允许的来源
allowedMethods:
- GET
- POST
GatewayAutoConfiguration.java:网关的核心配置
// 查找匹配到路由Route并进行处理
@Bean
public RoutePredicateHandlerMapping routePredicateHandlerMapping(FilteringWebHandler webHandler, RouteLocator routeLocator, GlobalCorsProperties globalCorsProperties, Environment environment) {
return new RoutePredicateHandlerMapping(webHandler, routeLocator, globalCorsProperties, environment);
}
// 加载网关配置,初始化的时候,将配置文件中的信息保存在GatewayProperties中。
@Bean
public GatewayProperties gatewayProperties() {
return new GatewayProperties();
}
// 创建一个根据RouteDefinition抓换的路由定位器
@Bean
public RouteLocator routeDefinitionRouteLocator(GatewayProperties properties, List<GatewayFilterFactory> gatewayFilters, List<RoutePredicateFactory> predicates, RouteDefinitionLocator routeDefinitionLocator, ConfigurationService configurationService) {
return new RouteDefinitionRouteLocator(routeDefinitionLocator, predicates, gatewayFilters, properties, configurationService);
}
GatewayLoadBalancerClientAutoConfiguration.java:负载均衡相关配置信息
// 初始化路由负载过滤器,实际上就是一个全局过滤器
@Bean
@ConditionalOnBean({LoadBalancerClient.class})
@ConditionalOnMissingBean({LoadBalancerClientFilter.class, ReactiveLoadBalancerClientFilter.class})
@ConditionalOnEnabledGlobalFilter
public LoadBalancerClientFilter loadBalancerClientFilter(LoadBalancerClient client, LoadBalancerProperties properties) {
return new LoadBalancerClientFilter(client, properties);
}
所有的请求都会经过DispatcherHandler.handle(ServerWebExchange exchange)这个方法,这个类就可以理解为MVC中的ServletDispatcher。
DispatcherHandler.handle(ServerWebExchange exchange)
@Override
public Mono<Void> handle(ServerWebExchange exchange) {
return Flux.fromIterable(this.handlerMappings)
// 处理当前请求,实际上就是需要找当前请求所对应的路由信息。并保存在当前请求上下文exchange中。
.concatMap(mapping -> mapping.getHandler(exchange))
.next()
.switchIfEmpty(createNotFoundError())
// 找到当前请求对应的路由信息之后,就是在这里调用执行的。
.flatMap(handler -> invokeHandler(exchange, handler))
.flatMap(result -> handleResult(exchange, result));
}
// mapping.getHandler(exchange))会调用AbstractHandlerMapping.getHandler
AbstractHandlerMapping.getHandler
@Override
public Mono<Object> getHandler(ServerWebExchange exchange) {
return getHandlerInternal(exchange).map(handler -> {
……省略部分代码……
return handler;
});
}
// getHandlerInternal(exchange)最终会调用RoutePredicateHandlerMapping.getHandlerInternal(ServerWebExchange exchange)
RoutePredicateHandlerMapping.getHandlerInternal(ServerWebExchange exchange)
@Override
protected Mono<?> getHandlerInternal(ServerWebExchange exchange) {
……省略部分代码……
// 可以理解为匹配当前请求所满足的路由信息。
return lookupRoute(exchange).flatMap((Function<Route, Mono<?>>) r -> {
……省略部分代码……
// 将目前找到的router封装到当前的请求上下文中。那么后续肯定会在调用getHandlerInternal的地方使用处理。
exchange.getAttributes().put(GATEWAY_ROUTE_ATTR, r);
}
……省略部分代码……
}
// lookupRoute(exchange)最终会调用RouteDefinitionRouteLocator.getRoutes()
RouteDefinitionRouteLocator.getRoutes()
@Override
public Flux<Route> getRoutes() {
// yml文件中的配置的routers信息会被封装到RouteDefinition中,getRouteDefinitions就是获取定义的所有的routers信息
Flux<Route> routes = this.routeDefinitionLocator.getRouteDefinitions()
// 把RouteDefinition转化为真正的能够使用的Router
.map(this::convertToRoute);
……省略部分代码……
return routes.map(route -> {
if (logger.isDebugEnabled()) {
logger.debug("RouteDefinition matched: " + route.getId());
}
return route;
});
}
FilteringWebHandler.handle:DispatcherHandler.handle(ServerWebExchange exchange)方法中的invokeHandler(exchange, handler)最终会执行到此处。
@Override
public Mono<Void> handle(ServerWebExchange exchange) {
// 获取RoutePredicateHandlerMapping.getHandlerInternal(ServerWebExchange exchange)方法中设置的GATEWAY_ROUTE_ATTR属性,实际上就是当前请求所对应路由信息
Route route = exchange.getRequiredAttribute(GATEWAY_ROUTE_ATTR);
// 从当前请求所对应路由信息中获取到所有的过滤器
List<GatewayFilter> gatewayFilters = route.getFilters();
// 根据globalFilters属性,可以判断这里就是的将所有的全局过滤器进行组装成了一个List集合。
List<GatewayFilter> combined = new ArrayList<>(this.globalFilters);
// 将当前请求所对应路由信息中获取到所有的过滤器添加到全局过滤器中的集合中。
combined.addAll(gatewayFilters);
// 对所有的过滤器按照设定的Order进行排序
AnnotationAwareOrderComparator.sort(combined);
// 创建一个过滤器执行链(责任链模式),并调用filter依次执行所有的过滤器方法。
return new DefaultGatewayFilterChain(combined).filter(exchange);
}
gateway默认整合Ribbon做负载均衡策略
// Ribbon默认采用轮询方式做负载均衡,ribbon默认的轮训方式实现
private int incrementAndGetModulo(int modulo) {
int current;
int next;
do {
current = this.nextIndex.get();
next = (current + 1) % modulo;
} while(!this.nextIndex.compareAndSet(current, next) || current >= modulo);
return current;
}
上述所有的步骤都是在做一个事情,那就是匹配当前请求中所适配的路由,并将路由的信息拼装当前请求上下文中,比如:我们请求订单服务,但是得先经过gateway服务,那我们请求网关服务的URL可能是http://localhost:8081/order/createOrder?id=1,上述的所有步骤执行完成之后,我们的URL才能变成实际上订单服务的地址http://192.168.12.11:8000/createOrder?id=1(假设我们的订单服务部署在192.168.12.11机器上,开放端口为8000),这个URL地址才是真正能创建订单的地址。但是上述步骤都只是在组装这个订单服务的地址http://192.168.12.11:8000/createOrder?id=1,在哪里调用呢,实际上就是在NettyRoutingFilter中调用的,NettyRoutingFilter是Filter执行链中最后执行的Filter,也是最终调用路由的地方,此处会真正的执行http://192.168.12.11:8000/createOrder?id=1这个请求,调到订单服务上,创建一个真正的订单。
@Override
@SuppressWarnings("Duplicates")
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 这里获取到上述步骤中组装好的真正的要请求订单服务的地址:http://192.168.12.11:8000/createOrder?id=1
URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);
……省略部分代码……
//
Flux<HttpClientResponse> responseFlux = getHttpClient(route, exchange)
.headers(headers -> {
……省略部分代码……
return Mono.just(res);
});
……省略部分代码……
return responseFlux.then(chain.filter(exchange));
}
protected HttpClient getHttpClient(Route route, ServerWebExchange exchange) {
Object connectTimeoutAttr = route.getMetadata().get(CONNECT_TIMEOUT_ATTR);
if (connectTimeoutAttr != null) {
Integer connectTimeout = getInteger(connectTimeoutAttr);
// 最终也就是通过httpClient调用
return this.httpClient.tcpConfiguration((tcpClient) -> tcpClient
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectTimeout));
}
return httpClient;
}