1. Введение
По мере того, как микросервисные архитектуры становятся все более популярными, становится все более распространенным запускать несколько сервисов, распределенных по разным серверам. В этом кратком руководстве мы рассмотрим использование Spring Cloud Load Balancer для создания более отказоустойчивых приложений .
2. Что такое балансировка нагрузки?
Балансировка нагрузки — это процесс распределения трафика между разными экземплярами одного и того же приложения.
Чтобы создать отказоустойчивую систему, обычно запускают несколько экземпляров каждого приложения. Таким образом, всякий раз, когда одной службе необходимо связаться с другой, она должна выбрать конкретный экземпляр для отправки своего запроса.
Когда дело доходит до балансировки нагрузки, существует множество алгоритмов:
- Случайный выбор: случайный выбор экземпляра
- Циклический перебор: каждый раз выбор экземпляра в одном и том же порядке
- Наименьшее количество подключений: выбор экземпляра с наименьшим количеством текущих подключений.
- Взвешенная метрика: использование взвешенной метрики для выбора лучшего экземпляра (например, использование ЦП или памяти).
- Хэш IP: использование хэша IP-адреса клиента для сопоставления с экземпляром
Это всего лишь несколько примеров алгоритмов балансировки нагрузки, и у каждого есть свои плюсы и минусы .
Случайный выбор и циклический выбор легко реализовать, но они могут не обеспечивать оптимальное использование услуг. И наоборот, наименьшее количество подключений и взвешенные метрики являются более сложными, но обычно обеспечивают более оптимальное использование службы. И IP-хэш хорош, когда важна липкость сервера, но он не очень отказоустойчив.
3. Введение в Spring Cloud Load Balancer
Библиотека Spring Cloud Load Balancer позволяет нам создавать приложения, которые взаимодействуют с другими приложениями с балансировкой нагрузки . Используя любой алгоритм, который мы хотим, мы можем легко реализовать балансировку нагрузки при выполнении удаленных вызовов службы.
Чтобы проиллюстрировать это, давайте рассмотрим пример кода. Мы начнем с простого серверного приложения. Сервер будет иметь одну конечную точку HTTP и может работать как несколько экземпляров.
Затем мы создадим клиентское приложение, которое использует Spring Cloud Load Balancer для чередования запросов между разными экземплярами сервера.
3.1. Пример сервера
Для нашего примера сервера мы начнем с простого приложения Spring Boot:
@SpringBootApplication
@RestController
public class ServerApplication {
public static void main(String[] args) {
SpringApplication.run(ServerApplication.class, args);
}
@Value("${server.instance.id}")
String instanceId;
@GetMapping("/hello")
public String hello() {
return String.format("Hello from instance %s", instanceId);
}
}
Мы начинаем с внедрения настраиваемой переменной с именем instanceId.
Это позволяет нам различать несколько запущенных экземпляров. Затем мы добавляем одну конечную точку HTTP GET, которая возвращает сообщение и идентификатор экземпляра.
Экземпляр по умолчанию будет работать на порту 8080 с идентификатором 1. Чтобы запустить второй экземпляр, нам просто нужно добавить пару аргументов программы :
--server.instance.id=2 --server.port=8081
3.2. Пример клиента
Теперь давайте посмотрим на клиентский код. Здесь мы используем Spring Cloud Load Balancer , поэтому начнем с включения его в наше приложение:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
Далее мы создаем реализацию ServiceInstanceListSupplier
. Это один из ключевых интерфейсов в Spring Cloud Load Balancer . Он определяет, как мы находим доступные экземпляры службы.
Для нашего примера приложения мы жестко запрограммируем два разных экземпляра нашего примера сервера. Они работают на одной машине, но используют разные порты:
class DemoInstanceSupplier implements ServiceInstanceListSupplier {
private final String serviceId;
public DemoInstanceSupplier(String serviceId) {
this.serviceId = serviceId;
}
@Override
public String getServiceId() {
return serviceId;
}
@Override
public Flux<List<ServiceInstance>> get() {
return Flux.just(Arrays
.asList(new DefaultServiceInstance(serviceId + "1", serviceId, "localhost", 8080, false),
new DefaultServiceInstance(serviceId + "2", serviceId, "localhost", 8081, false)));
}
}
В реальной системе мы хотели бы использовать реализацию, которая не жестко кодирует адреса сервисов. Мы рассмотрим это немного позже.
Теперь давайте создадим класс LoadBalancerConfiguration
:
@Configuration
@LoadBalancerClient(name = "example-service", configuration = DemoServerInstanceConfiguration.class)
class WebClientConfig {
@LoadBalanced
@Bean
WebClient.Builder webClientBuilder() {
return WebClient.builder();
}
}
У этого класса есть одна роль: создать построитель WebClient
с балансировкой нагрузки для выполнения удаленных запросов. Обратите внимание, что в нашей аннотации для службы используется псевдоимя .
Это связано с тем, что мы, скорее всего, не будем знать фактические имена хостов и порты для запуска экземпляров заранее. Итак, мы используем псевдоимя в качестве заполнителя, и фреймворк подставит реальные значения, когда выберет работающий экземпляр.
Далее давайте создадим класс Configuration
, который создает экземпляр нашего поставщика экземпляров службы. Обратите внимание, что мы используем то же псевдоним, что и выше:
@Configuration
class DemoServerInstanceConfiguration {
@Bean
ServiceInstanceListSupplier serviceInstanceListSupplier() {
return new DemoInstanceSupplier("example-service");
}
}
Теперь мы можем создать настоящее клиентское приложение. Давайте используем bean- компонент WebClient
, описанный выше, для отправки десяти запросов на пример сервера:
@SpringBootApplication
public class ClientApplication {
public static void main(String[] args) {
ConfigurableApplicationContext ctx = new SpringApplicationBuilder(ClientApplication.class)
.web(WebApplicationType.NONE)
.run(args);
WebClient loadBalancedClient = ctx.getBean(WebClient.Builder.class).build();
for(int i = 1; i <= 10; i++) {
String response =
loadBalancedClient.get().uri("http://example-service/hello")
.retrieve().toEntity(String.class)
.block().getBody();
System.out.println(response);
}
}
}
Глядя на вывод, мы можем подтвердить, что балансируем нагрузку между двумя разными экземплярами:
Hello from instance 2
Hello from instance 1
Hello from instance 2
Hello from instance 1
Hello from instance 2
Hello from instance 1
Hello from instance 2
Hello from instance 1
Hello from instance 2
Hello from instance 1
4. Другие функции
Пример сервера и клиента демонстрирует очень простое использование Spring Cloud Load Balancer . Но стоит упомянуть и другие возможности библиотеки.
Во-первых, пример клиента использовал политику RoundRobinLoadBalancer по умолчанию.
Библиотека также предоставляет класс RandomLoadBalancer
. Мы также можем создать собственную реализацию ReactorServiceInstanceLoadBalancer
с любым алгоритмом, который захотим.
Кроме того, библиотека предоставляет способ динамического обнаружения экземпляров службы . Мы делаем это с помощью интерфейса DiscoveryClientServiceInstanceListSupplier .
Это полезно для интеграции с системами обнаружения сервисов, такими как Eureka или Zookeeper .
В дополнение к различным функциям балансировки нагрузки и обнаружения служб библиотека также предлагает базовую возможность повторных попыток. Под капотом он в конечном итоге опирается на библиотеку Spring Retry . Это позволяет нам повторить неудачные запросы , возможно, используя тот же экземпляр после некоторого периода ожидания.
Еще одна встроенная функция — это метрики, созданные на основе библиотеки Micrometer . Из коробки мы получаем базовые метрики уровня обслуживания для каждого инстанса, но можем добавить и свои.
Наконец, библиотека Spring Cloud Load Balancer предоставляет способ кэширования экземпляров службы с помощью интерфейса LoadBalancerCacheManager
. Это важно, потому что на самом деле поиск доступных экземпляров службы, скорее всего, требует удаленного вызова . Это означает, что поиск данных, которые не часто меняются, может быть дорогостоящим, а также представляет собой возможную точку сбоя в приложении. Используя кэш экземпляров службы, наши приложения могут обойти некоторые из этих недостатков.
5. Вывод
Балансировка нагрузки является неотъемлемой частью построения современных отказоустойчивых систем. Используя Spring Cloud Load Balancer, мы можем легко создавать приложения, использующие различные методы балансировки нагрузки для распределения запросов по разным экземплярам службы .
И, конечно же, весь приведенный здесь пример кода можно найти на GitHub .