Spring Cloud Gateway 动态路由 – 进阶
方案简介
(1)创建一个路由信息维护的项目(dynamic-route),实现增删改查路由信息到mysql
(2)提供发布功能,发布后将路由信息与版本信息保存到redis中,对外提供 rest 接口获取路由信息
(3)网关(gateway-dynamic-route)开启定时任务,定时拉取 rest 接口中发布的最新版本的路由信息,对比版本号,如果网关的版本号与rest接口中的不一致,则获取路由信息后更新网关路由,这样网关发布多个实例后,都会单独的去拉取维护路由信息
(4)整体架构设计如下:
根据上面的思路进行代码实现,下面将创建2个项目,都要注册到eureka上
dynamic-route
,路由管理,维护路由信息,并存储到mysql与redis中gateway-dynamic-route
,网关,通过rest接口定时拉取最新路由信息并更新到网关中consumer-service
,消费者服务,测试动态路由配置后将请求转发到此服务
参考:https://gitee.com/zhuyu1991/spring-cloud/tree/master/gateway
参考:https://gitee.com/zhuyu1991/spring-cloud/tree/master/dynamic-route
路由管理
创建一个路由信息维护的项目dynamic-route
,实现对路由信息的增删改查和发布功能,信息保存到mysql中,发布后保存到redis,对外提供路由数据时返回redis中的路由信息(使用缓存来应对网关定时任务读请求)
有2张表,路由信息表与版本发布表,表结构如下:
-- ----------------------------
-- Table structure for gateway_routes
-- ----------------------------
DROP TABLE IF EXISTS `gateway_routes`;
CREATE TABLE `gateway_routes` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`route_id` varchar(64) DEFAULT NULL COMMENT '路由id',
`route_uri` varchar(128) DEFAULT NULL COMMENT '转发目标uri',
`route_order` int(11) DEFAULT NULL COMMENT '路由执行顺序',
`predicates` text COMMENT '断言JSON字符串集合',
`filters` text COMMENT '过滤器JSON字符串集合',
`is_ebl` tinyint(1) DEFAULT NULL COMMENT '是否启用',
`is_del` tinyint(1) DEFAULT NULL COMMENT '是否删除',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '修改时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;
-- ----------------------------
-- Table structure for dynamic_version
-- ----------------------------
DROP TABLE IF EXISTS `dynamic_version`;
CREATE TABLE `dynamic_version` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键、自动增长、版本号',
`create_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4;
路由列表:http://localhost:9600/gateway-routes/list
添加路由信息:http://localhost:9600/gateway-routes/edit?id=2
获取版本信息:http://localhost:9600/version/lastVersion
获取路由信息:http://localhost:9600/gateway-routes/routes
网关先获取版本号,与本地版本号对比,如不一致则通过此接口拉取路由信息
网关项目
重点是网关项目,创建 gateway-dynamic-route 项目,网关启动时设置默认的版本号为0,通过定时任务每60秒拉取一次远程路由项目提供最新版本号,与网关的版本号对比,如不一致,则拉取远程路由项目最新的路由信息,更新到网关路由上去,同时把本地版本号覆盖为最新的版本号
(1)启动定时任务,注册到erueka,添加RestTemplate Bean且以负载均衡形式访问路由项目
@EnableScheduling
@EnableDiscoveryClient
@SpringBootApplication
public class GatewayDynamicRouteApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayDynamicRouteApplication.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
(2)定时任务实现类
/**
* 定时任务,拉取路由信息
* 路由信息由路由项目单独维护
*/
@Component
public class DynamicRouteScheduling {
@Autowired private RestTemplate restTemplate;
@Autowired private DynamicRouteService dynamicRouteService; //动态路由实现类,与前篇博客中的实现类代码是一样的
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private static final String dynamicRouteServerName = "dynamic-route-service";
//发布路由信息的版本号
private static Long versionId = 0L;
//每60秒中执行一次
//如果版本号不相等则获取最新路由信息并更新网关路由
@Scheduled(cron = "*/60 * * * * ?")
public void getDynamicRouteInfo() {
try {
System.out.println("拉取时间:" + dateFormat.format(new Date()));
//先拉取版本信息,如果版本号不想等则更新路由
Long resultVersionId = restTemplate.getForObject("http://"+ dynamicRouteServerName + "/version/lastVersion", Long.class);
System.out.println("路由版本信息:本地版本号:" + versionId + ",远程版本号:" + resultVersionId);
if(resultVersionId != null && versionId != resultVersionId) {
System.out.println("开始拉取路由信息......");
String resultRoutes = restTemplate.getForObject("http://"+ dynamicRouteServerName + "/gateway-routes/routes", String.class);
System.out.println("路由信息为:" + resultRoutes);
if (!StringUtils.isEmpty(resultRoutes)) {
List<GatewayRouteDefinition> list = JSON.parseArray(resultRoutes, GatewayRouteDefinition.class);
for (GatewayRouteDefinition definition : list) {
//更新路由
RouteDefinition routeDefinition = assembleRouteDefinition(definition);
dynamicRouteService.update(routeDefinition);
}
versionId = resultVersionId;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
//把前端传递的参数转换成路由对象
private RouteDefinition assembleRouteDefinition(GatewayRouteDefinition gwdefinition) {
RouteDefinition definition = new RouteDefinition();
definition.setId(gwdefinition.getId());
definition.setOrder(gwdefinition.getOrder());
//设置断言
List<PredicateDefinition> pdList=new ArrayList<>();
List<GatewayPredicateDefinition> gatewayPredicateDefinitionList=gwdefinition.getPredicates();
for (GatewayPredicateDefinition gpDefinition: gatewayPredicateDefinitionList) {
PredicateDefinition predicate = new PredicateDefinition();
predicate.setArgs(gpDefinition.getArgs());
predicate.setName(gpDefinition.getName());
pdList.add(predicate);
}
definition.setPredicates(pdList);
//设置过滤器
List<FilterDefinition> filters = new ArrayList();
List<GatewayFilterDefinition> gatewayFilters = gwdefinition.getFilters();
for (GatewayFilterDefinition filterDefinition : gatewayFilters) {
FilterDefinition filter = new FilterDefinition();
filter.setName(filterDefinition.getName());
filter.setArgs(filterDefinition.getArgs());
filters.add(filter);
}
definition.setFilters(filters);
URI uri = null;
if (gwdefinition.getUri().startsWith("http")) {
uri = UriComponentsBuilder.fromHttpUrl(gwdefinition.getUri()).build().toUri();
} else {
uri = URI.create(gwdefinition.getUri());
}
definition.setUri(uri);
return definition;
}
}
(3)网关提供获取所有路由信息的Controller
/**
* 查询网关的路由信息
*/
@RestController
@RequestMapping("/route")
public class DynamicRouteController {
@Autowired
private RouteDefinitionLocator routeDefinitionLocator;
//获取网关所有的路由信息
@RequestMapping("/routes")
public Flux<RouteDefinition> getRouteDefinitions() {
return routeDefinitionLocator.getRouteDefinitions();
}
}
(4)启动eureka
、dynamic-route
、gateway-dynamic-route
、consumer-service
(5)通过dynamic-route
项目新增一条路由信息,路由的 uri 是consumer-service
,且发布路由
发布路由时,会将版本表最新的版本号与路由信息添加到redis中,网关定时获取版本信息与路由信息都是先从redis中取出来的
(6)因网关没有配置路由信息,可通过 http://localhost:9999/route/routes 获取所有路由信息,此时有两条路由信息,是网关从eureka上拉下来的默认的路由信息,不是配置的路由信息
(7)等待60秒后,网关通过 RestTemplate 拉取dynamic-route
项目的最新版本号,发现不一致,则拉取最新路由信息,并更新到网关路由中
(8)通过网关访问consumer-service
,验证路由信息是否已更新到网关中,访问:http://localhost:9999/appblog/hello?name=joe
(9)再次查看所有路由信息:http://localhost:9999/route/routes ,可看到consumer-service
已被设置到网关路由中了
(10)每隔60秒再次对比版本号,当有新的版本号发布后,就会拉取维护的路由信息,没有则网关不会更新路由
(11)修改一下路由信息,把断言Path=/appblog/**
,改为Path=/consumer/**
,并发布,隔了60秒后,网关的控制台拉取到最新的路由信息
(12)此时访问 http://localhost:9999/appblog/hello?name=joe 报错,因为路由断言path被更改了路由不到
(13)需要通过 http://localhost:9999/consumer/hello?name=joe 访问,正常返回
改进:可将定时任务改为MQ发送、主动请求推送、长连接请求等方案
版权声明:
作者:Joe.Ye
链接:https://www.appblog.cn/index.php/2023/04/01/spring-cloud-gateway-dynamic-routing-advanced/
来源:APP全栈技术分享
文章版权归作者所有,未经允许请勿转载。
共有 0 条评论