Column catalog

  1. Spring Cloud OpenFeign source code parsing
  2. Spring Cloud Ribbon source code
  3. Spring Cloud Alibaba Sentinel source code analysis
  4. Spring Cloud Gatway source code analysis
  5. Spring Cloud Alibaba Nacos source code analysis

The code for

dependencies

+------------+              +------------+
|            |              |            |
|            |              |            |
|            |              |            |
|            |              |            |
|  consumer  +------------> |   provider |
|            | RestTemplate |            |
|            |              |            |
|            |              |            |
|            |              |            |
+------------+              +------------+
Copy the code

Pom depends on

Add nacOS service discovery, which internally references spring-Cloud-ribbon dependencies

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

Calling client

We start with the Ribbon here with the simplest RestTemplate call

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

// The Controller invokes the service provider interface using the restTemplate
ResponseEntity<String> forEntity = restTemplate.getForEntity("http://provider/req", String.class);

Copy the code

The source code parsing

Create a call interceptor

1. Get it all@LoadBalancedOf the tagRestTemplate

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

2. AddLoadBalancerInterceptorProcessing logic

  • No introductionspring-retryUsing the
@Bean
public LoadBalancerInterceptor ribbonInterceptor(a) {
	return new LoadBalancerInterceptor();
}
Copy the code
  • The introduction ofspring-retryUsing the
@Bean
@ConditionalOnMissingBean
public RetryLoadBalancerInterceptor ribbonInterceptor(a) {
	return new RetryLoadBalancerInterceptor();
}
Copy the code
  • LoadBalancerInterceptor business logic
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
    @Override
    public ClientHttpResponse intercept(a) {
        final URI originalUri = request.getURI();
        // http://demo-provider/req Intercepts the demo-provider service name
        String serviceName = originalUri.getHost();

        / / the default injection RibbonAutoConfiguration RibbonLoadBalancerClient
        return this.loadBalancer.execute(serviceName,
                // Create the request object
                this.requestFactory.createRequest(request, body, execution)); }}Copy the code

Execution interceptor

3. RibbonLoadBalancerClient execution

/ / RibbonLoadBalancerClient RibbonAutoConfiguration injection by default
@Bean
@ConditionalOnMissingBean(LoadBalancerClient.class)
public LoadBalancerClient loadBalancerClient(a) {
  return new RibbonLoadBalancerClient(springClientFactory());
}
Copy the code

4. The execute execution

public class RibbonLoadBalancerClient implements LoadBalancerClient {
    public <T> T execute(a){
        // Get the specific ILoadBalancer 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 logger to save the selected server
        RibbonLoadBalancerContext context = this.clientFactory
                .getLoadBalancerContext(serviceId);
        RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server);
        T returnVal = request.apply(serviceInstance);
        statsRecorder.recordStats(returnVal);
        returnreturnVal; }}Copy the code

Get ILoadBalancer

5 SpringClientFactory

// The bean factory generates an implementation of LoadBalancer
protected ILoadBalancer getLoadBalancer(String serviceId) {
	return this.springClientFactory.getLoadBalancer(serviceId);
}

/ / specific see RibbonClientConfiguration generation logic, the Bean will create only if the factory calls
@Bean
@ConditionalOnMissingBean
public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,
IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
	return new ZoneAwareLoadBalancer<>();
}
Copy the code

6. Create a LoadBalancer dependency

The name of the The default implementation role
IClientConfig DefaultClientConfigImpl Ribbon client configuration parameters, such as timeout Settings and compression Settings
ServerList NacosServerList A table of instance instances of the target service, the specific service discovered by the client implementation
ServerListFilter ZonePreferenceServerListFilter Filtering logic for ServerList instance lists
IRule ZoneAvoidanceRule Server selection rule for load balancing
IPing DummyPing A method implementation that verifies that a service is available
ServerListUpdater PollingServerListUpdater Implementation of operation for ServerList update

Above. The default implementation reference RibbonClientConfiguration ZoneAwareLoadBalancer

Obtaining service Instances

//Server server = getServer(loadBalancer, hint); 4. Excute method
protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
	returnloadBalancer.chooseServer(hint ! =null ? hint : "default");
}
Copy the code

7. ZoneAwareLoadBalancer

public class ZoneAwareLoadBalancer{
    public ZoneAwareLoadBalancer(a) {
        // Call the parent class initialization method. This will enable scheduled tasks for instance maintenance.
        super(clientConfig, rule, ping, serverList, filter, serverListUpdater);
    }
    @Override
    public Server chooseServer(Object key) {
        // If the Nacos service used is discovered, there is no concept of Zone, and the parent class implementation is called directly
        if(! ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <=1) {
            return super.chooseServer(key);
        }
        // The following is the concept of a Zone such as Eureka..returnserver; }}Copy the code
  • The parent class callIRuleImplement Server selection
public Server chooseServer(Object key) {
	return rule.choose(key);
}
Copy the code

8.PredicateBasedRule Select rules

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

9. ZoneAvoidancePredicate service list assertion

public class ZoneAvoidancePredicate {
    @Override
    public boolean apply(@Nullable PredicateKey input) {
        if(! ENABLED.get()) {return true;
        }
        // Still get the locale configuration, and return true if Nacos is used
        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;
        }
        // Area high availability judgment. }}Copy the code

Extension: ServerList maintenance

Initialize the ServerList

Above 6. Create a LoadBalancer dependency element, in ServerList target service instance instance table, specific service discovery client implementation. Let’s look at the implementation of Nacos

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

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

    private List<NacosServer> getServers(a) {
        String group = discoveryProperties.getGroup();
        // Call nacos-sdk to query the list of instances
        List<Instance> instances = discoveryProperties.namingServiceInstance()
                .selectInstances(serviceId, group, true);
        // Type conversion
        returninstancesToServerList(instances); }}Copy the code

Update ServerListUpdater

  • The ServerList update succeeds after initializationPollingServerListUpdater
public class PollingServerListUpdater implements ServerListUpdater {
    @Override
    public synchronized void start(final UpdateAction updateAction) {
        // Update the task to the updateAction implementation
        final Runnable wrapperRunnable = () -> {
            updateAction.doUpdate();
            lastUpdated = System.currentTimeMillis();
        };

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

Extension: Server status maintenance

  • LoadBalancer is triggered when the initial construction is performedsetupPingTask()
public BaseLoadBalancer(a) {
  this.name = DEFAULT_NAME;
  this.ping = null;
  setRule(DEFAULT_RULE);
  // Start the ping check task
  setupPingTask();
  lbStats = new LoadBalancerStats(DEFAULT_NAME);
}
Copy the code
  • setupPingTask
void setupPingTask(a) {
  // Whether the ping function can be performed, the default DummyPing function is skipped
  if (canSkipPing()) {
    return;
  }
  / / PingTask execution
  lbTimer.schedule(new BaseLoadBalancer.PingTask(), 0, pingIntervalSeconds * 1000);
  // Start the task
  new BaseLoadBalancer.Pinger(pingStrategy).runPinger();
}
Copy the code
  • SerialPingStrategy Serial execution logic
// Serial scheduling executes 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]); }}returnresults; }}Copy the code
  • Call the URL to determine 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;
        returnisAlive; }}Copy the code

Extension: RibbonClient lazy load handling

By default, the Ribbon creates a LoadBalancer only on the first request. This lazy loading mechanism can cause delays in the first service invocation after the service is started, and even lead to time-out fuses in hystrix.

To address this issue, we will configure the Ribbon for hunger loading

ribbon:
  eager-load:
    clients:
      - provider
Copy the code
  • RibbonApplicationContextInitializerService startup automatically invokes the factory to create the ribbon Clients required in advance
public class RibbonApplicationContextInitializer
        implements ApplicationListener<ApplicationReadyEvent> {
    private final List<String> clientNames;

    protected void initialize(a) {
        if(clientNames ! =null) {
            for (String clientName : clientNames) {
                this.springClientFactory.getContext(clientName); }}}@Override
    public void onApplicationEvent(ApplicationReadyEvent event) { initialize(); }}Copy the code

The follow-up plan

The Ribbon, Hystrix, Sentinel, Nacos and other components will be updated later.

Note: the above image materials (omnigraffle & 100 million images) can be obtained from the public account JAVA Architecture Diary

“★★★★” based on Spring Boot 2.2, Spring Cloud Hoxton & Alibaba, OAuth2 RBAC authority management system