Today we will learn about the Ribbon, a component of Spring Cloud. The main content of this article is as follows:

  • What is theRibbon
  • RibbonThe use of
  • RibbonImplementation principle of

For those of you who are familiar with the Ribbon, you can skip the first two sections.

1 What is Ribbon

When learning a technology, we should first understand what the technology does and what needs it can help us achieve. In a microservice architecture project, calls between services are inevitable, and multiple nodes are deployed for each service to avoid single points of failure. A simple example is as follows:

In the figure above, when Service1 calls service2, it needs to select a node from service2’s service list, get its IP address and port, and then initiate a request. This is a load balancing algorithm. The Ribbon is a component that enables this function.

2. Use of Ribbon

Now that we know what the Ribbon can do, let’s use a simple example to learn how to use the Ribbon. The project structure is relatively simple, with two modules as follows:

  • user-serviceaSpringBootWrite a Web project that defines an interface.
  • ribbon-sampleaSpringBootWritten web project, introducedRibbon, the calluser-serviceThe interface

We modify the port of user-service and start two to simulate multi-node. The result is shown in the following figure:

2.1 user-serviceThe module

This module is a SpringBoot-written Web service that provides an interface as follows:

@RestController
@RequestMapping(value = "/user")
public class UserController {
    @Value("${server.port}")
    private int port;
    
    @GetMapping(value = "/{name}")
    public String getByName(@PathVariable String name) {
        System.out.println(port);
        returnname; }}Copy the code

2.2 ribbon-sampleThe module

Introduce the following Ribbon dependencies:

<! -- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-ribbon -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
    <version>2.2.9. RELEASE</version>
</dependency>
Copy the code

Then I define an interface in this project and use RestTemplate to call the interface in user-service. For simplicity, I define the interface directly in the startup class, as follows:

@RestController
@SpringBootApplication
public class RibbonServiceBootstrap {
    public static void main(String[] args) {
        SpringApplication.run(RibbonServiceBootstrap.class, args);
    }
​
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {
        return restTemplateBuilder.build();
    }
​
    @Resource
    private RestTemplate restTemplate;
​
    @GetMapping(value = "/ribbon/user/{name}")
    public String getUserByName(@PathVariable String name) {
        return restTemplate.getForObject("http://user-service/user/"+ name, String.class); }}Copy the code

Configure the user-service address in the application.yml configuration file as follows:

user-service:
  ribbon:
    listOfServers: localhost:8080,localhost:8081
Copy the code

Note that the @loadBalanced annotation is added to the method to inject RestTemplate into the Spring container. We access the interface in the user-Service using the service name instead of IP :port.

2.3 test

After writing the code, we start these two services, including user-service needs to start two, respectively 8080 and 8081 port, Then in the browser input ribbon – sample service interface in http://localhost:8082/ribbon/user/zhangsan, found that can be normal access:

If you look at the output of the user-service console a few more times, you will see that the two services will print their port numbers alternately, which you can easily imagine as a polling method.

3 RibbonImplementation principle of

In the example above, we can use the Ribbon’s functionality by adding an @loadBalanced annotation to the RestTemplate creation method. This annotation is defined as follows:

@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
​
}
Copy the code

The @qualifier annotation in this annotation is used to add an identity to the object added to the Spring IOC container, and then we can get the corresponding identity of the object.

3.1 Loading Logic

After understanding the @ LoadBalanced annotation, we look at how service startup phase load related classes, for the entrance to the loading org.springframework.cloud.net flix. Ribbon. RibbonAutoConfiguration, LoadBalancerClient: LoadBalancerClient: LoadBalancerClient: LoadBalancerClient: LoadBalancerClient: LoadBalancerClient: LoadBalancerClient: LoadBalancerClient: LoadBalancerClient

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

This class is modified by the @autoConfigureBefore annotation, in which the configured class is loaded first. The source code is as follows:

@AutoConfigureBefore({ LoadBalancerAutoConfiguration.class, AsyncLoadBalancerAutoConfiguration.class })
Copy the code

Through the above code we see loads org. Springframework. Cloud. Client. Loadbalancer. LoadBalancerAutoConfiguration class. In this class we will find the following variable:

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

This variable stores variables that are modified by the @loadBalanced annotation.

This class creates objects and stores them in the Spring IOC container. The three main instantiated classes are as follows:

  • LoadBalancerInterceptor
  • RestTemplateCustomizer
  • SmartInitializingSingleton

The logic is as follows:

@Bean
public LoadBalancerInterceptor loadBalancerInterceptor(LoadBalancerClient loadBalancerClient, LoadBalancerRequestFactory requestFactory) {
    return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
}
​
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(final LoadBalancerInterceptor loadBalancerInterceptor) {
    return restTemplate -> {
        List<ClientHttpRequestInterceptor> list = new ArrayList<>(restTemplate.getInterceptors());
        list.add(loadBalancerInterceptor);
        restTemplate.setInterceptors(list);
    };
}
​
@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
    final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
    return () -> restTemplateCustomizers.ifAvailable(customizers -> {
        for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
            for(RestTemplateCustomizer customizer : customizers) { customizer.customize(restTemplate); }}}); }Copy the code

Spring Cloud adds a LoadBalancerInterceptor object to the RestTemplate class. This object is an interceptor. There is only one intercept() method in this class.

@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
                                    final ClientHttpRequestExecution execution) throws IOException {
    // Parse out the requested URI object
    final URI originalUri = request.getURI();
    // The host in the request example is user-serviceString serviceName = originalUri.getHost(); Assert.state(serviceName ! =null."Request URI does not contain a valid hostname: " + originalUri);
    return this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));
}
Copy the code

3.2 Execution Phase

Use RestTemplate when they send the request to enter the interceptor approach, we will walk down from the source to RibbonLoadBalancerClient. The execute () method, the logic is as follows:

@Override
public <T> T execute(String serviceId, LoadBalancerRequest<T> request)
    throws IOException {
    return execute(serviceId, request, null);
}
​
public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)
    throws IOException {
    // Get the LoadBalancer object
    ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
    // Get the Server object
    Server server = getServer(loadBalancer, hint);
    if (server == null) {
        throw new IllegalStateException("No instances available for " + serviceId);
    }
    RibbonServer ribbonServer = new RibbonServer(serviceId, server,
                                                 isSecure(server, serviceId),
                                                 serverIntrospector(serviceId).getMetadata(server));
​
    return execute(serviceId, ribbonServer, request);
}
Copy the code

The ILoadBalancer and Server objects are retrieved in the above code, respectively.

The getLoadBalancer code is not pasted here; the logic here is to get the object from the Spring context and return a ZoneAwareLoadBalancer object.

The logic of the getServer() method is as follows:

protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
    if (loadBalancer == null) {
        return null;
    }
    // Use 'default' on a null hint, or just pass it on?
    returnloadBalancer.chooseServer(hint ! =null ? hint : "default");
}
Copy the code

Follow the source found will be called to BaseLoadBalancer. ChooseServer () method, the logic of this method is as follows:

public Server chooseServer(Object key) {
    if (counter == null) {
        counter = createCounter();
    }
    counter.increment();
    if (rule == null) {
        return null;
    } else {
        try {
            return rule.choose(key);
        } catch (Exception e) {
            logger.warn("LoadBalancer [{}]: Error choosing server for key {}", name, key, e);
            return null; }}}Copy the code

The rules for load balancing are implemented in IRule. The Ribbon provides the following rules:

  • RoundRobbinRulepolling
  • RandomRulerandom
  • WeightedResponseTimeRuleSelect based on response weight
  • MyGrayBalancerRule
  • RetryRulePoll and retry the rules
  • BestAvailableRuleIgnore the server that failed to connect
  • ZoneAvoidanceRule
  • AvailabilityFilteringRulePolling from available services

The default load balancing rule in Spring Cloud Ribbon is round robin.

BaseLoadBalancer: BaseLoadBalancer: BaseLoadBalancer: BaseLoadBalancer: BaseLoadBalancer: BaseLoadBalancer: BaseLoadBalancer: BaseLoadBalancer: BaseLoadBalancer: BaseLoadBalancer: BaseLoadBalancer: BaseLoadBalancer

// List of all services
@Monitor(name = PREFIX + "AllServerList", type = DataSourceType.INFORMATIONAL)
protected volatile List<Server> allServerList = Collections    .synchronizedList(new ArrayList<Server>());
// List of surviving services
@Monitor(name = PREFIX + "UpServerList", type = DataSourceType.INFORMATIONAL)
protected volatile List<Server> upServerList = Collections    .synchronizedList(new ArrayList<Server>());
Copy the code

3.3 Service list Maintenance

The Ribbon maintains two fields that hold service lists in the load balancer. Here’s how the Ribbon maintains service lists.

Remember what the ILoadBalancer instance was that we got in the code above? Is a ZoneAwareLoadBalancer object, the object is in the RibbonClientConfiguration class is added to the Spring container, the code is as follows:

@Bean
@ConditionalOnMissingBean
public ILoadBalancer ribbonLoadBalancer(IClientConfig config, ServerList
       
         serverList, ServerListFilter
        
          serverListFilter, IRule rule, IPing ping, ServerListUpdater serverListUpdater)
        
        {
   if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {
      return this.propertiesFactory.get(ILoadBalancer.class, config, name);
   }
   return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,
         serverListFilter, serverListUpdater);
}

public ZoneAwareLoadBalancer(IClientConfig clientConfig, IRule rule, IPing ping, ServerList
       
         serverList, ServerListFilter
        
          filter, ServerListUpdater serverListUpdater)
        
        {
    super(clientConfig, rule, ping, serverList, filter, serverListUpdater);
}

public DynamicServerListLoadBalancer(IClientConfig clientConfig, IRule rule, IPing ping, ServerList
       
         serverList, ServerListFilter
        
          filter, ServerListUpdater serverListUpdater)
        
        {
    super(clientConfig, rule, ping);
    this.serverListImpl = serverList;
    this.filter = filter;
    this.serverListUpdater = serverListUpdater;
    if (filter instanceof AbstractServerListFilter) {
        ((AbstractServerListFilter) filter).setLoadBalancerStats(getLoadBalancerStats());
    }
    restOfInit(clientConfig);
}
Copy the code

According to the above source we will see will be called to DynamicServerListLoadBalancer. RestOfInit method, in this method can maintain the above two fields, the method of the source code is as follows:

void restOfInit(IClientConfig clientConfig) {
    boolean primeConnection = this.isEnablePrimingConnections();
    // turn this off to avoid duplicated asynchronous priming done in BaseLoadBalancer.setServerList()
    this.setEnablePrimingConnections(false);
    // Start a scheduled task to update the service list periodically
    enableAndInitLearnNewServersFeature();
    // Get the list of services
    updateListOfServers();
    if (primeConnection && this.getPrimeConnections() ! =null) {
        this.getPrimeConnections()
            .primeConnections(getReachableServers());
    }
    this.setEnablePrimingConnections(primeConnection);
    LOGGER.info("DynamicServerListLoadBalancer for client {} initialized: {}", clientConfig.getClientName(), this.toString());
}
Copy the code

In this method the main logic in enableAndInitLearnNewServersFeature and updateListOfServers two method, we first take a look at updateListOfServers method, the logic of this method is as follows:

public void updateListOfServers(a) {
    List<T> servers = new ArrayList<T>();
    if(serverListImpl ! =null) {
        / / get ConfigurationBasedServerList from the configuration file
        servers = serverListImpl.getUpdatedListOfServers();
        LOGGER.debug("List of Servers for {} obtained from Discovery client: {}",
                     getIdentifier(), servers);

        if(filter ! =null) {
            servers = filter.getFilteredListOfServers(servers);
            LOGGER.debug("Filtered List of Servers for {} obtained from Discovery client: {}",
                         getIdentifier(), servers);
        }
    }
    updateAllServerList(servers);
}

protected void updateAllServerList(List<T> ls) {
    // other threads might be doing this - in which case, we pass
    if (serverListUpdateInProgress.compareAndSet(false.true)) {
        try {
            for (T s : ls) {
                s.setAlive(true); // set so that clients can start using these
                // servers right away instead
                // of having to wait out the ping cycle.
            }
            If Ping is not used, upServerList = allServerList
            setServersList(ls);
            // The Ping rule is used to determine the surviving service
            super.forceQuickPing();
        } finally {
            serverListUpdateInProgress.set(false); }}}Copy the code

EnableAndInitLearnNewServersFeature method logic is as follows:

public void enableAndInitLearnNewServersFeature(a) {
    LOGGER.info("Using serverListUpdater {}", serverListUpdater.getClass().getSimpleName());
    serverListUpdater.start(updateAction);
}
Copy the code

In will call down to PollingServerListUpdater. The start method, this method will start timing task, time call updateListOfServers method, screenshots are as follows:

In IPing, the default value is true, so we won’t post it here. You can check the source code by yourself.

4 summarizes

Article here is over today, in this article mainly introduced how to use native components Ribbon for the service call, in the example in this article we don’t introduce registry, is write service list in the configuration file to death, after introduce registry we’ll look at how Ribbon is used together with the registry.

  • nativeRibbonThe use of
  • View the source code, find the entry is key, the main configuration classes areRibbonClientConfiguration,RibbonAutoConfigurationandLoadBalancerAutoConfiguration
  • RibbonCore components in
    • ILoadBalancerLoad balancer
    • IRuleLoad balancing rule
    • ServerListGetting a list of services
    • IPingUsed to verify survival
  • Update rules for service lists

Welcome to pay attention to the public number [Bug handling small expert] continue to update Java related knowledge