Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

ThinkingJava/dynroute

Repository files navigation

动态网关路由

1 前言

​ 上个月入职了新公司,发现中台网关路由配置是放在apollo上的,所以这次目的是设计一款动态路由配置的网关。

2 设计

​ 由于Gateway 的传统的通过配置中心及配置文件的方式配置路由略显得笨拙,通过api或者图形界面修改,则显得相对灵活的多。

2.1 技术选型

通过重写 spring cloud gateway 实现API动态更改路由,非常便捷的实现控制请求接入管理。路由信息使用mysql作为持久化存储,初始化启动加载,redis作为临时存储并发布订阅监听。

2.2 模块划分

将项目划分为以下几个模块

名称 描述
dynroute-api 一些公共的代码,常量,异常类等。
dynroute-core 测试应用功能模块
dynroute-gateway 网关应用,鉴权,负载均衡,路由转发等
dynroute-server 测试应用功能模块

3 重构网关说明

3.1 网关接入数据库加载路由信息

  • 创建数据库表用于保存路由配置

     CREATE TABLE `gateway_route_config` (
     `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
     `route_name` varchar(30) DEFAULT NULL,
     `route_id` varchar(30) DEFAULT NULL,
     `predicates` json DEFAULT NULL COMMENT '断言',
     `filters` json DEFAULT NULL COMMENT '过滤器',
     `uri` varchar(50) DEFAULT NULL,
     `order` int(2) DEFAULT '0' COMMENT '排序',
     `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
     `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
     `del_flag` char(1) DEFAULT '0',
     PRIMARY KEY (`id`) USING BTREE
    ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COMMENT='路由配置表';
  • 初始化网关配置为空

     /**
     * 配置文件设置为空 redis 加载为准
     * @return
     */
     @Bean
     public PropertiesRouteDefinitionLocator propertiesRouteDefinitionLocator() {
     return new PropertiesRouteDefinitionLocator(new GatewayProperties());
     }
  • 初始化路由配置

     public void initRoute() {
     redisTemplate.delete(CacheConstants.ROUTE_KEY);
     log.info("开始初始化网关路由");
     gatewayRouteConfigService.list().forEach(route -> {
     RouteDefinitionVo vo = new RouteDefinitionVo();
     vo.setRouteName(route.getRouteName());
     vo.setId(route.getRouteId());
     vo.setUri(URI.create(route.getUri()));
     vo.setOrder(route.getOrder());
     JSONArray filterObj = JSONUtil.parseArray(route.getFilters());
     vo.setFilters(filterObj.toList(FilterDefinition.class));
     JSONArray predicateObj = JSONUtil.parseArray(route.getPredicates());
     vo.setPredicates(predicateObj.toList(PredicateDefinition.class));
     log.info("加载路由ID:{},{}", route.getRouteId(), vo);
     redisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(RouteDefinitionVo.class));
     redisTemplate.opsForHash().put(CacheConstants.ROUTE_KEY, route.getRouteId(), vo);
     });
     // 通知网关重置路由
     redisTemplate.convertAndSend(CacheConstants.ROUTE_JVM_RELOAD_TOPIC, "路由信息,网关缓存更新");
     log.debug("初始化网关路由结束 ");
     }
  • 设置监听路由配置,并监听订阅

 public RedisMessageListenerContainer redisContainer(RedisConnectionFactory redisConnectionFactory) {
 RedisMessageListenerContainer container = new RedisMessageListenerContainer();
 container.setConnectionFactory(redisConnectionFactory);
 container.addMessageListener((message, bytes) -> {
 log.warn("接收到重新JVM 重新加载路由事件");
 RouteCacheHolder.removeRouteList();
 // 发送刷新路由事件
 SpringContextHolder.publishEvent(new RefreshRoutesEvent(this));
 }, new ChannelTopic(CacheConstants.ROUTE_JVM_RELOAD_TOPIC));
 return container;
 }
  • 路由配置查询及修改
 /**
 * 路由配置查询及修改
 * @param serverRequest
 * @return
 */
 @SneakyThrows
 @Override
 public Mono<ServerResponse> handle(ServerRequest serverRequest) {
 HttpMethod method = serverRequest.method();
 if (method == HttpMethod.GET) {
 return ServerResponse.status(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON)
 .body(BodyInserters.fromValue(objectMapper.writeValueAsString(Response.ok(gatewayRouteConfigService.list()))));
 } else if (method == HttpMethod.PUT) {
 Mono<Response> routeStatus = serverRequest.bodyToMono(JSONArray.class).flatMap(updateRoutes());
 BodyInserter bodyInserter = BodyInserters.fromPublisher(routeStatus, Response.class);
 return ServerResponse.status(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON).body(bodyInserter);
 }
 return ServerResponse.status(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON)
 .body(BodyInserters.fromValue(objectMapper.writeValueAsString(Response.failed())));
 }

4 本地调试

4.1 本地启动nacos服务。

从 Github 上下载源码方式

git clone https://github.com/alibaba/nacos.git
cd nacos/
mvn -Prelease-nacos -Dmaven.test.skip=true clean install -U 
ls -al distribution/target/
// change the $version to your actual path
cd distribution/target/nacos-server-$version/nacos/bin
//启动nacos
startup.cmd -m standalone

4.2 Redis和Mysql

4.3 往mysql插入路由配置

INSERT INTO `gateway_route_config`(`id`,`route_name`,`route_id`,`predicates`,`filters`,`uri`,`order`,`create_time`,`update_time`,`del_flag`) VALUES 
(1,'动态网关测试','dynroute-server','[{\"args\": {\"_genkey_0\": \"/server/**\"}, \"name\": \"Path\"}]','[]','lb://dynroute-server',0,'2021年05月04日 16:44:41','2021年05月05日 04:48:51','0');

4.2 启动网关Gateway

实例1配置: 在启动参数VM options 添加 -Dserver.port=9999

4.3 启动dynroute-core

4.5 启动dynroute-server

4.6 postman调试

这个时候服务core服务是不通的。

image

新增网关路由配置

[
 {
 "routeId": "dynroute-server",
 "routeName": "动态网关测试",
 "predicates": [
 {
 "args": {
 "_genkey_0": "/server/**"
 },
 "name": "Path"
 }
 ],
 "filters": [],
 "uri": "lb://dynroute-server",
 "order": 1,
 "delFlag": "0"
 },
 {
 "routeId": "dynroute-core",
 "routeName": "动态网关测试",
 "predicates": [
 {
 "args": {
 "_genkey_0": "/core/**"
 },
 "name": "Path"
 }
 ],
 "filters": [],
 "uri": "lb://dynroute-core",
 "order": 2,
 "delFlag": "0"
 }
]

新增路由配置后,则可访问

About

一款动态配置的路由网关

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

AltStyle によって変換されたページ (->オリジナル) /