SpringCloud架构组件中gateWay主要功能处理众多微服务统一访问路由问题。因为微服务都是在单一的内置Tomcat中运行,可能有不同的IP和端口,如果没有一个统一的访问入口。当微服务数量太多时,对于访问者需要手动管理,容易出现混乱。这时gateWay就孕育而生了。
网关本身也是一个SpringBoot服务。
引入maven
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<version>3.0.7</version>
</dependency>
网关相关配置
cloud:
gateway:
discovery:
locator:
enabled: false # 这个配置是默认给每个服务创建一个router,设置为false防止请求默认转发到url中包含的微服务名上
#例:/auth/**会默认转发到服务auth下,而不是转发到配置的uri
lower-case-service-id: true # 微服务名称以小写形式呈现
routes:
- id: 微服务名称
uri: lb://微服务名称
predicates:
- Path=/微服务名称/**
filters: # /微服务名称/** 转发到 uri/**
- StripPrefix=1
源码赏析
从源代码包中可以看出网关支持过滤器功能,路由发现功能。
下面是路由核心代码实现源码
/*
* Copyright 2013-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.gateway.route;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import reactor.core.publisher.Flux;
import org.springframework.cloud.gateway.config.GatewayProperties;
import org.springframework.cloud.gateway.event.FilterArgsEvent;
import org.springframework.cloud.gateway.event.PredicateArgsEvent;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.OrderedGatewayFilter;
import org.springframework.cloud.gateway.filter.factory.GatewayFilterFactory;
import org.springframework.cloud.gateway.handler.AsyncPredicate;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.handler.predicate.RoutePredicateFactory;
import org.springframework.cloud.gateway.support.ConfigurationService;
import org.springframework.cloud.gateway.support.HasRouteId;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.web.server.ServerWebExchange;
/**
* {@link RouteLocator} that loads routes from a {@link RouteDefinitionLocator}.
*
* @author Spencer Gibb
*/
public class RouteDefinitionRouteLocator implements RouteLocator {
/**
* Default filters name.
*/
public static final String DEFAULT_FILTERS = "defaultFilters";
protected final Log logger = LogFactory.getLog(getClass());
private final RouteDefinitionLocator routeDefinitionLocator;
private final ConfigurationService configurationService;
private final Map<String, RoutePredicateFactory> predicates = new LinkedHashMap<>();
private final Map<String, GatewayFilterFactory> gatewayFilterFactories = new HashMap<>();
private final GatewayProperties gatewayProperties;
public RouteDefinitionRouteLocator(RouteDefinitionLocator routeDefinitionLocator,
List<RoutePredicateFactory> predicates, List<GatewayFilterFactory> gatewayFilterFactories,
GatewayProperties gatewayProperties, ConfigurationService configurationService) {
this.routeDefinitionLocator = routeDefinitionLocator;
this.configurationService = configurationService;
initFactories(predicates);
gatewayFilterFactories.forEach(factory -> this.gatewayFilterFactories.put(factory.name(), factory));
this.gatewayProperties = gatewayProperties;
}
private void initFactories(List<RoutePredicateFactory> predicates) {
predicates.forEach(factory -> {
String key = factory.name();
if (this.predicates.containsKey(key)) {
this.logger.warn("A RoutePredicateFactory named " + key + " already exists, class: "
+ this.predicates.get(key) + ". It will be overwritten.");
}
this.predicates.put(key, factory);
if (logger.isInfoEnabled()) {
logger.info("Loaded RoutePredicateFactory [" + key + "]");
}
});
}
@Override
public Flux<Route> getRoutes() {
Flux<Route> routes = this.routeDefinitionLocator.getRouteDefinitions().map(this::convertToRoute);
if (!gatewayProperties.isFailOnRouteDefinitionError()) {
// instead of letting error bubble up, continue
routes = routes.onErrorContinue((error, obj) -> {
if (logger.isWarnEnabled()) {
logger.warn("RouteDefinition id " + ((RouteDefinition) obj).getId()
+ " will be ignored. Definition has invalid configs, " + error.getMessage());
}
});
}
return routes.map(route -> {
if (logger.isDebugEnabled()) {
logger.debug("RouteDefinition matched: " + route.getId());
}
return route;
});
}
private Route convertToRoute(RouteDefinition routeDefinition) {
AsyncPredicate<ServerWebExchange> predicate = combinePredicates(routeDefinition);
List<GatewayFilter> gatewayFilters = getFilters(routeDefinition);
return Route.async(routeDefinition).asyncPredicate(predicate).replaceFilters(gatewayFilters).build();
}
@SuppressWarnings("unchecked")
List<GatewayFilter> loadGatewayFilters(String id, List<FilterDefinition> filterDefinitions) {
ArrayList<GatewayFilter> ordered = new ArrayList<>(filterDefinitions.size());
for (int i = 0; i < filterDefinitions.size(); i++) {
FilterDefinition definition = filterDefinitions.get(i);
GatewayFilterFactory factory = this.gatewayFilterFactories.get(definition.getName());
if (factory == null) {
throw new IllegalArgumentException(
"Unable to find GatewayFilterFactory with name " + definition.getName());
}
if (logger.isDebugEnabled()) {
logger.debug("RouteDefinition " + id + " applying filter " + definition.getArgs() + " to "
+ definition.getName());
}
// @formatter:off
Object configuration = this.configurationService.with(factory)
.name(definition.getName())
.properties(definition.getArgs())
.eventFunction((bound, properties) -> new FilterArgsEvent(
// TODO: why explicit cast needed or java compile fails
RouteDefinitionRouteLocator.this, id, (Map<String, Object>) properties))
.bind();
// @formatter:on
// some filters require routeId
// TODO: is there a better place to apply this?
if (configuration instanceof HasRouteId) {
HasRouteId hasRouteId = (HasRouteId) configuration;
hasRouteId.setRouteId(id);
}
GatewayFilter gatewayFilter = factory.apply(configuration);
if (gatewayFilter instanceof Ordered) {
ordered.add(gatewayFilter);
}
else {
ordered.add(new OrderedGatewayFilter(gatewayFilter, i + 1));
}
}
return ordered;
}
private List<GatewayFilter> getFilters(RouteDefinition routeDefinition) {
List<GatewayFilter> filters = new ArrayList<>();
// TODO: support option to apply defaults after route specific filters?
if (!this.gatewayProperties.getDefaultFilters().isEmpty()) {
filters.addAll(loadGatewayFilters(routeDefinition.getId(),
new ArrayList<>(this.gatewayProperties.getDefaultFilters())));
}
if (!routeDefinition.getFilters().isEmpty()) {
filters.addAll(loadGatewayFilters(routeDefinition.getId(), new ArrayList<>(routeDefinition.getFilters())));
}
AnnotationAwareOrderComparator.sort(filters);
return filters;
}
private AsyncPredicate<ServerWebExchange> combinePredicates(RouteDefinition routeDefinition) {
List<PredicateDefinition> predicates = routeDefinition.getPredicates();
if (predicates == null || predicates.isEmpty()) {
// this is a very rare case, but possible, just match all
return AsyncPredicate.from(exchange -> true);
}
AsyncPredicate<ServerWebExchange> predicate = lookup(routeDefinition, predicates.get(0));
for (PredicateDefinition andPredicate : predicates.subList(1, predicates.size())) {
AsyncPredicate<ServerWebExchange> found = lookup(routeDefinition, andPredicate);
predicate = predicate.and(found);
}
return predicate;
}
@SuppressWarnings("unchecked")
private AsyncPredicate<ServerWebExchange> lookup(RouteDefinition route, PredicateDefinition predicate) {
RoutePredicateFactory<Object> factory = this.predicates.get(predicate.getName());
if (factory == null) {
throw new IllegalArgumentException("Unable to find RoutePredicateFactory with name " + predicate.getName());
}
if (logger.isDebugEnabled()) {
logger.debug("RouteDefinition " + route.getId() + " applying " + predicate.getArgs() + " to "
+ predicate.getName());
}
// @formatter:off
Object config = this.configurationService.with(factory)
.name(predicate.getName())
.properties(predicate.getArgs())
.eventFunction((bound, properties) -> new PredicateArgsEvent(
RouteDefinitionRouteLocator.this, route.getId(), properties))
.bind();
// @formatter:on
return factory.applyAsync(config);
}
}
Spring Cloud架构的各个组件原理如下:
服务注册与发现:Spring Cloud使用Eureka作为服务注册与发现的组件。Eureka采用c-s的设计架构,其中Eureka Server作为服务注册功能的服务器,是服务注册中心。系统的其他微服务使用Eureka的客户端连接到Eureka Server并维持心跳。这样,系统的维护人员可以通过Eureka Server来监控系统中的各个微服务是否正常运行。
服务消费者:服务消费者通过Eureka客户端与服务注册中心进行交互,从Eureka Server获取服务提供者的地址列表,并直接与服务提供者进行通信。这种方式简化了服务调用,并且可以动态地添加或删除服务实例。
服务提供者:服务提供者将自己的服务注册到Eureka Server,并定期发送心跳来续约自己的服务。服务提供者会向Eureka Server提供自己的服务实例的信息,包括IP地址和端口号等。
负载均衡:Spring Cloud使用Ribbon作为负载均衡组件。Ribbon基于HTTP和TCP提供了一种客户端负载均衡的机制,可以有效地降低系统中的负载压力。
断路器:Spring Cloud使用Hystrix作为断路器组件。当某个服务调用失败时,Hystrix会触发断路器机制,快速失败并返回错误信息,避免因某个服务的故障导致整个系统的瘫痪。
配置中心:Spring Cloud使用Spring Cloud Config作为配置中心组件。通过将配置信息存储在统一的配置中心中,可以实现配置的集中管理和动态刷新,提高了系统的可维护性和灵活性。
网关:Spring Cloud使用Zuul作为网关组件。Zuul可以用来统一管理和调度微服务的请求,实现了请求的路由、过滤和监控等功能。
Spring Cloud是一个基于Java的微服务架构开发框架,它提供了一套完整的解决方案,帮助开发者快速构建高可用、可扩展的微服务应用。下面我们将详细探讨Spring Cloud的优缺点:
优点:
易于集成:Spring Cloud与Spring Boot集成良好,可以快速构建微服务应用。它还提供了丰富的功能和工具,如服务注册与发现、负载均衡、断路器、配置管理等,简化了微服务应用的开发过程。
灵活性高:Spring Cloud基于Spring Boot,可以使用各种自定义的配置和插件,提供了高度的灵活性。这使得开发者可以根据实际需求来定制自己的微服务应用。
生态丰富:Spring Cloud拥有庞大的开发者社区和丰富的生态,可以轻松找到各种所需的工具和组件。此外,由于其基于Java开发,可以利用Java丰富的生态资源。
稳定性强:Spring Cloud经过了广泛的生产环境验证,具有高可用性和稳定性。它还提供了各种监控和报警工具,可以帮助开发者及时发现和解决问题。
缺点:
学习曲线陡峭:Spring Cloud涉及大量的概念和技术,学习曲线相对陡峭。对于初学者来说,需要花费一定的时间和精力来学习和理解这些概念和技术。
配置复杂:Spring Cloud提供了丰富的功能和工具,但这也导致了配置相对复杂。在开发微服务应用时,开发者需要处理大量的配置和部署工作。
性能开销:由于Spring Cloud使用的是Java虚拟机(JVM),每个微服务都需要一个独立的JVM实例,这可能会导致性能开销相对较大。此外,启动每个微服务实例也需要一定的时间。
安全问题:随着微服务架构的普及,安全问题变得越来越突出。由于微服务之间存在频繁的交互,如何保证微服务之间的通信安全以及如何保护敏感数据是一个亟待解决的问题。