A list,

1. What is

  • Spring Cloud Ribbon is a client load balancing tool based on Netflix Ribbon.
  • To put it simply, the Ribbon is an open source project released by Netflix. Its main function is to provide client-side software load balancing algorithm and service invocation.
  • The official documentation
  • It is now in maintenance and can be replaced by Open Feign
  • Load balancing +RestTemplate to implement load balancing calls

2. Load balancing

  • Load balancing (LB), in which user requests are spread across multiple services to achieve high availability (HA) of the system
  • There are two load balancing schemes: centralized LB and in-process LB

2.1 Centralized LB

  • That is, a separate LB facility is used between the service provider and the consumer, which is responsible for forwarding access requests to the service provider through some policy.
  • Examples include Nginx, Gateway, Zuul, etc

2.2 In-process LB

  • The load balancing algorithm is integrated into the consumer, which obtains the available address in the registry and then selects a suitable server through the LB algorithm.
  • The Ribbon is an in-process LB. It is simply a library that integrates with the consumer process to retrieve the address provided by the service provider.

Second, the experimental

  • Ribbon integration inspring-cloud-starter-netflix-eureka-client,See the use of Eureka. Service invocation and load balancing can be accomplished with a few simple modifications on this basis

1. RestTemplate

  • website
  • RestTemplate provides HttpClient with a URL and return type to implement remote method calls.

1.1 Add to IOC container

  • First, add it to the IOC container.@LoadBalancedIndicates that load balancing is enabled.
@Configuration
public class ApplicationContextConfig {
  @Bean
  @LoadBalanced
  public RestTemplate restTemplate(a) {
    return newRestTemplate(); }}Copy the code

1.2 RestTemplate Remote Call

@Slf4j
@RestController
@RequestMapping("/order")
public class OrderController {
  @Autowired
  RestTemplate restTemplate;  // In the ioc container
  @Value("${payment.url}")
  String paymentUrl;  // The URL of the remote call, stored in the configuration file, decoupled

  @GetMapping("/payment/get/{id}")
  public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id) {
    CommonResult<Payment> result = restTemplate.getForObject(paymentUrl + "/payment/get/" + id, CommonResult.class);  // get method call, and return encapsulated as CommonResult type
    log.info("Order query Payment, id:" + id);
    returnresult; }}Copy the code
  • You can also usegetForEntity()Method, get the entire response, and get the desired content in the response yourself.
  @GetMapping("/payment/getEntity/{id}")
  public CommonResult<Payment> getPaymentEntityById(@PathVariable("id") Long id) {
    ResponseEntity<CommonResult> entity = restTemplate.getForEntity(paymentUrl + "/payment/get/" + id, CommonResult.class);
    log.info("The information obtained is:" + entity.toString());
    log.info("StatusCode obtained is:" + entity.getStatusCode());
    log.info("StatusCodeValue obtained is:" + entity.getStatusCodeValue());
    log.info("Get Headers:" + entity.getHeaders());

    if (entity.getStatusCode().is2xxSuccessful()) {
      log.info("Query successful:" + id);
      return entity.getBody();
    } else {
      log.info("Query failed:" + id);
      return new CommonResult<>(CommonResult.FAIlURE, "Query failed"); }}Copy the code
  • If you use the POST method, just change get to POST.

1.3 Configuration File

  • Url, you can write specific address, that is, call the address directly. You can also write it to the eureka service name. Obtain all the addresses of the service from eureka and select one from the LB.
payment:
  url: "http://CLOUD-PAYMENT-SERVICE"
Copy the code

2. LoadBalancer

  • Through the above@LoadBalancedLoad balancing is enabled. The polling algorithm is used by default, but can be changed to other algorithms.
Class algorithm
com.netflix.loadbalancer.RoundRobinRule Polling, default algorithm
com.netflix.loadbalancer.RandomRule Random algorithm, by generating random number selection server
com.netflix.loadbalancer.RetryRule Obtain the service based on RoundRobinRule. If the service fails to be obtained, the system tries again within a specified period to obtain available services
WeightedResponseTimeRule With an extension to RoundRobinRule, instances that are faster in response have greater selection weight and are easier to select
BestAvailableRule Services that are in the breaker trip state due to multiple access failures are filtered out and the service with the least concurrency is selected
AvailabilityFilteringRule Filter out the failed instances first, and then select the instances with low concurrency
ZoneAvoidanceRule Default rules that compound the performance of the region where the server resides and the availability of the server to select the server

2.1 Modifying the load Balancing Algorithm

  • If you want the algorithm to be specific to a service, you cannot place it inComponentScanOtherwise the load balancing algorithm for all services will be modified. Therefore, it is best to build a new one outsidepackageTo put the LB
@Configuration
public class MyRule {
  @Bean
  public IRule rule(a) {
    return newRandomRule(); }}Copy the code
  • On the main startup class, identify the direct mapping between the service and the algorithm
@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name = "CLOUD-PAYMENT-SERVICE", configuration = MyRule.class)
public class OrderApplication80 {
  public static void main(String[] args) { SpringApplication.run(OrderApplication80.class, args); }}Copy the code
  • If this method is troublesome, you can also use the configuration file method
CLOUD-PAYMENT-SERVICE:  # service name
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule  # Algorithm selection
Copy the code

3. Load balancing algorithm source

  • In the default ofRoundRobinRuleAs read source, the rest of the source code is basically similar, just modified to select the server code.

  • RoundRobinRuleThe parent class forAbstractLoadBalancerRule.AbstractLoadBalancerRuleImplements the interfaceIRule

3.1 IRule

public interface IRule {
  Server choose(Object var1);  // Select the server, the most important method

  void setLoadBalancer(ILoadBalancer var1);

  ILoadBalancer getLoadBalancer(a);
}
Copy the code

3.2 AbstractLoadBalancerRule

  • Basically nothing, just pull out the common parts for implementation.
public abstract class AbstractLoadBalancerRule implements IRule.IClientConfigAware {
  private ILoadBalancer lb;  // ILoadBalancer interface, whose main function is to obtain the current server status, number, etc., to provide the calculation parameters for the load balancing algorithm

  public AbstractLoadBalancerRule(a) {}public void setLoadBalancer(ILoadBalancer lb) {
    this.lb = lb;
  }

  public ILoadBalancer getLoadBalancer(a) {
    return this.lb; }}Copy the code

3.3 RoundRobinRule

  • Simply put, polling is implemented through a counter
public class RoundRobinRule extends AbstractLoadBalancerRule {
  private AtomicInteger nextServerCyclicCounter;  // Atom class, which keeps a count of where the polling is now
  private static final boolean AVAILABLE_ONLY_SERVERS = true;
  private static final boolean ALL_SERVERS = false;
  private static Logger log = LoggerFactory.getLogger(RoundRobinRule.class);

  public RoundRobinRule(a) {
    this.nextServerCyclicCounter = new AtomicInteger(0);  / / initialization
  }

  public RoundRobinRule(ILoadBalancer lb) {  / / set the LoadBalancer
    this(a);this.setLoadBalancer(lb);
  }

  public Server choose(ILoadBalancer lb, Object key) {  // The most important method is to select the server and return
  // Post it below
  }
 
  private int incrementAndGetModulo(int modulo) {  // Modify the counter and return a selected value, which is the implementation of the polling algorithm
  // Post it below
  }

  public Server choose(Object key) {   // interface method in which another method implementation is called
    return this.choose(this.getLoadBalancer(), key);
  }

  public void initWithNiwsConfig(IClientConfig clientConfig) {}}Copy the code
  • In simple terms, this method is based on the current state, select a server to return.
public Server choose(ILoadBalancer lb, Object key) {
    if (lb == null) {  // If there is no LoadBalancer, then there is no wasted effort
      log.warn("no load balancer");
      return null;
    } else {
      Server server = null;
      int count = 0;

      while(true) {  
        if (server == null && count++ < 10) {  // Try 10 times and give up if you can't find the server
          List<Server> reachableServers = lb.getReachableServers();  // Get all currently available servers through LB
          List<Server> allServers = lb.getAllServers();  // Get the actual servers
          int upCount = reachableServers.size();  // Get the number of servers currently available
          int serverCount = allServers.size();  // The number of servers, which is the divisor of the mod
          if(upCount ! =0&& serverCount ! =0) {  // If a server is available and available
            int nextServerIndex = this.incrementAndGetModulo(serverCount);  // The most critical selection algorithm, puts the current number of servers in, returns a selected number
            server = (Server)allServers.get(nextServerIndex);   // Retrieve the server according to the subscript
            if (server == null) {  // If it is empty, it is currently unavailable, and the next loop is entered
              Thread.yield(); 
            } else {
              if (server.isAlive() && server.isReadyToServe()) {  // If the server is alive and usable, it is returned directly
                return server;
              }

              server = null;
            }
            continue;
          }

          log.warn("No up servers available from load balancer: " + lb);
          return null;
        }

        if (count >= 10) {
          log.warn("No available alive servers after 10 tries from load balancer: " + lb);
        }

        returnserver; }}}Copy the code
  • In simple terms, mod the current counter +1, get a subscript, and return. To avoid the risk of high concurrency, use the CAS method.
  private int incrementAndGetModulo(int modulo) {
    int current;
    int next;
    do {
      current = this.nextServerCyclicCounter.get();  // Get the current value
      next = (current + 1) % modulo;  / / + 1
    } while(!this.nextServerCyclicCounter.compareAndSet(current, next));  // CAS, return on success, return on failure

    return next;
  }
Copy the code