Spring Cloud Article 4 | Client Load Balancing Ribbon

Keywords: Java Spring Nginx JDK network

This is the fourth article in the Spring Cloud column. Understanding the first three articles will help you better understand this article:

  1. First Spring Cloud | An overview of the Spring Cloud preamble and its common components

  2. Spring Cloud Part 2 | Use and Know the Eureka Registry

  3. Spring Cloud Article 3 | Build a highly available Eureka registry

1. What is Ribbon

Ribbon is an HTTP-and TCP-based client load balancer that extends service discovery capabilities for Eureka clients when accessing services using Ribbon, obtains a list of servers from the Eureka registry, and determines whether the server is started through the Eureka client.On the basis of Eureka client service discovery, Ribbon implements a selection strategy for service instances to achieve load balanced consumption of services.Load balancing is a very important content in the system architecture, because load balancing is one of the important means of high availability of the system, buffering of network pressure and expanding processing capacity. Load balancing is commonly referred to as load balancing on the service side, which is divided into hardware load balancing and software load balancing.

  • Hardware Load Balancing: Mainly install devices dedicated to load balancing between server nodes, such as F5, Array, etc.

  • Software load balancing: Request distribution is accomplished by installing software with load functions or modules on the server, such as Nginx, LVS, HAProxy, etc.

Hardware load balancing devices or software load balancing modules maintain a list of available downloaded servers, which are guaranteed to be accessible through heartbeat detection to remove failures.When a client sends a request to a load balanced device, the device takes a server address from the list of available servers for maintenance by some algorithm (such as linear polling, weight loading, traffic loading, etc.) and forwards it.

Ribbon is an open source project published by Netflix that provides client-side software load balancing algorithms and is an HTTP and TCP-based client load balancing tool.Spring Cloud re-encapsulates Ribbon so that we can use RestTemplate's service requests to automatically convert them into client load balanced service calls.Ribbon supports a variety of load balancing algorithms, as well as custom load balancing algorithms.Ribbon is just a tool class framework. It is compact, and Spring Cloud is very convenient to use after encapsulation. It does not need to be deployed independently like service registry, configuration center, AP gateway. Ribbon only needs to be used directly in code.

The difference between Ribbon and Nginx:

  • All are soft loads

  • Ribbon is client load balancing

  • Nginx is server segment load balancing

The difference is:

Services lists are stored in different locations. In client load balancing, the list of services under all client nodes needs to be obtained from the service registry itself, such as the Eureka service registry.Similar to the service-side load balancing architecture, a heartbeat is required in client load balancing to maintain the health of the service-side list, but this step needs to be completed in conjunction with the service registry. In the service governance framework implemented by SpringCloud, Ribbon automated integration configurations are created by default for each service governance framework, such as org.springframework in Eureka.Cloud.netflix.ribbon.eureka.RibbonEurekaAutoConfiguration, when used in practice, we can look at the implementation of this class to find out their configuration details to help us use it better.

With the encapsulation of Spring Cloud Ribbon, it is very simple to use client load balancing calls in our microservice architecture in just two steps:

  1. A service provider only needs to start multiple service instances and register them with one registry or multiple associated service registries

  2. Service consumers implement service-oriented interface calls directly by calling RestTemplate decorated with the @LoadBalanced annotation.

This allows us to combine the high availability of service providers with the load balancing of service consumers.

  • Load balancing on the server side is configured ahead of time: Nginx

  • Load balancing for clients is done from the registry: Ribbon

In SpringCloud, Ribbon is mainly used with RestTemplate objects. Ribbon automatically configures the RestTemplate objects and opens the load balancing of RestTemplate object calls through @LoadBalance. Ribbon is in the role shown in the following figure:

Picture Source Network

2. Ribbon Implements Client Load Balancing

1. As mentioned earlier, using client load balancing calls in our microservice architecture via Spring Cloud Ribbon encapsulation is very simple, requiring only two steps:

  1. A service provider only needs to start multiple service instances and register them with one registry or multiple associated service registries

  2. Service consumers implement service-oriented interface calls directly by calling RestTemplate decorated with the @LoadBalanced annotation.

2. We duplicate the service provider (springcloud-service-provider) and name it springcloud-service-provider-02, modify the control response result content, and distinguish the content of the service provider (springcloud-service-provider).Modify the service provider (springcloud-service-provider-02) port to 8081, see the case source code in detail.Registry We will use 8700 single node in the future, just for convenience.

3. Add the following code to the consumer's RestTemplate:

    //Use Ribbon Implement load balancing calls,Polling by default
    @LoadBalanced //join ribbon Support so that when invoked, the service name can be used instead to access
    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }

4. View two instances of Eureka's web page display provider

5. Activate consumers to access the pictures:

provider-01 and provider-02 appear alternately, so you can see that the default is polling policy.

3. Ribbon Load Balancing Strategy

Ribbon's load balancing strategy is defined by the IRule interface, which is implemented as follows:

 

Load policy implications for IRule implementation classes

RandomRule

random

RoundRobinRule

polling

AvailabilityFilteringRule

Filter out services that have multiple access failures and services with concurrent connections exceeding the threshold, then access the remaining services according to polling policy

WeightedResponseTimeRule

Calculate the weight of all services based on the average response time. The quicker the response time, the higher the probability that the service weight will be selected. If the service has insufficient statistics for its startup time, the RoundRobinRule policy will be used, and if the statistics are sufficient, the WeightedResponseTimeRule policy will be switched to.

RetryRule

Distribute first according to the RoundRobinRule policy. If the distributed service is not accessible, try again within a specified time. If not, distribute to other available services

BestAvailableRule

Filter out failing services due to multiple visits, then select one with the least concurrency

ZoneAvoidanceRule

Determine which service to choose by synthesizing the performance and availability of the service node's region

TIP: Combined with Ribbon load balancing, the default is polling, reinjecting IRule to achieve other load balancing strategies

4. Interpretation of Rest Request Template Class

When we invoke a service provider's service from the consumer side of the service, a very convenient object called RestTemplate was used. At that time, we only used the simplest function of RestTemplate, getForEntity, to initiate a get request to invoke the server side's data. At the same time, we also enabled client load balancing by configuring the @Loadbalanced annotation.Very powerful, so let's take a closer look at the use of several common request methods in RestTemplate.In daily operations, there are four general Rest-based approaches, which are tabulated as

  • GET Request-Query Data

  • POST Request-Add Data

  • PUT Request-Modify Data

  • DELETE - Delete data

1. GET Request for RestTemplate

Get requests can be made in two ways

First: getForEntity(..)

This method returns a ResponseEntity <T>object, ResponseEntity <T>is Spring's encapsulation of HTTP request responses, including several important elements, such as response codes, contentType, contentLength, response message bodies, and so on.

        ResponseEntity<String> forEntity = restTemplate.getForEntity("http://SPRINGCLOUD-SERVICE-PROVIDER/provider/hello", String.class);
        String body = forEntity.getBody();
        HttpStatus statusCode = forEntity.getStatusCode();
        int statusCodeValue = forEntity.getStatusCodeValue();
        HttpHeaders headers = forEntity.getHeaders();

        System.out.println(body);
        System.out.println(statusCode);
        System.out.println(statusCodeValue);
        System.out.println(headers);

In the above code, the getForEntity method th parameter is the address of the service to be invoked, that is, the http://SPRINGCLOUD-SERVICE-PROVIDER/provider/hello interface address provided by the service provider. Note that this is a service name call instead of a service address, and if you change to a service address, you cannot use Ribbon to achieve client load balancing.The second parameter String.class of the getForEntity method indicates that the type of body you want to return is a String type, which is also possible if you want to return an object, such as a User object

    /**
     * Call get request and return a User object
     * @return
     */
    @RequestMapping("/user")
    public User user(){
        //Logical Judgment Omitting
        ResponseEntity<User> forEntity = restTemplate.getForEntity("http://SPRINGCLOUD-SERVICE-PROVIDER/provider/user", User.class);
        System.out.println(forEntity.getBody().getId()+""+forEntity.getBody().getName()+""+forEntity.getBody().getPhone());
        return forEntity.getBody();
    }

Two other overload methods:

  @Override
  public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables)
      throws RestClientException {

    RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
    ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
    return nonNull(execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables));
  }

  @Override
  public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String, ?> uriVariables)
      throws RestClientException {

    RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
    ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
    return nonNull(execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables));
  }

For example:

   /**
     * Pass parameter Get request to service
     * @return
     */
    @RequestMapping("/getUser")
    public User getUser(){
        //Logical Judgment Omitting
        String [] arr={"2","xxx","4545645456"};
        Map<String,Object> map=new HashMap<>();
        map.put("id",1);
        map.put("name","wwwwww");
        map.put("phone","1213213213123");

        //ResponseEntity<User> forEntity = restTemplate.getForEntity("http://SPRINGCLOUD-SERVICE-PROVIDER/provider/getUser?id={0}&name={1}&phone={2}", User.class,arr);
        //ResponseEntity<User> forEntity = restTemplate.getForEntity("http://SPRINGCLOUD-SERVICE-PROVIDER/provider/getUser?id={id}&name={name}&phone={phone}", User.class,map);
        /*
         * restTemplate.getForObject In getForObject encapsulated in getForEntity, get the return value type directly, equivalent to getBody in ResponseEntity
         */
            User user1 = restTemplate.getForObject("http://SPRINGCLOUD-SERVICE-PROVIDER/provider/getUser?id={id}&name={name}&phone={phone}", User.class, map);

        //System.out.println(forEntity.getBody().getId()+""+forEntity.getBody().getName()+""+forEntity.getBody().getPhone());
        System.out.println(user1.getId()+""+user1.getName()+""+user1.getPhone());
        return user1;
    }

You can use a number as a placeholder and finally a variable-length parameter to replace the previous placeholder or you can use name={name} as a preceding one. The last parameter is a map, where the key of the map is the name of the preceding placeholder and the value of the map is the value of the parameter

Second: getForObject(..)

Similar to getForEntity, except that getForobject s are re-encapsulated on the basis of getForEntity, which converts the body information of http's response body into a specified object for our code development. This is more convenient when you don't need to return other information in the response but only the body information. It also has two overloaded methods, which are similar to getForEntityas if

  @Override
  @Nullable
  public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException {
    RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
    HttpMessageConverterExtractor<T> responseExtractor =
        new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger);
    return execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables);
  }

  @Override
  @Nullable
  public <T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException {
    RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
    HttpMessageConverterExtractor<T> responseExtractor =
        new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger);
    return execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables);
  }

  @Override
  @Nullable
  public <T> T getForObject(URI url, Class<T> responseType) throws RestClientException {
    RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
    HttpMessageConverterExtractor<T> responseExtractor =
        new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger);
    return execute(url, HttpMethod.GET, requestCallback, responseExtractor);
  }

The above examples are already covered, so don't go on here.

2. POST Request for RestTemplate

restTemplate.postForEntity();
restTemplate.postForObject();
restTemplate.postForLocation();

For example:

   /**
     * Invoke POST Request
     * @return
     */
    @RequestMapping("/addUser")
    public User addUser(){
        //Logical Judgment Omitting
        String [] arr={"2","xxx","4545645456"};
        //Out of commission map Pass-through parameters
        Map<String,Object> map=new HashMap<>();
        map.put("id",1);
        map.put("name","wwwwww");
        map.put("phone","1213213213123");

        /**
         *Form information to pass, parameter data (very puzzling)
         */
        MultiValueMap<String,Object> multiValueMap=new LinkedMultiValueMap<>();
        multiValueMap.add("id",1);
        multiValueMap.add("name","xxxxx");
        multiValueMap.add("phone","000000000");

        //Use jdk In map Pass parameter, no receive
        ResponseEntity<User> userResponseEntity = restTemplate.postForEntity(
                "http://SPRINGCLOUD-SERVICE-PROVIDER/provider/addUser", multiValueMap, User.class);

        System.out.println(userResponseEntity.getBody().getId()+""+userResponseEntity.getBody().getName()+""+userResponseEntity.getBody().getPhone());
        return userResponseEntity.getBody();
    }

3. PUT request for RestTemplate

restTemplate.put();

For example:

   /**
     * Invoke PUT request
     * @return
     */
    @RequestMapping("/updateUser")
    public String updateUser(){
        //Logical Judgment Omitting
        String [] arr={"2","xxx","4545645456"};
        //Out of commission map Pass-through parameters
        Map<String,Object> map=new HashMap<>();
        map.put("id",1);
        map.put("name","wwwwww");
        map.put("phone","1213213213123");

        /**
         *Form information to pass, parameter data (very puzzling)
         */
        MultiValueMap<String,Object> multiValueMap=new LinkedMultiValueMap<>();
        multiValueMap.add("id",1);
        multiValueMap.add("name","xxxxx");
        multiValueMap.add("phone","000000000");

        //Use jdk In map Pass parameter, no receive
        restTemplate.put("http://SPRINGCLOUD-SERVICE-PROVIDER/provider/updateUser", multiValueMap);

        return "SUCCESS";
    }

4. DELETE request for RestTemplate

restTemplate.delete();

For example:

   /**
     * Invoke DELETE request
     * @return
     */
    @RequestMapping("/deleteUser")
    public String deleteUser(){
        //Logical Judgment Omitting
        String [] arr={"2","xxx","4545645456"};
        Map<String,Object> map=new HashMap<>();
        map.put("id",1);
        map.put("name","wwwwww");
        map.put("phone","1213213213123");

        /**
         *Form information to be passed, parameter data (very puzzling), only post,PUT requests this map pass parameter
         */
     /*   MultiValueMap<String,Object> multiValueMap=new LinkedMultiValueMap<>();
        multiValueMap.add("id",1);
        multiValueMap.add("name","xxxxx");
        multiValueMap.add("phone","000000000"); */

        //Use jdk In map Pass parameter, no receive,Out of commission MultiValueMap,No parameters received
        restTemplate.delete("http://SPRINGCLOUD-SERVICE-PROVIDER/provider/deleteUser?id={id}&name={name}&phone={phone}", map);

        return "SUCCESS";
    }

 

Detailed reference case source: https://gitee.com/coding-farmer/spirngcloud-learn

 

Posted by kylebuttress on Tue, 10 Dec 2019 13:09:09 -0800