preface

This chapter learns the weighted routing of the SpringCloudGateway. Weighted routing is a more practical function in Gateway, whether ABTest or grayscale publishing, you can use the weighted routing function of Gateway.

A case,

Method 1: Code a RouteLocator

@Bean public RouteLocator customRouteLocator(RouteLocatorBuilder builder) { return builder.routes() .route(r -> r.path("/app/v1").and().weight("appV1", (3). The uri "http://127.0.0.1:15001/app")). The route (r - > r.p ath ("/app/v1 ") and (). The weight (" appV1 ", (5). The uri "http://127.0.0.1:15101/app")). The route (r - > r.p ath ("/app/v1 ") and (). The weight (" appV1 ", (2). The uri "http://127.0.0.1:15201/app")). The build (); }Copy the code

Method 2: Configure the RouteDefinition file

spring:
  cloud:
    gateway:
      routes:
      - id: serviceA
        uri: lb://httpbin
        predicates:
          - Path=/get
          - Weight=ABCTest, 20
      - id: serviceB
        uri: lb://httpbin2
        predicates:
          - Path=/get
          - Weight=ABCTest, 30
      - id: serviceC
        uri: lb://httpbin3
        predicates:
          - Path=/get
          - Weight=ABCTest, 50
Copy the code

WeightConfig and GroupWeightConfig

WeightConfig

WeightConfig configures the weight of a single route.

@age public class WeightConfig {@age private String group; // routeId private String routeId; @min (0) private int weight; }Copy the code

GroupWeightConfig

GroupWeightConfig is for a group of weights allocation, WeightConfig: GroupWeightConfig can be thought of as n: 1, the relationship between a corresponding multiple WeightConfig GroupWeightConfig. Take a look at the member variable of GroupWeightConfig.

Static class GroupWeightConfig {// GroupWeightConfig; // route ID - weights LinkedHashMap<String, Integer> weights = new LinkedHashMap<>(); // Route ID - standardised weights (a Double between 0 and 1) LinkedHashMap<String, Double> normalizedWeights = new LinkedHashMap<>(); // Range start subscript -route ID LinkedHashMap<Integer, String> rangeIndexes = new LinkedHashMap<>(); List<Double> ranges = new ArrayList<>(); }Copy the code

How is GroupWeightConfig calculated? Let’s take an example. Suppose there is a demoGroup with three WeightConfig routes whose ids are routea-2, routeb-3, and routec-5.

GroupWeightConfig.group = demoGroup GroupWeightConfig.weights = {"RouteA": 2, "RouteB": 3, "RouteC": 5} GroupWeightConfig. NormalizedWeights = {" RouteA ": 0.2," RouteB ": 0.3," RouteC ": 0.5} GroupWeightConfig. Ranges = [0, 0.2, 0.3, 0.5]. GroupWeightConfig rangeIndexes = {0: "RouteA", rank 1: "RouteB", 2: "RouteC"}Copy the code

When the request comes in, a random decimal number of 0-1 is generated to determine which range of the decimal number is in the ranges, and the starting position of the range is in the subscript of the ranges, by which the rangeIndexes are mapped to the actual route ID.

For example, a random decimal of 0.25 in the range [0.2, 0.3) starts at subscript 1 (the subscript of the 0.2 element), and the RouteID corresponding to subscript 1 is RouteB based on rangeIndexes.

Generate GroupWeightConfig

GroupWeightConfig is typically generated during the Spring container startup phase and placed after Spring events are receivedWeightCalculatorWebFilter#groupWeights.

WeightCalculatorWebFilter receive events

The Spring event WeightCalculatorWebFilter# onApplicationEvent received.

@Override public void onApplicationEvent(ApplicationEvent event) { if (event instanceof PredicateArgsEvent) { // In the case of Definition creating a route, convert to WeightConfig and then execute addWeightConfig Handle ((PredicateArgsEvent) event); } else if (event instanceof WeightDefinedEvent) { AddWeightConfig addWeightConfig(((WeightDefinedEvent) event).getweightConfig ()); } } public void handle(PredicateArgsEvent event) { Map<String, Object> args = event.getArgs(); / / if the args do not include the weight at the beginning of the key, ignore the if (args. IsEmpty () | |! hasRelevantKey(args)) { return; WeightConfig config = new WeightConfig(event.getrouteId ()); this.configurationService.with(config).name(WeightConfig.CONFIG_PREFIX) .normalizedProperties(args).bind(); GroupWeightConfig addWeightConfig(config); }Copy the code

WeightCalculatorWebFilter generated GroupWeightConfig

Generate GroupWeightConfig core method is WeightCalculatorWebFilter# addWeightConfig.

public class WeightCalculatorWebFilter implements WebFilter, Ordered, SmartApplicationListener {// GroupWeightConfig private Map<String, GroupWeightConfig> groupWeights = new ConcurrentHashMap<>(); // Every time an event occurs, Void addWeightConfig(WeightConfig WeightConfig) {String group = weightconfig.getGroup (); GroupWeightConfig config; If (groupweights.containskey (group)) {// The configuration of the group already exists, Weightconfig = new GroupWeightConfig(groupweights.get (group)); } else {config = new GroupWeightConfig(group); Weightconfig.weights. Put (weightconfig.getrouteId (), weightconfig.getweight ())); Int weightsSum = 0; for (Integer weight : config.weights.values()) { weightsSum += weight; } final AtomicInteger index = new AtomicInteger(0); // loop RouteID- weight for (map.entry <String, Integer> Entry: config.weights. EntrySet ()) {String RouteID = entry.getKey(); // Loop RouteID- weight for (map.entry <String, Integer> Entry: config.weights. Integer weight = entry.getValue(); Double nomalizedWeight = weight/(Double) weightsSum; / / record RouteID - standardized weight config. NormalizedWeights. Put (RouteID, nomalizedWeight); // Record the subscript -routeId config.rangeindexes. Put (index.getAndIncrement(), RouteID); } // Clear the previous range config.ranges. Clear (); // The first value of the range must be 0.0 config.ranges. Add (0.0); / / structure interval ranges 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); } groupWeights.put(group, config); }}Copy the code

4. Operation stage

WeightCalculatorWebFilter

WeightCalculatorWebFilter# filter traverse all groups, each group is determined by a random number of the actual match routeId, mapping relationship will group – routeId, save to ServerWebExchange attributes.

private Random random = new Random(); GroupWeightConfig private Map<String, GroupWeightConfig> groupWeights = new ConcurrentHashMap<>(); public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {ServerWebExchange WEIGHT_ATTR Value is the RouteID Map<String, String> weights = getWeights(exchange); // loop all groups for (String group: groupweights.keyset ()) {GroupWeightConfig config = groupweights.get (group); if (config == null) { continue; } // Get a 0-1 random number double r = this.random.nextdouble (); List<Double> ranges = config.ranges; For (int I = 0; i < ranges.size() - 1; I ++) {if (r >= ranges. Get (I) &&r < ranges. Get (I + 1)) {// Obtain the corresponding RouteID String RouteID = config.rangeIndexes.get(i); WEIGHT_ATTR weights. Put (group, routeId); break; } } } return chain.filter(exchange); }Copy the code

GatewayPredicate

WeightRoutePredicateFactory created GatewayPredicate according to ServerWebExchange routing match.

@Override public Predicate<ServerWebExchange> apply(WeightConfig config) { return new GatewayPredicate() { // Verify whether ServerWebExchange matches the current route. @Override public Boolean test(ServerWebExchange Exchange) {// The mapping relationship from WEIGHT_ATTR grouping and routing ID (from WeightCalculatorWebFilter) Map < String, String> weights = exchange.getAttributeOrDefault(WEIGHT_ATTR, Collections.emptyMap()); / / get the current judgment from GATEWAY_PREDICATE_ROUTE_ATTR routing ID / / this property is in RoutePredicateHandlerMapping# lookupRoute into claims before the match in the String routeId = exchange.getAttribute(GATEWAY_PREDICATE_ROUTE_ATTR); String group = config.getGroup(); If (weights. ContainsKey (group)) {// Obtain the randomly selected route ID by the group name. String chosenRoute = weights. Return routeid.equals (chosenRoute); // check whether the current routeId matches the randomly selected routeId. } return false; }}; }Copy the code

conclusion

  • The abstraction of the weight configuration isWeightConfigandGroupWeightConfig.
  • The configuration initialization of weighted routes is triggered by Spring events fromWeightCalculatorWebFilterProcess and assemble member variablesprivate Map<String, GroupWeightConfig> groupWeights.
  • Operation phaseWeightCalculatorWebFilterAs aWebFilterThe route corresponding to each group is determined by random number and addedServerWebExchangeAttribute (WEIGHT_ATTR) in theWeightRoutePredicateFactoryTo create theGatewayPredicateThe current route (GATEWAY_PREDICATE_ROUTE_ATTR) and the corresponding route (WEIGHT_ATTR) are used to determine whether the current route matches.