Spring Cloud GateWay系列(三):路由规则动态刷新

Spring Cloud Gateway旨在提供一种简单而有效的方式来路由API,并为它们提供横切关注点,例如:安全性、监控/指标和弹性。Route(路由)是网关的基本单元,由唯一标识符ID、目标地址URI、一组Predicate、一组Filter组成,根据Predicate进行匹配转发。使用配置文件构建路由规则的简单样例如下:

#指定监控和管理端口
management.server.port=9090
#指定需要公开的监控端点
management.endpoints.web.exposure.include=*

#路由规则 
spring.cloud.gateway.routes[0].id=csdn-service
spring.cloud.gateway.routes[0].uri=https://blog.csdn.net/
#断言器 路由转发的判断条件,目前SpringCloud Gateway支持多种方式,常见如:Path、Query、Method、Header等
spring.cloud.gateway.routes[0].predicates[0]= Path=/csdn/**,/csdnblog/**
#使用Gateway内置路径重写过滤器实现路径重写
spring.cloud.gateway.routes[0].filters[0]= RewritePath=/csdn(blog)?/(?<segment>.*), /${segment}

引入spring-boot-starter-actuator包之后,我们可以在http://localhost:9090/actuator/gateway/routes路径下查看已配置的路由规则。

 从org.springframework.cloud.gateway.actuate.GatewayControllerEndpoint类中可以找到对应的Handler处理器方法:

@RestControllerEndpoint(id = "gateway")
public class GatewayControllerEndpoint extends AbstractGatewayControllerEndpoint {

	public GatewayControllerEndpoint(List<GlobalFilter> globalFilters,
			List<GatewayFilterFactory> gatewayFilters,
			List<RoutePredicateFactory> routePredicates,
			RouteDefinitionWriter routeDefinitionWriter, RouteLocator routeLocator) {
		super(null, globalFilters, gatewayFilters, routePredicates, routeDefinitionWriter,
				routeLocator);
	}

	// TODO: Flush out routes without a definition
	@GetMapping("/routes")
	public Flux<Map<String, Object>> routes() {
        /*从Gateway的核心配置类GatewayAutoConfiguration中
          可以看到Spring容器中注入的RouteLocator实例
        this.routeLocator属性是CachingRouteLocator的实例*/
		return this.routeLocator.getRoutes().map(this::serialize);
	}
}

 SpringBoot自动装配的核心就是在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值,将这些值作为自动配置类导入到容器中,使自动配置类生效,帮我们进行自动配置工作。

 CachingRouteLocator通过RouteDefinitionRouteLocator类最终调用的是RouteDefinitionLocator的getRouteDefinitions()方法

public class RouteDefinitionRouteLocator
		implements RouteLocator, BeanFactoryAware, ApplicationEventPublisherAware {
    @Override
	public Flux<Route> getRoutes() {
		    return     this.routeDefinitionLocator.getRouteDefinitions().map(this::convertToRoute)
    	    // TODO: error handling
				.map(route -> {
					if (logger.isDebugEnabled()) {
						logger.debug("RouteDefinition matched: " + route.getId());
					}
					return route;
				});

		    /*
		     * TODO: trace logging if (logger.isTraceEnabled()) {
		     * logger.trace("RouteDefinition did not match: " +         routeDefinition.getId()); }
		     */
	}

    private Route convertToRoute(RouteDefinition routeDefinition) {
		    AsyncPredicate<ServerWebExchange> predicate = combinePredicates(routeDefinition);
		    List<GatewayFilter> gatewayFilters = getFilters(routeDefinition);

		    return Route.async(routeDefinition).asyncPredicate(predicate)
				.replaceFilters(gatewayFilters).build();
	}
}

通过Gateway自动化配置类可以清晰的看到有两个实现类PropertiesRouteDefinitionLocator和InMemoryRouteDefinitionRepository类,通过自定义RouteDefinitionRepository的实现类将路由规则存储在MySQL数据库中并配合actuator URL  /actuator/gateway/refresh实现在不重启Gateway的情况下动态刷新路由规则。

public class GatewayAutoConfiguration {

	@Bean
	public StringToZonedDateTimeConverter stringToZonedDateTimeConverter() {
		return new StringToZonedDateTimeConverter();
	}

	@Bean
	public RouteLocatorBuilder routeLocatorBuilder(
			ConfigurableApplicationContext context) {
		return new RouteLocatorBuilder(context);
	}

    /*加载application.properties文件中配置的路由规则*/
	@Bean
	@ConditionalOnMissingBean
	public PropertiesRouteDefinitionLocator propertiesRouteDefinitionLocator(
			GatewayProperties properties) {
		return new PropertiesRouteDefinitionLocator(properties);
	}

    /*InMemory加载内存中配置的路由规则 
      Application启动的时候内存中是没有路由规则的且Application关闭的时候内存中保存的路由规则也会丢失
    我们可以通过自定义RouteDefinitionRepository类替换掉InMemoryRouteDefinitionRepository
    实现将路由规则保存到数据库中
    数据库表格的设计可通过分析PropertiesRouteDefinitionLocator类抽象出模型*/
	@Bean
	@ConditionalOnMissingBean(RouteDefinitionRepository.class)
	public InMemoryRouteDefinitionRepository inMemoryRouteDefinitionRepository() {
		return new InMemoryRouteDefinitionRepository();
	}

	@Bean
	@Primary
	public RouteDefinitionLocator routeDefinitionLocator(
			List<RouteDefinitionLocator> routeDefinitionLocators) {
		return new CompositeRouteDefinitionLocator(
				Flux.fromIterable(routeDefinitionLocators));
	}

	@Bean
	public RouteLocator routeDefinitionRouteLocator(GatewayProperties properties,
			List<GatewayFilterFactory> gatewayFilters,
			List<RoutePredicateFactory> predicates,
			RouteDefinitionLocator routeDefinitionLocator,
			@Qualifier("webFluxConversionService") ConversionService conversionService) {
		return new RouteDefinitionRouteLocator(routeDefinitionLocator, predicates,
				gatewayFilters, properties, conversionService);
	}

	@Bean
	@Primary
	@ConditionalOnMissingBean(name = "cachedCompositeRouteLocator")
	// TODO: property to disable composite?
	public RouteLocator cachedCompositeRouteLocator(List<RouteLocator> routeLocators) {
		return new CachingRouteLocator(
				new CompositeRouteLocator(Flux.fromIterable(routeLocators)));
	}
}

下面将会详细分析 PropertiesRouteDefinitionLocator加载路由规则的代码实现,并抽象出MySQL数据库routes的创建语句。

public class PropertiesRouteDefinitionLocator implements RouteDefinitionLocator {

	private final GatewayProperties properties;

	public PropertiesRouteDefinitionLocator(GatewayProperties properties) {
		this.properties = properties;
	}

    
	@Override
	public Flux<RouteDefinition> getRouteDefinitions() {
        /*获取GatewayProperties中的routes属性*/
		return Flux.fromIterable(this.properties.getRoutes());
	}

}

/*注入application.properties文件中配置的路由规则*/
@ConfigurationProperties("spring.cloud.gateway")
@Validated
public class GatewayProperties {

	private final Log logger = LogFactory.getLog(getClass());

	/**
	 * List of Routes.
	 */
	@NotNull
	@Valid
	private List<RouteDefinition> routes = new ArrayList<>();
}

/*无论通过propertities配置文件,
还是通过自定义RouteDefinitionRepository实现从MySQL中加载加载路由规则
最终都是将定义的路由规则转换为RouteDefinition*/
@Validated
public class RouteDefinition {

	private String id;

    /*RouteDefinition和PredicateDefinition/FilterDefinition是一对多的映射关系
    PredicateDefinition和FilterDefinition的构造函数如出一辙,可以抽象到同一张表中*/
	@NotEmpty
	@Valid
	private List<PredicateDefinition> predicates = new ArrayList<>();

	@Valid
	private List<FilterDefinition> filters = new ArrayList<>();
}

进一步分析 PredicateDefinition和FilterDefinition

public class PredicateDefinition {

	@NotNull
	private String name;

	private Map<String, String> args = new LinkedHashMap<>();

	public PredicateDefinition() {
	}

    /*下面将会参照当前构造函数实现从数据库中加载路由规则配置并封装成Predicate*/
	public PredicateDefinition(String text) {
		int eqIdx = text.indexOf('=');
        /*properties文件中定义的路由规则 断言器 中必须包含 =*/
		if (eqIdx <= 0) {
			throw new ValidationException("Unable to parse PredicateDefinition text '"
					+ text + "'" + ", must be of the form name=value");
		}
		setName(text.substring(0, eqIdx));

		String[] args = tokenizeToStringArray(text.substring(eqIdx + 1), ",");

		for (int i = 0; i < args.length; i++) {
			this.args.put(NameUtils.generateName(i), args[i]);
		}
	}
}

通过上面对RouteDefinition PredicateDefinition和FilterDefinition的分析,我们抽象出route_rule和route_args两张表。

-- 路由规则表
CREATE TABLE `route_rule` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增主键',
  `route_id` varchar(255) DEFAULT NULL COMMENT '路由ID ',
  `uri` varchar(255) DEFAULT NULL COMMENT '目标地址',
  `ordered` int(11) DEFAULT NULL COMMENT '加载顺序',
  `created_at` datetime DEFAULT NULL COMMENT '创建时间',
  `updated_at` datetime DEFAULT NULL COMMENT '修改时间',
  `version` bigint(20) DEFAULT NULL COMMENT '数据版本',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 路由参数表 主要用来存储 断言器Predicates和过滤器Filter
-- route_rule与route_args 存在一对多的映射关系
CREATE TABLE `route_args` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `type` int(11) DEFAULT NULL COMMENT '参数类型 0:断言器 1:过滤器',
  `name` varchar(255) DEFAULT NULL COMMENT '断言器名称 例如: Path RewritePath',
  `args_name` varchar(255) DEFAULT NULL COMMENT '参数名称',
  `args_value` varchar(255) DEFAULT NULL COMMENT '参数值',
  `route_id` varchar(255) DEFAULT NULL COMMENT '表route_id中的字段route_id',
  `created_at` datetime DEFAULT NULL COMMENT '创建时间',
  `updated_at` datetime DEFAULT NULL COMMENT '修改时间',
  `version` bigint(20) DEFAULT NULL COMMENT '数据版本',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

 Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty,接下来的数据库访问操作将会基于r2dbc-mysql。

通过自定义RouteDefinitionRepository来替换InMemoryRouteDefinitionRepository,从而更改actuator监控端口 POST  actuator/gateway/routes/{id} 的保存地址,下面代码由于时间关系并没有完善,以后将会进一步完善并将代码放置在GitHub上面.....

@Service
public class GatewayService implements RouteDefinitionRepository {

    @Autowired
    private RouteRuleRepository routeRuleRepository;

    /**
     * @return
     */
    @Override public Flux<RouteDefinition> getRouteDefinitions() {
        return null;
    }


    /**
     * @param route
     * @return
     */
    @Override public Mono<Void> save(Mono<RouteDefinition> route) {
        return route.flatMap(r -> routeRuleRepository.findByRouteId(r.getId())
                .flatMap(routeRule -> {
                    routeRule.setUri(r.getUri().toString());
                    routeRule.setOrdered(r.getOrder());
                    return Mono.just(routeRule);
                })
                .switchIfEmpty(Mono.just(
                        RouteRule.builder().routeId(r.getId()).uri(r.getUri().toString())
                                .ordered(r.getOrder()).build()))
                .flatMap(routeRule -> routeRuleRepository.save(routeRule))
                .flatMap(routeRule -> {
                    if (!CollectionUtils.isEmpty(r.getPredicates())) {
                        List<RouteArgs> modelList = new ArrayList<>();
                        for (PredicateDefinition definition : r.getPredicates()) {
                            Map<String, String> args = definition.getArgs();
                            if (CollectionUtils.isEmpty(args)) {
                                continue;
                            }
                            /*TODO:先判断数据库中是否存在 存在时更新 不存在时新增*/
                            args.forEach((argName, argValue) -> {
                                RouteArgs argsModel =
                                        RouteArgs.builder().type(0)
                                                .name(definition.getName())
                                                .argsName(argName)
                                                .argsValue(argValue).build();
                                modelList.add(argsModel);
                            });
                        }
                        if (!CollectionUtils.isEmpty(modelList)) {
                            return Mono.just(routeRule);
                        }
                    }
                    return Mono.just(routeRule);
                })).then();
    }
}

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值