更多精彩欢迎关注公众号:闲人张
基本概念
Spring Cloud Gateway旨在为微服务架构提供简单、有效和统一的API路由管理方式,Spring Cloud Gateway作为Spring Cloud生态系统中的网关,目标是替代Netflix Zuul,其不仅提供统一的路由方式,并且还基于Filer链的方式提供了网关基本的功能,例如:安全、监控/埋点、限流等。
实现原理
Spring Cloud Gateway 底层使用了高性能的通信框架Netty,启动一个netty server(默认端口为8080)接受请求,然后通过Routes(每个Route由Predicate(等同于HandlerMapping)和Filter(等同于HandlerAdapter))处理后通过Netty Client发给响应的微服务。 具体配置在org.springframework.cloud.gateway.config.GatewayAutoConfiguration.NettyConfiguration中。
名词解释
- Route(路由):路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如果断言为true则匹配该路由。
- Predicate(断言):参考的是java8的java.util.function.Predicate开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由。
- Filter(过滤):指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改。
实现方式
- 基础URI一种路由配置方式
spring:
cloud:
gateway:
routes:
-id: proxy-1
uri: https://www.baidu.com
predicates:
-Path=/bd
id:我们自定义的路由 ID,保持唯一 uri:目标服务地址 predicates:路由条件,Predicate 接受一个输入参数,返回一个布尔值结果。该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非)
- 和注册中心相结合的路由配置方式
spring:
cloud:
gateway:
# 以服务名方式路由
discovery:
locator:
# 开启基于eureka服务名路径方式路由(不需要每次配置重启服务)
enabled: true
# 路由服务名可以是小写
lowerCaseServiceId: true
routes:
-id: proxy-2
uri: lb://demo-service.provider
predicates:
- Path=/app/**
# 将以app开头的请求转发到demo-service.provider
filters:
# StripPrefix 转发时去掉第一个前缀/app
- StripPrefix=1
# PrefixPath 转发时实际路径会加上这个前缀 即 访问/test 实际请求为/mapath/test
- PrefixPath=/mypath
# 访问localhost:8080/demo/v1/api/logs, 请求会转发到localhost:8001/v1/api/logs
- RewritePath=/demo/v1/api/logs, /v1/api/logs
- 基于代码的路由配置方式
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route(r -> r.path("/bd").uri("http://www.baidu.com"))
.route("id",r -> r.path("/app/**").filters(f -> f.stripPrefix(1)).uri("lb://demo-service.provider"))
.build();
}
动态路由
RouteDefinitionRepository 继承 RouteDefinitionLocator & RouteDefinitionWriter,可通过实现RouteDefinitionLocator 或 RouteDefinitionRepository 自定义路由。
RouteDefinitionLocator 是路由定义定位器的顶级接口,它的主要作用就是读取路由的配置信息,只有getRouteDefinitions()一个方法。 从org.springframework.cloud.gateway.route.RouteDefinition里面读取
RouteDefinitionRouteLocator 与 RouteDefinitionLocator比较容易混淆,前者是一个RouteLocator(路由定位器), 后者是一个RouteDefinitionLocator(路由定义定位器),前者的 RouteDefinitionRouteLocator 主要从后者获取路由定义信息。
在WeightCalculatorWebFilter.java中有 RefreshRoutesEvent 每隔30s 调用 getRouteDefinitions
每次请求都会调用getRouteDefinitions,当网关较多时,会影响请求速度,考虑放到本地Map中,使用消息通知Map更新 RouteDefinitionWriter 用来实现路由的添加与删除。 默认 InMemoryRouteDefinitionRepository数据没有持久化。
路由的配置存储应该加入版本控制。
数据库表结构
CREATE TABLE `route` (
`id` BIGINT ( 20 ) NOT NULL AUTO_INCREMENT,
`server_id` VARCHAR ( 50 ) COLLATE utf8mb4_bin NOT NULL DEFAULT '' COMMENT '服务Id',
`uri` VARCHAR ( 100 ) COLLATE utf8mb4_bin NOT NULL COMMENT '服务路径',
`predicates` VARCHAR ( 500 ) COLLATE utf8mb4_bin NOT NULL COMMENT '服务凭证',
`filters` VARCHAR ( 1000 ) COLLATE utf8mb4_bin NOT NULL COMMENT '服务过滤',
`order` INT ( 11 ) NOT NULL DEFAULT '0' COMMENT '调用权重',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
`remarks` VARCHAR ( 100 ) COLLATE utf8mb4_bin DEFAULT '' COMMENT '描述',
`enable` INT ( 11 ) NOT NULL DEFAULT '1' COMMENT '是否生效',
PRIMARY KEY ( `id` ) USING BTREE,
UNIQUE KEY `unique_server_id` ( `server_id` ) USING BTREE
) ENGINE = INNODB AUTO_INCREMENT = 1233 DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_bin;
动态路由实现
@Component
@Slf4j
public class MyRouteDefinitionRepository implements RouteDefinitionRepository,ApplicationListener<RefreshRoutesEvent> {
private static final Map<String, RouteDefinition> ROUTES_CACHE = new ConcurrentHashMap<>();
@Resource
private RouteMapper routeMapper;
@Resource
private ApplicationEventPublisher applicationEventPublisher;
@Override
public Flux<RouteDefinition> getRouteDefinitions() {
return Flux.fromIterable(ROUTES_CACHE.values());
}
@Override
public Mono<Void> save(Mono<RouteDefinition> route) {
return route.flatMap(routeDefinition -> {
if(!ROUTES_CACHE.containsKey(routeDefinition.getId())){
ROUTES_CACHE.put(routeDefinition.getId(),routeDefinition);
}
return Mono.empty();
});
}
@Override
public Mono<Void> delete(Mono<String> routeId) {
return routeId.flatMap(id -> {
if(ROUTES_CACHE.containsKey(id)){
ROUTES_CACHE.remove(id);
}
return Mono.empty();
});
}
@Override
public void onApplicationEvent(RefreshRoutesEvent refreshRoutesEvent) { }
public void refreshRoutes() {
getRoutes();
applicationEventPublisher.publishEvent(new RefreshRoutesEvent(""));
}
private void getRoutes(){
List<Route> routeList = routeMapper.selectAll();
if(!CollectionUtils.isEmpty(routeList)){
ROUTES_CACHE.clear();
routeList.forEach(route -> {
//RouteDefinition 作为GatewayProperties中的属性,在网关启动的时候读取配置文件中的相关配置信息
RouteDefinition definition = new RouteDefinition();
definition.setId(route.getServerId());
List<PredicateDefinition> predicates = Lists.newArrayList();
List<FilterDefinition> filters = Lists.newArrayList();
List<RouteParameters> routePredicates = JSONArray.parseArray(route.getPredicates(),RouteParameters.class);
routePredicates.forEach(routePredicate -> {
PredicateDefinition predicateDefinition = new PredicateDefinition();
//名称是固定的,spring gateway会根据名称找对应的PredicateFactory
predicateDefinition.setName(routePredicate.getName());
predicateDefinition.setArgs(routePredicate.getArgs());
predicates.add(predicateDefinition);
});
definition.setPredicates(predicates);
List<RouteParameters> routeFilters = JSONArray.parseArray(route.getFilters(),RouteParameters.class);
routeFilters.forEach(routeFilter -> {
FilterDefinition filterDefinition = new FilterDefinition();
filterDefinition.setName(routeFilter.getName());
filterDefinition.setArgs(routeFilter.getArgs());
filters.add(filterDefinition);
});
definition.setFilters(filters);
URI uri = UriComponentsBuilder.fromUriString(route.getUri()).build().toUri();
definition.setUri(uri);
definition.setOrder(route.getOrder());
save(Mono.just(definition)).subscribe();
});
}
}
}
可通过restful接口刷新,实现路由刷新!
更多精彩欢迎关注公众号:闲人张