1. 配置:
代码方式配置(配置文件方式配置请求自行百度):
@Configuration
public class RouteConfig {
@Bean
public RouteLocator loadRoutes(RouteLocatorBuilder builder) {
RouteLocatorBuilder.Builder routeBuilder = builder.routes();
routeBuilder.route(r ->
r.path("/api/app/v1").and().weight("gt_001", 30).uri("http://10.0.1.171:15001/app")
).route(r ->
r.path("/api/app/v1").and().weight("gt_001", 50).uri("http://10.0.1.171:15101/app")
).route(r ->
r.path("/api/app/v1").and().weight("gt_001", 20).uri("http://10.0.1.171:15201/app")
);
return routeBuilder.build();
}
}
2. 路由实现源码: org.springframework.cloud.gateway.handler.predicate.WeightRoutePredicateFactory
@Override
public Predicate<ServerWebExchange> apply(WeightConfig config) {
return new GatewayPredicate() {
@Override
public boolean test(ServerWebExchange exchange) {
Map<String, String> weights = exchange.getAttributeOrDefault(WEIGHT_ATTR, Collections.emptyMap());
String routeId = exchange.getAttribute(GATEWAY_PREDICATE_ROUTE_ATTR);
// all calculations and comparison against random num happened in
// WeightCalculatorWebFilter
String group = config.getGroup();
if (weights.containsKey(group)) {
String chosenRoute = weights.get(group);
if (log.isTraceEnabled()) {
log.trace("in group weight: " + group + ", current route: "
+ routeId + ", chosen route: " + chosenRoute);
}
return routeId.equals(chosenRoute);
}
else if (log.isTraceEnabled()) {
log.trace("no weights found for group: " + group + ", current route: "
+ routeId);
}
return false;
}
@Override
public String toString() {
return String.format("Weight: %s %s", config.getGroup(),
config.getWeight());
}
};
}
咋一看
这里直接到exchange中获取weights,然后匹配分组和路由ID,匹配成功当前路由生效;
但通过配置的参数和逻辑推理不出按权重路由的地方,也没看到权重的使用;
通过分析该路由工厂发现他
额外实现了一个接口: ApplicationEventPublisherAware,
同时重写了beforeApply:发布了一个事件
public class WeightRoutePredicateFactory extends AbstractRoutePredicateFactory<WeightConfig>
implements ApplicationEventPublisherAware {
...
@Override
public void beforeApply(WeightConfig config) {
if (publisher != null) {
publisher.publishEvent(new WeightDefinedEvent(this, config));
}
}
...
}
有事件发布就会有订阅,只是这种很难跟代码;
终于通过方法:org.springframework.cloud.gateway.event.WeightDefinedEvent.getWeightConfig,
直接定位到了订阅者:org.springframework.cloud.gateway.filter.WeightCalculatorWebFilter;
通过名字可以看出是计算权重的一个过滤器;
至此,一切明朗了.
3. 路由注册并计算路由对应范围
权重路由是通过其工厂创建并发布一个事件,把自己添加到权重计算过滤器;
相同分组的路由会进行统一计算,最后统一算出多个范围,每个范围对应一个路由;
例如:
1号路由 : 权重30;
2号路由 : 权重50;
3号路由 : 权重20;
计算ranges后
0.0-0.3 : 1号路由;
0.3-0.8 : 2号路由;
0.8-1.0 : 3号路由;
public class WeightCalculatorWebFilter implements WebFilter, Ordered, SmartApplicationListener {
...
/* for testing */ void addWeightConfig(WeightConfig weightConfig) {
String group = weightConfig.getGroup();
GroupWeightConfig config;
// only create new GroupWeightConfig rather than modify
// and put at end of calculations. This avoids concurency problems
// later during filter execution.
if (groupWeights.containsKey(group)) {
config = new GroupWeightConfig(groupWeights.get(group));
}
else {
config = new GroupWeightConfig(group);
}
config.weights.put(weightConfig.getRouteId(), weightConfig.getWeight());
// recalculate
// normalize weights
int weightsSum = 0;
for (Integer weight : config.weights.values()) {
weightsSum += weight;
}
final AtomicInteger index = new AtomicInteger(0);
for (Map.Entry<String, Integer> entry : config.weights.entrySet()) {
String routeId = entry.getKey();
Integer weight = entry.getValue();
Double nomalizedWeight = weight / (double) weightsSum;
config.normalizedWeights.put(routeId, nomalizedWeight);
// recalculate rangeIndexes
config.rangeIndexes.put(index.getAndIncrement(), routeId);
}
// TODO: calculate ranges
config.ranges.clear();
config.ranges.add(0.0);
List<Double> values = new ArrayList<>(config.normalizedWeights.values());
for (int i = 0; i < values.size(); i++) {
Double currentWeight = values.get(i);
Double previousRange = config.ranges.get(i);
Double range = previousRange + currentWeight;
config.ranges.add(range);
}
if (log.isTraceEnabled()) {
log.trace("Recalculated group weight config " + config);
}
// only update after all calculations
groupWeights.put(group, config);
}
...
}
4. 按权重计算的范围进行路由
每次请求,通过该过滤器,发现又权重路由配置,生成一个随机数,
再比对随机数的落点在之前计算的那个范围,然后找到对应的路由,
把该路由放到exchange中,后面路由判定的时候比对,发现是自己则进行路由.
public class WeightCalculatorWebFilter implements WebFilter, Ordered, SmartApplicationListener {
...
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
Map<String, String> weights = getWeights(exchange);
for (String group : groupWeights.keySet()) {
GroupWeightConfig config = groupWeights.get(group);
if (config == null) {
if (log.isDebugEnabled()) {
log.debug("No GroupWeightConfig found for group: " + group);
}
continue; // nothing we can do, but this is odd
}
double r = this.random.nextDouble();
List<Double> ranges = config.ranges;
if (log.isTraceEnabled()) {
log.trace("Weight for group: " + group + ", ranges: " + ranges + ", r: "
+ r);
}
for (int i = 0; i < ranges.size() - 1; i++) {
if (r >= ranges.get(i) && r < ranges.get(i + 1)) {
String routeId = config.rangeIndexes.get(i);
weights.put(group, routeId);
break;
}
}
}
if (log.isTraceEnabled()) {
log.trace("Weights attr: " + weights);
}
return chain.filter(exchange);
}
...
}
通过对权重路由的分析,发现确实是高手;
在不破坏路由本身的设计上,巧妙的通过事件的方式实现了按权重路由;
虽然启动的计算步骤很多而且重复,但实际路由时,随机数+落点判定的方式可以高效的执行;
不错,不错,真的很不错,用一句美国话叫:very good!!!
网友评论