Ribbon

The Ribbon is a client-side IPC library and Netflix’s open source client-side load balancer. It provides the following capabilities:

Load balancing

Fault tolerance

Support for asynchrony and multiple protocols (HTTP, TCP, UDP) in the corresponding model

Caching and batching

Why use the Ribbon

In our system (distributed system), there is process communication between services; Before using the Ribbon, you need to manually implement the load balancer. With the Ribbon, we can automatically get service lists from nacOS services and use user-specified load balancing algorithms to calculate the instances that need to be invoked. This enables us to achieve load balancing easily and efficiently. The Ribbon is scalable enough to meet 99.9999% of production scenarios.

There are two load balancing modes

Server-side load balancing

Load balancing on the client

Manually implement the load balancer on the client side

No load balancer is used

@Override
    public ShareDTO findById(Integer id) {
        // Get share details
        Share share = baseMapper.selectById(id);
        // Get the publisher ID
        Integer userId = share.getUserId();

        List<ServiceInstance> instances = discoveryClient.getInstances("user-center");
        String url = instances.stream().map(instance -> instance.getUri().toString() + "/users/{id}").findFirst().orElse("");
        // Remotely invoke the user center service interface
        UserDTO userDTO = restTemplate.getForObject(url, UserDTO.class, userId);

        // Message assembly
        ShareDTO shareDTO = ShareDTO.builder()
                .wxNickname(userDTO.getWxNickname())
                .build();
        BeanUtils.copyProperties(share, shareDTO);
        return shareDTO;
    }
Copy the code

Manual Load balancer implementation (random)

@Override
    public ShareDTO findById(Integer id) {
        // Get share details
        Share share = baseMapper.selectById(id);
        // Get the publisher ID
        Integer userId = share.getUserId();

        List<ServiceInstance> instances = discoveryClient.getInstances("user-center");
        List<String> urls = instances.stream().map(instance -> instance.getUri().toString() + "/users/{id}").collect(Collectors.toList());

        // Random algorithm
        int i = ThreadLocalRandom.current().nextInt(urls.size());
        // Remotely invoke the user center service interface
        UserDTO userDTO = restTemplate.getForObject(urls.get(i), UserDTO.class, userId);

        // Message assembly
        ShareDTO shareDTO = ShareDTO.builder()
                .wxNickname(userDTO.getWxNickname())
                .build();
        BeanUtils.copyProperties(share, shareDTO);
        return shareDTO;
    }
Copy the code

The Ribbon implements load balancing

  1. Add dependencies (if you are using NacOS Discovery, you do not need to introduce ribbon dependencies separately)

  1. Write notes

    // If you use RestTemplate to integrate the Ribbon, annotate the RestTemplate
    @Bean
    @LoadBalanced
    RestTemplate restTemplate(a) {
      return new RestTemplate();
    }
    Copy the code
  2. Write configuration (not configured)

Of Ribbon

interface role The default value
IClientConfig Reading configuration DefaultClientConfigImpl
IRule Load balancing rule, select instances ZoneAvoidanceRule
IPing Filter instances that cannot ping DummyPing
ServerList Give the Ribbon a list of instances Ribbon: ConfigurantionBasedServerList.Spring Cloud Alibaba: NacosServerList
ServerListFilter Filter out instances that do not meet the criteria ZonePreferenceServerListFilter
ILoadBalancer The entry of the Ribbon ZoneAwareLoadBalancer
ServerListUpdater Update the List strategy given to the Ribbon PollingServerListUpdater

We can implement these interfaces and customize our requirements.

Load balancing rules built into the Ribbon

Rule name The characteristics of
AvailabilityFilterRule Filter out back-end servers tagged with circuit tripped that repeatedly failed to connect and filter out those with high concurrency or use an AvailabilityPredicate that includes the logic to filter servers, Check the running status of each Server recorded in status.
BestAvailableRule Pick a Server with minimal concurrent requests, inspect servers one by one, and skip them if they tripped.
RandomRule Select a Server at random.
ResponseTimeWeightedRule Have been abandoned. Function as WeightedResponseTimeRule.
RetryRule For the on-machine retry mechanism of the selected load balancing policy, if the Server selection fails during a configuration period, the system tries to use subRule to select an available Server.
RoundRobinRule Poll Select, poll index, and select the Server corresponding to index.
WeightedResponseTimeRule According to the corresponding time weighting, the longer the response time, the smaller the weight, the lower the probability of being selected.
ZoneAvoidanceRule Default rules. Compound judge the performance of the Server Zone and the availability of the Server select the Server,In an environment without zones, similar to RoundRobinRule.

Fine-grained configuration

Java code approach

Note: RibbonConfiguration must be outside the Springboot package scan. (Parent-child context explanation)

  1. Create the Ribbon configuration class for a service

    package com.samir.contentcenter.configuration;
    
    import org.springframework.cloud.netflix.ribbon.RibbonClient;
    import org.springframework.context.annotation.Configuration;
    import ribbonconfiguration.RibbonConfiguration;
    
    @Configuration
    @RibbonClient(name = "user-center", configuration = RibbonConfiguration.class)
    public class UserCenterRibbonConfiguration {}Copy the code
  2. Create a load balancing rule for the current service

    package ribbonconfiguration;
    
    import com.netflix.loadbalancer.IRule;
    import com.netflix.loadbalancer.RandomRule;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class RibbonConfiguration {
        @Bean
        public IRule ribbonRule(a) {
            return newRandomRule(); }}Copy the code

Parent and child context

  1. The @Configuration annotation is also a special Component annotation, and the @SpringBootApplication on top of the bootstrap class is an aggregate annotation that contains the @ComponentScan annotation.

  2. All Component annotations will be scanned (the package and subpackages of the currently launched class).

  3. The Component annotation scanned by @SpringBootApplication is the parent context.

  4. The Ribbon also scans the Component annotation as a subcontext.

  5. Packages of load balancing rules can only be placed outside. Overlapping parent and child contexts can lead to a series of strange problems such as transaction invalidity.

    The Ribbon explains that the @Configuration annotation must be used to configure load balancing rules for clients. Note that this annotation cannot be scanned by @ComponentScan. Otherwise, it will be shared by all @RibbonClients. It becomes a global configuration.

Configuring Attribute Mode

The full path. Ribbon. NFLoadBalancerRuleClassName = rules

The name of the service in the registry
user-center:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule Rule full path
Copy the code

Two ways to compare

Configuration mode advantages disadvantages
Code configuration Code based, more flexible Pay attention to parent-child context issues; Online changes need to be repackaged for distribution
The configuration properties Easy-to-use; Simple and intuitive configuration; Online modification does not need to be repackaged and published (configuration center); Higher priority Extreme scenarios are less flexible than code configuration

The best implementation

  • Use attribute configuration as much as possible, and consider code configuration when attribute mode is not possible.
  • Try to keep the same microservice simple, do not mix the two ways, increase the complexity of the location code. Simple is beautiful.

Global configuration

  • Method 1: Make ComponentSacn parent-child context overlap (strongly not recommended). (See parent context or official documentation)

  • Mode 2: @RibbonClients(defaultConfiguration= XXX.class)

    package com.samir.contentcenter.configuration;
    
    import org.springframework.cloud.netflix.ribbon.RibbonClients;
    import org.springframework.context.annotation.Configuration;
    import ribbonconfiguration.RibbonConfiguration;
    
    @Configuration
    @RibbonClients(defaultConfiguration = RibbonConfiguration.class) // Notice here
    public class UserCenterRibbonConfiguration {}Copy the code

Supported configuration items

Code way

  1. Create the Ribbon configuration class for a service

    package com.samir.contentcenter.configuration;
    
    import org.springframework.cloud.netflix.ribbon.RibbonClient;
    import org.springframework.context.annotation.Configuration;
    import ribbonconfiguration.RibbonConfiguration;
    
    @Configuration
    @RibbonClient(name = "user-center", configuration = RibbonConfiguration.class)
    public class UserCenterRibbonConfiguration {}Copy the code
  2. Create a load balancing rule for the current service

    package ribbonconfiguration;
    
    import com.netflix.loadbalancer.IRule;
    import com.netflix.loadbalancer.RandomRule;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class RibbonConfiguration {
        @Bean
        public IRule ribbonRule(a) {
            return new RandomRule();
        }
      
      	@Bean
        public IPing ping(a) {
            return new PingUrl();
        }
      
      	// The Ribbon provides the corresponding interface
    }
    Copy the code

Configuring Attribute Mode

Use the **.ribbon. Property **. The following attributes:

  • NFLoadBalancerRuleClassName: ILoadBalancer implementation class
  • NFLoadBalancerRuleClassClassName: IRule implementation class
  • NFLoadBalancerPingClassClassName: IPing implementation class
  • NIWSServerListClassName: Implementation class of ServerList
  • NIWSServerListFilterClassName: ServerListFilter implementation class

Hunger is loaded

The following solutions can solve the problem that causes the first request to be slow

# ribbon enables hungry loading (default is off)
ribbon:
  eager-load:
    enabled: true
    clients: user-center Configure which services to enable hungry loading (multiple, split)
Copy the code

Extend the Ribbon

Nacos weights are supported

  1. Weight information can be configured for each instance on the NACOS console. The diagram below:

The larger the threshold value is, the greater the probability of being requested is. The use of thresholds is useful in distributed scenarios, where the performance of our servers is inconsistent, and we can use thresholds to get more requests from good servers.

  1. The Ribbon’s built-in load balancing rules do not support thresholds, so we need to extend the Ribbon with nacOS (implement the IRule interface and inherit AbstractLoadBalancerRule). The following is a partial configuration as a configuration property.

    1. Edit custom load balancing rules

      package com.samir.contentcenter.configuration;
      
      import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
      import com.alibaba.cloud.nacos.ribbon.NacosServer;
      import com.alibaba.nacos.api.exception.NacosException;
      import com.alibaba.nacos.api.naming.NamingService;
      import com.alibaba.nacos.api.naming.pojo.Instance;
      import com.netflix.client.config.IClientConfig;
      import com.netflix.loadbalancer.AbstractLoadBalancerRule;
      import com.netflix.loadbalancer.BaseLoadBalancer;
      import com.netflix.loadbalancer.Server;
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.beans.factory.annotation.Autowired;
      
      @Slf4j
      public class NacosWeightedRule  extends AbstractLoadBalancerRule {
          @Autowired
          private NacosDiscoveryProperties nacosDiscoveryProperties;
      
          @Override
          public void initWithNiwsConfig(IClientConfig iClientConfig) {
              // Read the configuration file and initialize it
          }
      
          @Override
          public Server choose(Object o) {
              try {
                  // The ribbon entry
                  BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer();
                  // Get the name of the microservice you want to request
                  String name = loadBalancer.getName();
                  // Get the API for service discovery
                  NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
                  // The Nacos client automatically selects an instance for me through a weight-based load balancing algorithm
                  Instance instance = namingService.selectOneHealthyInstance(name);
      
                  log.info("The selected instance is: port = {}, instance = {}", instance.getPort(), instance);
                  return new NacosServer(instance);
              } catch (NacosException e) {
                  e.printStackTrace();
                  return null; }}}Copy the code
    2. Editing a Configuration File

      The name of the service in the registry
      user-center:
        ribbon:
          NFLoadBalancerRuleClassName: com.samir.contentcenter.configuration.NacosWeightedRule Rule full path
      Copy the code

The same cluster is preferentially loaded

  1. Edit custom load balancing rules

    package com.samir.contentcenter.configuration;
    
    import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
    import com.alibaba.cloud.nacos.ribbon.NacosServer;
    import com.alibaba.nacos.api.exception.NacosException;
    import com.alibaba.nacos.api.naming.NamingService;
    import com.alibaba.nacos.api.naming.pojo.Instance;
    import com.alibaba.nacos.client.naming.core.Balancer;
    import com.netflix.client.config.IClientConfig;
    import com.netflix.loadbalancer.AbstractLoadBalancerRule;
    import com.netflix.loadbalancer.BaseLoadBalancer;
    import com.netflix.loadbalancer.Server;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.util.CollectionUtils;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Objects;
    import java.util.stream.Collectors;
    
    @Slf4j
    public class NacosSameClusterWeigthedRule extends AbstractLoadBalancerRule {
    
        @Autowired
        private NacosDiscoveryProperties nacosDiscoveryProperties;
    
        @Override
        public void initWithNiwsConfig(IClientConfig iClientConfig) {}@Override
        public Server choose(Object o) {
            try {
                / / get the current application (spring configuration file. Cloud. Nacos. Discovery. The cluster - name: BJ) the name of the cluster
                String clusterName = nacosDiscoveryProperties.getClusterName();
                BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer();
                // Get the name of the microservice you want to request
                String name = loadBalancer.getName();
                // Get the API for service discovery
                NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
    
                // 1. Find all instances A of the specified service
                List<Instance> instances = namingService.selectInstances(name, true);// true gets only healthy instances
                // 2. Filter out all instance B in the same cluster
                List<Instance> instanceList = instances.stream().filter(instance -> Objects.equals(instance.getClusterName(), clusterName)).collect(Collectors.toList());
                // 3. If B is empty, use A
                List<Instance> instancesToBeChosen = new ArrayList<>();
                if (CollectionUtils.isEmpty(instanceList)) {
                    instancesToBeChosen = instances;
                    log.warn(Name = {}, clusterName = {}, instances = {}", name, clusterName, instancesToBeChosen);
                } else {
                    instancesToBeChosen = instanceList;
                }
                // 4. Return an instance based on the weighted non-load balancing algorithm
                Instance instance = MyBalancer.myGetHostByRandomWeight(instancesToBeChosen);
                return new NacosServer(instance);
            } catch (NacosException e) {
                log.error("Something abnormal has happened.", e);
                return null; }}}/ * * * this method is to inherit the Balancer class to use his protected type of method by namingService. * * selectOneHealthyInstance method of tracking, Public Instance selectOneHealthyInstance(String serviceName, String groupName, List
            
              Clusters, boolean subscribe) throws NacosException { * return subscribe ? RandomByWeight.selectHost(this.hostReactor.getServiceInfo(NamingUtils.getGroupedName(serviceName, groupName), StringUtils.join(clusters, ","))) : RandomByWeight.selectHost(this.hostReactor.getServiceInfoDirectlyFromServer(NamingUtils.getGroupedName(serviceName, groupName), StringUtils.join(clusters, ","))); Public static Instance selectHost(ServiceInfo dom) {* List
             
               hosts = selectAll(dom); * if (CollectionUtils.isEmpty(hosts)) { * throw new IllegalStateException("no host to srv for service: " + dom.getName()); * } else { * return Balancer.getHostByRandomWeight(hosts); // This static method is the key to implementation *} *} */
             
            
    class MyBalancer extends Balancer {
        public static Instance myGetHostByRandomWeight(List<Instance> hosts) {
            returngetHostByRandomWeight(hosts); }}Copy the code
  2. Editing a Configuration File

    The name of the service in the registry
    user-center:
      ribbon:
        NFLoadBalancerRuleClassName: com.samir.contentcenter.configuration.NacosSameClusterWeigthedRule Rule full path
    Copy the code

Versioning based on metadata

  1. Edit custom load balancing rules

    @Slf4j
    public class NacosFinalRule extends AbstractLoadBalancerRule {
        @Autowired
        private NacosDiscoveryProperties nacosDiscoveryProperties;
    
        @Override
        public Server choose(Object key) {
            // Load balancing rule: Select instances that meet metadata requirements in the same cluster
            // If not, select all instances that match metadata in the cluster
    
            // 1. Query all instance A
            // 2. Filter instance B that matches metadata
            // 3. Select instance C that matches the metadata in cluster
            // 4. If C is empty, use B
            // 5. Randomly select instances
          
          	/ / the metadata information (spring configuration file. Cloud. Nacos. Metadata. Version: v1) your application version here
          	/ / the metadata information (spring configuration file. Cloud. Nacos. Metadata. The target - version: v1) allows the provider's version of the call
            try {
                String clusterName = this.nacosDiscoveryProperties.getClusterName();
                String targetVersion = this.nacosDiscoveryProperties.getMetadata().get("target-version");
    
                DynamicServerListLoadBalancer loadBalancer = (DynamicServerListLoadBalancer) getLoadBalancer();
                String name = loadBalancer.getName();
    
                NamingService namingService = this.nacosDiscoveryProperties.namingServiceInstance();
    
                // All instances
                List<Instance> instances = namingService.selectInstances(name, true);
    
                List<Instance> metadataMatchInstances = instances;
                // If version mapping is configured, only instances of metadata matching are called
                if (StringUtils.isNotBlank(targetVersion)) {
                    metadataMatchInstances = instances.stream()
                            .filter(instance -> Objects.equals(targetVersion, instance.getMetadata().get("version")))
                            .collect(Collectors.toList());
                    if (CollectionUtils.isEmpty(metadataMatchInstances)) {
                        log.warn("No metadata matching target instance found! Check the configuration. targetVersion = {}, instance = {}", targetVersion, instances);
                        return null;
                    }
                }
    
                List<Instance> clusterMetadataMatchInstances = metadataMatchInstances;
                // If the cluster name is specified, filter the instances whose metadata matches the cluster
                if(StringUtils.isNotBlank(clusterName)) { clusterMetadataMatchInstances = metadataMatchInstances.stream() .filter(instance  -> Objects.equals(clusterName, instance.getClusterName())) .collect(Collectors.toList());if (CollectionUtils.isEmpty(clusterMetadataMatchInstances)) {
                        clusterMetadataMatchInstances = metadataMatchInstances;
                        log.warn("A cross-cluster call occurred. clusterName = {}, targetVersion = {}, clusterMetadataMatchInstances = {}", clusterName, targetVersion, clusterMetadataMatchInstances);
                    }
                }
    
                Instance instance = ExtendBalancer.getHostByRandomWeight2(clusterMetadataMatchInstances);
                return new NacosServer(instance);
            } catch (Exception e) {
                log.warn("Abnormal", e);
                return null; }}@Override
        public void initWithNiwsConfig(IClientConfig iClientConfig) {}}class ExtendBalancer extends Balancer {
        /** * select instance ** randomly according to weight@paramInstances List of instances *@returnThe selected instance */
        public static Instance getHostByRandomWeight2(List<Instance> instances) {
            returngetHostByRandomWeight(instances); }}Copy the code
  2. Editing a Configuration File

    The name of the service in the registry
    user-center:
      ribbon:
        NFLoadBalancerRuleClassName: com.samir.contentcenter.configuration.NacosFinalRule Rule full path
    Copy the code