Рубрики
Без рубрики

Балансировка нагрузки на стороне клиента Внешние вызовы REST с помощью Java и Spring Boot

Нацелен на Java 8+, Spring Boot 5, любой фреймворк на стороне клиента, такой как Angular / React / Vue / etc…. Помечено как java, spring boot, rest, балансировка нагрузки.

Цели

Java 8+, Spring Boot 5, Любой фреймворк на стороне клиента, такой как Angular/React/Vue/и т.д.

Вступление

Какой бы необычной ни была необходимость, может наступить момент, когда вам потребуется написать приложение, которое выполняет различные вызовы внешнего REST API/микросервиса, существующего в нескольких местах, с целью обеспечения высокой доступности. Поскольку запущено несколько экземпляров службы, просто кажется неправильным настраивать ваше приложение с помощью прокси-сервера, который обращается только к одной из нескольких конечных точек, таким образом, выполнение вызовов службы с балансировкой нагрузки может быть вашей конечной целью.

Вероятно, существуют более эффективные способы балансировки нагрузки вызовов служб на предприятии, например, с помощью аппаратного обеспечения балансировки нагрузки или реализации продукта GSLB (Global Server Load Balancer), такого как NGINX . Однако с учетом сказанного может возникнуть необходимость в том, чтобы ваше приложение выполняло эту задачу без дополнительных затрат и/или дополнительных потенциальных точек отказа.

Если вы не знакомы с настройкой приложения Spring Boot и/или хотите получить больше информации о том, где я нашел свое вдохновение, я настоятельно рекомендую вам ознакомиться с руководством Балансировка нагрузки на стороне клиента с помощью Spring Cloud LoadBalancer , прежде чем продолжить.

Когда Прокси-Сервера Недостаточно

В качестве вторичной потребности, когда дело доходит до использования внешнего API из браузерного приложения, является совместное использование ресурсов Cross-Origin (CORS). проблема которая возникает, когда браузер обнаруживает, что выполняется вызов API, существующего в другом домене. Это можно решить, включив Access-Control-Allow-Origin в заголовке ответа службы; но ради этого упражнения мы собираемся предположить, что мы либо не можем, либо не хотим вносить это изменение в код службы.

С помощью Spring Boot можно использовать Netflix Zuul Прокси-сервер для решения этой проблемы, который чрезвычайно прост в настройке. К сожалению, вы будете ограничены использованием одной настроенной конечной точки. Если вы проведете дополнительные исследования, вы, несомненно, наткнетесь на ленту Netflix, которая имеет гораздо больше перспектив в том, чтобы доставить нас туда, где нам нужно быть, но значительно сложнее в настройке, чем прокси-сервер Zuul, и что еще более разочаровывает, вы узнаете, что лента официально устарела.

Балансировщик нагрузки Spring Cloud

Введите Spring Cloud Load Balancer , который подхватывает, где остановилась лента Netflix , и на самом деле он фактически использует ленту под капотом. На официальном сайте Spring есть хорошая информация о том, как можно использовать балансировщик нагрузки в вышеупомянутом руководстве: Балансировка нагрузки на стороне клиента с помощью Spring Cloud LoadBalancer , на котором я основываю наше решение.

Руководство Spring довольно близко, но на самом деле не отвечает вашим конкретным потребностям. То есть, что нам в конечном итоге нужно уметь делать: перехватывать все вызовы API, выполняемые из нашего клиентского приложения, запущенного в Spring Boot (получает, отправляет, помещает и удаляет), и перенаправлять их под капотом, сбалансированным по нагрузке образом, на несколько разных внешних конечных точек, без необходимости определять каждый конкретный вызов API.

Наш Сценарий

У нас есть клиентское приложение (скажем, Angular) со службой, которая взаимодействует со службой RESTful, существующей на нескольких конечных точках для обеспечения высокой доступности. Мы не хотим, чтобы наше приложение было просто настроено на доступ только к одной из этих конечных точек, поэтому мы собираемся использовать Spring Boot, чтобы воспользоваться преимуществами Spring Cloud LoadBalancer. Допустим, у нас есть 3 внешних конечных точки, на которых размещены API (ы), которые мы хотим использовать в нашем приложении:

https://myservice.host1.com , https://myservice.host2.com , https://myservice.host2.com

Мы хотим, чтобы наше клиентское приложение вызывало API-интерфейсы по пути ” /api “, как если бы эти конечные точки существовали в нашем приложении. Поэтому, когда выполняются вызовы, такие как ” /api/get this ” или ” /api/сохраните это ” у нас будет наше загрузочное приложение Java Spring, которое будет находиться в том же пространстве, что и наше клиентское приложение, перехватывать и перенаправлять эти вызовы на одну из 3 внешних конечных точек. Ниже показан один из способов, которым вы можете это настроить.

Настройка балансировщика нагрузки Java Spring Boot

После настройки вашего приложения Spring Boot (что легко сделать здесь ), перейдите к вашему основному классу приложения, который для моего примера я назвал L B Пример приложения и убедитесь , что он содержит следующее:

package com.lb.example;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.reactive.ReactorLoadBalancerExchangeFilterFunction;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.CoreSubscriber;
import reactor.core.publisher.Mono;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Enumeration;
import java.util.stream.Collectors;

@Slf4j
@SpringBootApplication
@RestController
public class LBExampleApplication {
   private final WebClient.Builder loadBalancedWebClientBuilder;
   private final ReactorLoadBalancerExchangeFilterFunction lbFunction;

   public LBExampleApplication(WebClient.Builder webClientBuilder,
                           ReactorLoadBalancerExchangeFilterFunction lbFunction) {
      this.loadBalancedWebClientBuilder = webClientBuilder;
      this.lbFunction = lbFunction;
   }

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

   @RequestMapping(value = "/api/*")
   public Mono balanceIt(HttpServletRequest request) throws IOException {
      Boolean logDebugInfo = true;
      String uri = request.getRequestURI();
      String type = request.getMethod();
      Enumeration headerNames = request.getHeaderNames();
      String url = "http://load-balancer" + uri;
      String body =  request.getReader().lines().collect(Collectors.joining(System.lineSeparator()));

      if (logDebugInfo) {
         if (headerNames != null) {
            while (headerNames.hasMoreElements()) {
               log.info(" --- Request Header: " + request.getHeader(headerNames.nextElement()));
            }
         }
         log.info(" --- Request Body: " + body);
         log.info(" --- " + type + " Reroute to Load Balancer: " + url);
      }

      switch (type) {
         case "GET":
            return loadBalancedWebClientBuilder.build().get().uri(url)
                  .retrieve().bodyToMono(String.class);
         case "POST":
                return loadBalancedWebClientBuilder.build().post().uri(url)
                  .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
                  .bodyValue(body)
                  .retrieve().bodyToMono(String.class);
         case "PUT":
            return loadBalancedWebClientBuilder.build().put().uri(url)
                  .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
                  .bodyValue(body)
                  .retrieve().bodyToMono(String.class);
         case "DELETE":
            return loadBalancedWebClientBuilder.build().delete().uri(url)
                  .retrieve().bodyToMono(String.class);
      }

      return new Mono() {
         @Override
         public void subscribe(CoreSubscriber actual) {
         }
      };
   }
}

записка: Ведение журнала было добавлено, чтобы вы могли видеть, что происходит в консоли отладки при локальном запуске .

Затем создайте класс с именем LB Example Configuration

package com.lb.example;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.DefaultServiceInstance;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import reactor.core.publisher.Flux;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;

@Configuration
public class LBExampleConfiguration {
    @Bean
    @Primary
    ServiceInstanceListSupplier serviceInstanceListSupplier() {
        return new IMServiceInstanceListSuppler("load-balancer");
    }
}

@Slf4j
class IMServiceInstanceListSuppler implements ServiceInstanceListSupplier {
    private final String serviceId;

    @Value("#{'${api.endpoints}'.split(',')}")
    private List endpoints;

    private List instances;

    IMServiceInstanceListSuppler(String serviceId) {
        this.serviceId = serviceId;
    }

    @PostConstruct
    void buildServiceInstance(){
        instances = new ArrayList<>();
        Integer sid = 0;
        for (String host : endpoints) {
            sid++;
            instances.add(new DefaultServiceInstance(serviceId + sid, serviceId, host, 443, true));
            log.info(" --- Added Host: " + host);
        }
    }

    @Override
    public String getServiceId() {
        return serviceId;
    }

    @Override
    public Flux> get() {
        return Flux.just(instances);
    }
}

Затем создайте класс с именем Web Client Config

package com.lb.example;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.client.WebClient;

@Configuration
@LoadBalancerClient(name = "api-gateway", configuration = LBExampleConfiguration.class)
public class WebClientConfig {
    @LoadBalanced
    @Bean
    WebClient.Builder webClientBuilder() {
        return WebClient.builder();
    }
}

Наконец, в вашем application.yml

api:
  endpoints: "myservice.host1.com,myservice.host2.com,myservice.host3.com"

Это список, разделенный запятыми, в котором вы определяете все конечные точки, в которых вы хотите, чтобы ваши вызовы API были сбалансированы по нагрузке. Не включайте здесь http или https, потому что функциональность в классе конфигурации LB Example позаботится об этом на основе номера порта, указанного в вызове new DefaultServiceInstance(…)

Настройка клиентской части

Ну, на самом деле здесь особо нечего делать. Из вашего клиентского приложения, будь то Angular, React, Vue или что-нибудь еще. Просто вызовите свои API (ы), как если бы они существовали в вашем бэкэнде. Ваш балансировщик нагрузки Java Spring Boot – это, по сути, простое серверное приложение, которое будет обрабатывать маршруты, направленные в/api/, и направлять их к их фактическим внешним конечным точкам.

Оригинал: “https://dev.to/coreylasley/client-side-load-balancing-external-rest-calls-with-java-and-spring-boot-hi8”