1. What is load balancing?

Load balancing is a basic network service. Its core principle is to distribute requests to back-end service clusters according to specified load balancing algorithms, thus providing parallel processing and high availability capabilities for the system. When you think of load balancing, you probably think of NGINx. Load balancing is generally divided into server load balancing and client load balancing

  • Server-side load balancing: Load balancing between consumers and service providers using separate proxies, either a hardware load balancer, such as F5, or software, such as Nginx.

  • Client Load balancing: The so-called client load balancing is the client load balancing based on its own requests. The Netflix Ribbon introduced in this article is the client load balancing component

2. What is Netflix Ribbon?

In the previous chapter, we learned about the basic concepts of microservices and how to implement service calls based on Ribbon+restTemplate. In the last blog, we will learn more about client load balancing in Netflix Ribbon. Then study this blog

Ribbon is a load balancer released by Netflix that helps control the behavior of HTTP and TCP clients. The Ribbon is client-side load balancing.

3. Preparation of Netflix Ribbon experimental environment

Environment Preparation:

  • JDK 1.8

  • SpringBoot2.2.3

  • SpringCloud(Hoxton.SR6)

  • Maven 3.2 +

  • The development tools

  • IntelliJ IDEA

  • smartGit

Create a SpringBoot Initialize project

You can introduce the Eureka Discovery Client, or you can add the Ribbon separately

Spring Cloud Hoxton.SR6 does not require the introduction of Spring-Cloud-starter-Netflix-ribbon, which is already integrated by default

You can also add a separate Ribbon dependency:

This blog is based on spring-cloud-starter- Netflix – Eureka -client test, before the test to run eureka server

Added: IDEA multi-instance running method

Step1: As shown in the figure, do not add the check box

Step2: specify different server ports and instance ids, as shown in the figure below:

After a successful startup, you can see multiple instances

4. Netflix Ribbon API usage

Using LoadBalancerClient:

@Autowired
    LoadBalancerClient loadBalancerClient;

    @Test
    void contextLoads() {
        ServiceInstance serviceInstance = loadBalancerClient.choose("EUREKA-SERVICE-PROVIDER");
        URI uri = URI.create(String.format("http://%s:%s", serviceInstance.getHost() , serviceInstance.getPort()));
        System.out.println(uri.toString());
    }
Copy the code

BaseLoadBalancer example

@test void testLoadBalancer(){// List<Server> serverList = array. asList(new Server("localhost", 8083), new Server("localhost", 8084)); / / build load instance BaseLoadBalancer loadBalancer = LoadBalancerBuilder. NewBuilder () buildFixedServerListLoadBalancer (serverList);  loadBalancer.setRule(new RandomRule()); for (int i = 0; i < 5; i++) { String result = LoadBalancerCommand.<String>builder().withLoadBalancer(loadBalancer).build() .submit(new ServerOperation<String>() { public Observable<String> call(Server server) { try { String address = "http://" + server.getHost() + ":" + server.getPort()+"/EUREKA-SERVICE-PROVIDER/api/users/mojombo"; System.out.println(" call address: "+ address); return Observable.just(""); } catch (Exception e) { return Observable.error(e); } } }).toBlocking().first(); System.out.println("result: "+ result); }}Copy the code

5. @LoadBalanced

Ribbon load balancing, RestTemplate @LoadBalanced

package com.example.springcloud.ribbon.configuration; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; /** ** <pre> * RestConfiguration * </pre> ** <pre> * @author Mazq * Modified record * Updated version: By: Date: 2020/07/31 09:43 Modified Contents: * </pre> */ @Configuration public class RestConfiguration { @Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); }}Copy the code

Yaml configuration:

server:
  port: 8082
spring:
  application:
    name: eureka-service-consumer
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
    fetch-registry: true
    register-with-eureka: false
    healthcheck:
      enabled: false
  instance:
    status-page-url-path: http://localhost:8761/actuator/info
    health-check-url-path: http://localhost:8761/actuator//health
    prefer-ip-address: true
    instance-id: eureka-service-consumer8082
Copy the code

Key point, use @loadBalanced of SpringCloud to tune http://EUREKA-SERVICE-PROVIDER/api/users/? Interface data, the browser is not directly called

import com.example.springcloud.ribbon.bean.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;

import java.net.URI;

@SpringBootApplication
@EnableEurekaClient
@RestController
@Slf4j
public class SpringcloudRibbonApplication {


    @Autowired
    RestTemplate restTemplate;

    public static void main(String[] args) {
        SpringApplication.run(SpringcloudRibbonApplication.class, args);
    }

    @GetMapping("/findUser/{username}")
    public User index(@PathVariable("username")String username){
        return restTemplate.getForObject("http://EUREKA-SERVICE-PROVIDER/api/users/"+username,User.class);
    }

}
Copy the code

6. Customize Netflix Ribbon Client

How to customize? Please refer to the official website. @RibbonClient can specify the customized configuration class

package com.example.springcloud.ribbon.configuration; import com.example.springcloud.ribbon.component.MyRule; import com.netflix.loadbalancer.IPing; import com.netflix.loadbalancer.IRule; import com.netflix.loadbalancer.PingUrl; import org.springframework.cloud.netflix.ribbon.ZonePreferenceServerListFilter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * <pre> * Ribbon Clients Configuration * </pre> ** <pre> * @author mazq * 2020/07/29 14:22 Modified contents: * </pre> */ //@Configuration(proxyBeanMethods = false) //@IgnoreComponentScan public class RibbonClientConfiguration { // @Autowired // IClientConfig config; @Bean public IRule roundRobinRule() { return new MyRule(); } @Bean public ZonePreferenceServerListFilter serverListFilter() { ZonePreferenceServerListFilter filter = new ZonePreferenceServerListFilter(); filter.setZone("myTestZone"); return filter; } @Bean public IPing ribbonPing() { return new PingUrl(); }}Copy the code

Add @RibbonClient to the Application class, name is the service name, same as bootstrap.yml

@RibbonClient(name = "eureka-service-provider",configuration = RibbonClientConfiguration.class)
Copy the code

Special attention: The official website is specially reminded that @RibbonClient must add @Configuration to the Configuration class (however, after my verification in Hoxton.SR6, it is not possible to add @Configuration, if it is added, error may be reported). @ComponentScan The custom configuration class is excluded from the scan; otherwise, it is shared by all @RibbonClients. If you use @ComponentScan(or @SpringBootApplication)

The idea is to exclude the global scan for this configuration, so we can code it by saying @ignoreComponentScan, which applies to the class, and specifies @target (elementType.type).

package com.example.springcloud.ribbon.configuration;

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface IgnoreComponentScan {

}
Copy the code

Add custom annotation classes

Any code added to Application to avoid global scanning:

@ComponentScan(excludeFilters={@ComponentScan.Filter(type= FilterType.ANNOTATION,value= IgnoreComponentScan.class)})
Copy the code

7. Netflix Ribbon Common components

Ps: Before introducing the load strategy of Netflix Ribbon, let’s introduce the common components of Netflix Ribbon and their functions:

8. Customize Netflix Ribbon strategy

Since the service provider is multi-instance, we will write an interface test to see which instance is invoked to see the load policy of Netflix Ribbon

@Autowired
    LoadBalancerClient loadBalancerClient;

    @GetMapping(value = {"/test"})
    public String test(){
        ServiceInstance serviceInstance = loadBalancerClient.choose("EUREKA-SERVICE-PROVIDER");
        URI uri = URI.create(String.format("http://%s:%s", serviceInstance.getHost() , serviceInstance.getPort()));
        System.out.println(uri.toString());
        return uri.toString();
    }
Copy the code

Successful deployment, multiple invocations, and see that the service instance is different each time? In fact, Netflix Ribbon is called polling by default

AbstractLoadBalancerRule AbstractLoadBalancerRule AbstractLoadBalancerRule AbstractLoadBalancerRule AbstractLoadBalancerRule AbstractLoadBalancerRule

Netflix Ribbon has the following load balancing policies built into it

Ok, then we can modify the rules in the configuration class

@Bean
    public IRule roundRobinRule() {
        return new BestAvailableRule();
    }
Copy the code

Test, the basic is to tune 8083 this instance, because this instance performance is better

Clearly, also can write their own strategy, code reference com.net flix. Loadbalancer. RandomRule, Internet also has many examples, way of thinking is to modify RandomRule original strategy, random before a service instance, every callback after 5 times to today, and other service instance

package com.example.springcloud.ribbon.component; import com.netflix.client.config.IClientConfig; import com.netflix.loadbalancer.AbstractLoadBalancerRule; import com.netflix.loadbalancer.ILoadBalancer; import com.netflix.loadbalancer.Server; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ThreadLocalRandom; Public class MyRule extends AbstractLoadBalancerRule {private int total = 0; public class MyRule extends AbstractLoadBalancerRule extends AbstractLoadBalancerRule { Private int index = 0; private int index = 0; public Server choose(ILoadBalancer lb, Object key) { if (lb == null) { return null; } Server server = null; while (server == null) { if (Thread.interrupted()) { return null; UpList = lb.getreachableservers (); upList = lb.getreachableservers (); List<Server> allList = lb.getallServers (); int serverCount = allList.size(); If (serverCount == 0) {return null; } //int index = chooseRandomInt(serverCount); //server = upList.get(index); if(total < 5) { server = upList.get(index); total++; }else { total = 0; index++; if(index >= upList.size()) { index = 0; }} if (server == null) {thread.yield (); continue; } if (server.isAlive()) { return (server); } server = null; Thread.yield(); } return server; } protected int chooseRandomInt(int serverCount) { return ThreadLocalRandom.current().nextInt(serverCount); } @Override public Server choose(Object key) { return choose(getLoadBalancer(), key); } @Override public void initWithNiwsConfig(IClientConfig clientConfig) { } }Copy the code

Modify IRule to return MyRule

@Bean
    public IRule roundRobinRule() {
        return new MyRule();
    }
Copy the code