2020 teaches you the latest Spring Cloud Ribbon source code analysis

Code preparation

Dependency relationship

+------------+              +------------+
|            |              |            |
|            |              |            |
|            |              |            |
|            |              |            |
|  consumer  +------------> |   provider |
|            | RestTemplate |            |
|            |              |            |
|            |              |            |
|            |              |            |
+------------+              +------------+

pom dependence

Just join the nacos service discovery, which internally references the spring cloud ribbon dependency

<dependency>
  <groupId>com.alibaba.cloud</groupId>
  <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

Call client

Let's start with the simplest RestTemplate call here

@Bean
@LoadBalanced
public RestTemplate restTemplate() {
	return new RestTemplate();
}

// Controller uses restTemplate to call service provider interface
ResponseEntity<String> forEntity = restTemplate.getForEntity("http://provider/req", String.class);

Source code analysis

Create call interceptor

1. Get the RestTemplate of all @ LoadBalanced Tags

public class LoadBalancerAutoConfiguration {
	@LoadBalanced
	@Autowired(required = false)
	private List<RestTemplate> restTemplates = Collections.emptyList();
}

2. Add LoadBalancerInterceptor processing logic

  • Spring retry was not introduced
@Bean
public LoadBalancerInterceptor ribbonInterceptor() {
	return new LoadBalancerInterceptor();
}
  • Spring retry was introduced using
@Bean
@ConditionalOnMissingBean
public RetryLoadBalancerInterceptor ribbonInterceptor() {
	return new RetryLoadBalancerInterceptor();
}
  • LoadBalancerInterceptor business logic
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
    @Override
    public ClientHttpResponse intercept() {
        final URI originalUri = request.getURI();
        // http://demo-provider/req intercept demo provider service name
        String serviceName = originalUri.getHost();

        // Default injected RibbonAutoConfiguration.RibbonLoadBalancerClient
        return this.loadBalancer.execute(serviceName,
                // Create request object
                this.requestFactory.createRequest(request, body, execution));
    }
}

Execution interceptor

3. RibbonLoadBalancerClient execution

//RibbonLoadBalancerClient injected by default by RibbonAutoConfiguration
@Bean
@ConditionalOnMissingBean(LoadBalancerClient.class)
public LoadBalancerClient loadBalancerClient() {
  return new RibbonLoadBalancerClient(springClientFactory());
}

4.execute

public class RibbonLoadBalancerClient implements LoadBalancerClient {
    public <T> T execute(){
        //Get specific Iload balancer implementation
        ILoadBalancer loadBalancer = getLoadBalancer(serviceId);

        // Call ILoadBalancer to get Server
        Server server = getServer(loadBalancer, hint);
        RibbonServer ribbonServer = new RibbonServer(serviceId, server,
                isSecure(server, serviceId),
                serverIntrospector(serviceId).getMetadata(server));

        //Get the status recorder and save the selected server
        RibbonLoadBalancerContext context = this.clientFactory
                .getLoadBalancerContext(serviceId);
        RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server);
        T returnVal = request.apply(serviceInstance);
        statsRecorder.recordStats(returnVal);
        return returnVal;
    }
}

Get ILoadBalancer

5 SpringClientFactory

// Implementation of generating LoadBalancer from bean factory
protected ILoadBalancer getLoadBalancer(String serviceId) {
	return this.springClientFactory.getLoadBalancer(serviceId);
}

// For the specific generation logic, see RibbonClientConfiguration. This Bean will be created only when it is called by the factory
@Bean
@ConditionalOnMissingBean
public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,
IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
	return new ZoneAwareLoadBalancer<>();
}

6. Create the dependency elements of LoadBalancer

For the above default implementation, please refer to ribbonclientconfiguration. Zoneawareloadebalancer

Get service instance

//Server server = getserver (loadbalancer, hint); 4. Extract method
protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
	return loadBalancer.chooseServer(hint != null ? hint : "default");
}

7. ZoneAwareLoadBalancer

public class ZoneAwareLoadBalancer{
    public ZoneAwareLoadBalancer() {
        // Call the parent class initialization method. The timing tasks for instance maintenance will be enabled here (refer to the extension for details)
        super(clientConfig, rule, ping, serverList, filter, serverListUpdater);
    }
    @Override
    public Server chooseServer(Object key) {
        // If the Nacos service used is found, there is no concept of Zone, and the implementation of the parent class is called directly
        if (!ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <= 1) {
            return super.chooseServer(key);
        }
        // Here is the concept of Zone, for example Eureka (specific)
        ...
        return server;
    }
}
  • Parent class calls IRule to implement selection Server
public Server chooseServer(Object key) {
	return rule.choose(key);
}

8.PredicateBasedRule selection rule

public abstract class PredicateBasedRule {
    @Override
    public Server choose(Object key) {
        ILoadBalancer lb = getLoadBalancer();
        // Get assertion configuration
        Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key);
        if (server.isPresent()) {
            return server.get();
        } else {
            return null;
        }
    }
}

9. Zoneaavoidancepredicate service list assertion

public class ZoneAvoidancePredicate {
    @Override
    public boolean apply(@Nullable PredicateKey input) {
        if (!ENABLED.get()) {
            return true;
        }
        // Or get the region configuration. If Nacos is used, it directly returns true
        String serverZone = input.getServer().getZone();
        if (serverZone == null) {
            // there is no zone information from the server, we do not want to filter
            // out this server
            return true;
        }
        // Regional high availability judgment
        ...
    }
}

Extension: ServerList maintenance

Initialize ServerList

In 6. Above, create the dependency element of LoadBalancer, the instance instance table of ServerList target service, and the specific service discovery client implementation. Let's take a look at the implementation of Nacos

public class NacosServerList extends AbstractServerList<NacosServer> {
    @Override
    public List<NacosServer> getInitialListOfServers() {
        return getServers();
    }

    @Override
    public List<NacosServer> getUpdatedListOfServers() {
        return getServers();
    }

    private List<NacosServer> getServers() {
        String group = discoveryProperties.getGroup();
        //Call Nacos SDK to query the instance list
        List<Instance> instances = discoveryProperties.namingServiceInstance()
                .selectInstances(serviceId, group, true);
        // Type conversion
        return instancesToServerList(instances);

    }
}

Update ServerListUpdater

  • Update operation after ServerList initialization through PollingServerListUpdater
public class PollingServerListUpdater implements ServerListUpdater {
    @Override
    public synchronized void start(final UpdateAction updateAction) {
        // Update task to updateAction
        final Runnable wrapperRunnable = () -> {
            updateAction.doUpdate();
            lastUpdated = System.currentTimeMillis();
        };

        // Enable background thread to execute updateAction regularly
        scheduledFuture = getRefreshExecutor().scheduleWithFixedDelay(
                wrapperRunnable,
                initialDelayMs,
                refreshIntervalMs,
                TimeUnit.MILLISECONDS
        );
    }
}
    • updateAction implementation
    public void doUpdate() {
    	DynamicServerListLoadBalancer.this.updateListOfServers();
    }
    Copy code
    public class PollingServerListUpdater implements ServerListUpdater {
        public void updateListOfServers() {
            List<T> servers = new ArrayList();
            
            // Call NacosServiceList to get the list of all services
            servers = this.serverListImpl.getUpdatedListOfServers();
            
            // If the configuration instance filter is performing filtering
            if (this.filter != null) {
                servers = this.filter.getFilteredListOfServers((List)servers);
            }
            
            // Update LoadBalancer service list
            this.updateAllServerList((List)servers);
        }
    }

    Extensions: Server state maintenance

    • setupPingTask() is triggered when LoadBalancer is initially constructed
    public BaseLoadBalancer() {
      this.name = DEFAULT_NAME;
      this.ping = null;
      setRule(DEFAULT_RULE);
      // Enable ping check task
      setupPingTask();
      lbStats = new LoadBalancerStats(DEFAULT_NAME);
    }
    • setupPingTask
    void setupPingTask() {
      // Whether it is possible to ping. The default DummyPing directly skips not to execute
      if (canSkipPing()) {
        return;
      }
      // Execute PingTask
      lbTimer.schedule(new BaseLoadBalancer.PingTask(), 0, pingIntervalSeconds * 1000);
      // Open task
      new BaseLoadBalancer.Pinger(pingStrategy).runPinger();
    }
    • SerialPingStrategy serial execution logic
    // Serial scheduling execution of Iping logic
    private static class SerialPingStrategy implements IPingStrategy {
      @Override
      public boolean[] pingServers(IPing ping, Server[] servers) {
        int numCandidates = servers.length;
        boolean[] results = new boolean[numCandidates];
    
        for (int i = 0; i < numCandidates; i++) {
          results[i] = false; /* Default answer is DEAD. */
          if (ping != null) {
            results[i] = ping.isAlive(servers[i]);
          }
    
        }
        return results;
      }
    }
    • Call url to judge availability
    public class PingUrl implements IPing {
        public boolean isAlive(Server server) {
            urlStr = urlStr + server.getId();
            urlStr = urlStr + this.getPingAppendString();
            boolean isAlive = false;
            HttpClient httpClient = new DefaultHttpClient();
            HttpUriRequest getRequest = new HttpGet(urlStr);
            String content = null;
    
            HttpResponse response = httpClient.execute(getRequest);
            content = EntityUtils.toString(response.getEntity());
            isAlive = response.getStatusLine().getStatusCode() == 200;
            return isAlive;
        }
    }

    Extension: RibbonClient lazy load processing

    It can be seen from the above that by default, the Ribbon will not create a LoadBalancer until the first request. This lazy loading mechanism will cause the delay of the first call to the service after the service is started, and even lead to the timeout fuse of the integrated circuit breaker (hystrix).

    To solve this problem, we will configure the hungry loading of Ribbon

    ribbon:
      eager-load:
        clients:
          - provider
    • After the RibbonApplicationContextInitializer service is started, it automatically calls the factory to create the required ribbon clients in advance
    public class RibbonApplicationContextInitializer
            implements ApplicationListener<ApplicationReadyEvent> {
        private final List<String> clientNames;
    
        protected void initialize() {
            if (clientNames != null) {
                for (String clientName : clientNames) {
                    this.springClientFactory.getContext(clientName);
                }
            }
        }
        @Override
        public void onApplicationEvent(ApplicationReadyEvent event) {
            initialize();
        }
    
    }

    All of the above are my own thoughts. You are welcome to share them. By the way, please pay attention to them. Partners with ideas can comment or send me private letters~

     

 

 

 

 

 

 

 


 

Keywords: Programming Spring SDK

Added by adrian28uk on Mon, 02 Mar 2020 09:09:49 +0200