Введение в Даббо

Введение в Даббо

1. Вступление

Dubbo - это фреймворк RPC и микросервисов с открытым исходным кодом от Alibaba.

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

В этой статье мы познакомим вас с Dubbo и его наиболее важными функциями.

2. Архитектура

Даббо выделяет несколько ролей:

  1. Провайдер - где предоставляется услуга; провайдер зарегистрирует свой сервис в реестре

  2. Контейнер - где служба запускается, загружается и запускается

  3. Потребитель - кто вызывает удаленные сервисы; потребитель подпишется на сервис, необходимый в реестре

  4. Реестр - где сервис будет зарегистрирован и обнаружен

  5. Монитор - записывает статистику для сервисов, например, частоту вызовов сервисов за заданный интервал времени.

image

Соединения между поставщиком, потребителем и реестром являются постоянными, поэтому, когда поставщик услуг не работает, реестр может обнаружить сбой и уведомить потребителей.

Реестр и монитор не являются обязательными. Потребители могут подключаться напрямую к поставщикам услуг, но это повлияет на стабильность всей системы.

3. Maven Dependency

Прежде чем мы углубимся, давайте добавим следующую зависимость к нашемуpom.xml:


    com.alibaba
    dubbo
    2.5.7

Последнюю версию можно найтиhere.

4. Бутстрапирование

Теперь давайте попробуем основные функции Dubbo.

Это минимально инвазивная структура, и многие ее функции зависят от внешних конфигураций или аннотаций.

Официально предлагается использовать файл конфигурации XML, потому что он зависит от контейнера Spring (в настоящее время Spring 4.3.10).

Мы продемонстрируем большинство его функций с помощью конфигурации XML.

4.1. Реестр многоадресной рассылки - поставщик услуг

Для начала нам понадобится только поставщик услуг, потребитель и «невидимый» реестр. Реестр невидим, потому что мы используем многоадресную сеть.

В следующем примере поставщик говорит только «привет» своим потребителям:

public interface GreetingsService {
    String sayHi(String name);
}

public class GreetingsServiceImpl implements GreetingsService {

    @Override
    public String sayHi(String name) {
        return "hi, " + name;
    }
}

Чтобы выполнить удаленный вызов процедуры, потребитель должен использовать общий интерфейс с поставщиком услуг, поэтому интерфейсGreetingsService должен использоваться совместно с потребителем.

4.2. Реестр многоадресной рассылки - Регистрация службы

Теперь зарегистрируемGreetingsService в реестре. Очень удобный способ - использовать многоадресный реестр, если и поставщики, и потребители находятся в одной локальной сети:





В приведенной выше конфигурации beans мы только что предоставили нашGreetingsService URL-адресу подdubbo://127.0.0.1:20880 и зарегистрировали службу на адрес многоадресной рассылки, указанный в<dubbo:registry />.

В конфигурации поставщика мы также объявили метаданные нашего приложения, интерфейс для публикации и его реализацию соответственно<dubbo:application />,<dubbo:service /> и<beans />.

Протоколdubbo - один из многих протоколов, поддерживаемых платформой. Он построен на основе неблокирующей функции Java NIO и используется по умолчанию.

Мы обсудим это более подробно позже в этой статье.

4.3. Реестр многоадресной рассылки - Потребитель услуг

Как правило, потребителю необходимо указать вызываемый интерфейс и адрес удаленной службы, и это именно то, что ему нужно:



Теперь все настроено, давайте посмотрим, как они работают в действии:

public class MulticastRegistryTest {

    @Before
    public void initRemote() {
        ClassPathXmlApplicationContext remoteContext
          = new ClassPathXmlApplicationContext("multicast/provider-app.xml");
        remoteContext.start();
    }

    @Test
    public void givenProvider_whenConsumerSaysHi_thenGotResponse(){
        ClassPathXmlApplicationContext localContext
          = new ClassPathXmlApplicationContext("multicast/consumer-app.xml");
        localContext.start();
        GreetingsService greetingsService
          = (GreetingsService) localContext.getBean("greetingsService");
        String hiMessage = greetingsService.sayHi("example");

        assertNotNull(hiMessage);
        assertEquals("hi, example", hiMessage);
    }
}

Когда запускаетсяremoteContext провайдера, Dubbo автоматически загружаетGreetingsService и регистрирует его в указанном реестре. В данном случае это реестр многоадресной рассылки.

Потребитель подписывается на реестр многоадресной рассылки и создает в контексте проксиGreetingsService. Когда наш локальный клиент вызывает методsayHi, он прозрачно вызывает удаленную службу.

Мы упоминали, что реестр является необязательным, это означает, что потребитель может подключиться напрямую к провайдеру через открытый порт:

По сути, процедура похожа на традиционный веб-сервис, но Dubbo делает его простым, простым и легким.

4.4. Простой реестр

Обратите внимание, что при использовании «невидимого» многоадресного реестра служба реестра не является автономной. Однако это применимо только к ограниченной локальной сети.

Чтобы явно настроить управляемый реестр, мы можем использоватьSimpleRegistryService.

После загрузки следующей конфигурации bean-компонентов в контекст Spring запускается простая служба реестра:




    
        
    
    
        
    


Обратите внимание, что классSimpleRegistryService не содержится в артефакте, поэтому мы скопировалиsource code непосредственно из репозитория Github.

Затем мы настроим конфигурацию реестра провайдера и потребителя:

SimpleRegistryService можно использовать как автономный реестр при тестировании, но не рекомендуется использовать его в производственной среде.

4.5. Конфигурация Java

Конфигурация через Java API, файл свойств и аннотации также поддерживаются. Однако файл свойств и аннотации применимы только в том случае, если наша архитектура не очень сложна.

Давайте посмотрим, как наши предыдущие конфигурации XML для многоадресного реестра можно преобразовать в конфигурацию API. Во-первых, поставщик настраивается следующим образом:

ApplicationConfig application = new ApplicationConfig();
application.setName("demo-provider");
application.setVersion("1.0");

RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setAddress("multicast://224.1.1.1:9090");

ServiceConfig service = new ServiceConfig<>();
service.setApplication(application);
service.setRegistry(registryConfig);
service.setInterface(GreetingsService.class);
service.setRef(new GreetingsServiceImpl());

service.export();

Теперь, когда служба уже доступна через реестр многоадресной рассылки, давайте воспользуемся ею в локальном клиенте:

ApplicationConfig application = new ApplicationConfig();
application.setName("demo-consumer");
application.setVersion("1.0");

RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setAddress("multicast://224.1.1.1:9090");

ReferenceConfig reference = new ReferenceConfig<>();
reference.setApplication(application);
reference.setRegistry(registryConfig);
reference.setInterface(GreetingsService.class);

GreetingsService greetingsService = reference.get();
String hiMessage = greetingsService.sayHi("example");

Хотя приведенный выше фрагмент кода работает как шарм, как и предыдущий пример конфигурации XML, он немного более тривиален. В настоящее время XML-конфигурация должна быть первым выбором, если мы намерены в полной мере использовать Dubbo.

5. Поддержка протокола

Платформа поддерживает несколько протоколов, включаяdubbo,RMI,hessian,HTTP,web service,thrift,memcached иredisс. Большинство протоколов выглядит знакомо, за исключениемdubbo. Посмотрим, что нового в этом протоколе.

Протоколdubbo поддерживает постоянное соединение между поставщиками и потребителями. Длинное соединение и неблокирующая сетевая связь NIO обеспечивают довольно высокую производительность при передаче небольших пакетов данных (<100 КБ).

Существует несколько настраиваемых свойств, таких как порт, количество подключений на одного потребителя, максимально допустимые подключения и т. Д.

Dubbo также поддерживает предоставление сервисов через разные протоколы одновременно:





И да, мы можем предоставлять разные сервисы, используя разные протоколы, как показано во фрагменте выше. Базовые транспортеры, реализации сериализации и другие общие свойства, связанные с сетью, также можно настраивать.

6. Кеширование результатов

Для ускорения доступа к горячим данным поддерживается собственное удаленное кэширование результатов. Это так же просто, как добавить атрибут кеша в ссылку на компонент:

Здесь мы настроили наименее недавно использованный кеш. Чтобы проверить поведение кеширования, мы немного изменим предыдущую стандартную реализацию (назовем ее «специальной реализацией»):

public class GreetingsServiceSpecialImpl implements GreetingsService {
    @Override
    public String sayHi(String name) {
        try {
            SECONDS.sleep(5);
        } catch (Exception ignored) { }
        return "hi, " + name;
    }
}

После запуска провайдера мы можем проверить на стороне потребителя, что результат кэшируется при более чем однократном вызове:

@Test
public void givenProvider_whenConsumerSaysHi_thenGotResponse() {
    ClassPathXmlApplicationContext localContext
      = new ClassPathXmlApplicationContext("multicast/consumer-app.xml");
    localContext.start();
    GreetingsService greetingsService
      = (GreetingsService) localContext.getBean("greetingsService");

    long before = System.currentTimeMillis();
    String hiMessage = greetingsService.sayHi("example");

    long timeElapsed = System.currentTimeMillis() - before;
    assertTrue(timeElapsed > 5000);
    assertNotNull(hiMessage);
    assertEquals("hi, example", hiMessage);

    before = System.currentTimeMillis();
    hiMessage = greetingsService.sayHi("example");
    timeElapsed = System.currentTimeMillis() - before;

    assertTrue(timeElapsed < 1000);
    assertNotNull(hiMessage);
    assertEquals("hi, example", hiMessage);
}

Здесь потребитель вызывает реализацию специального сервиса, поэтому для первого вызова потребовалось более 5 секунд. Когда мы вызываем снова, методsayHi завершается почти сразу, так как результат возвращается из кеша.

Обратите внимание, что локальный кэш-поток и JCache также поддерживаются.

7. Кластерная поддержка

Dubbo помогает нам свободно расширять наши сервисы благодаря возможности балансировки нагрузки и нескольким стратегиям отказоустойчивости. Предположим, у нас есть Zookeeper в качестве реестра для управления службами в кластере. Поставщики могут зарегистрировать свои услуги в Zookeeper следующим образом:

Обратите внимание, что нам нужны эти дополнительные зависимости вPOM:


    org.apache.zookeeper
    zookeeper
    3.4.11


    com.101tec
    zkclient
    0.10

Последние версии зависимостейzookeeper иzkclient можно найти вhere иhere.

7.1. Балансировки нагрузки

В настоящее время платформа поддерживает несколько стратегий балансировки нагрузки:

  • случайный

  • по-круговой

  • наименее активным

  • самосогласованного хэш.

В следующем примере у нас есть две реализации сервиса в качестве провайдеров в кластере. Запросы направляются с использованием метода циклического перебора.

Во-первых, давайте настроим поставщиков услуг:

@Before
public void initRemote() {
    ExecutorService executorService = Executors.newFixedThreadPool(2);
    executorService.submit(() -> {
        ClassPathXmlApplicationContext remoteContext
          = new ClassPathXmlApplicationContext("cluster/provider-app-default.xml");
        remoteContext.start();
    });
    executorService.submit(() -> {
        ClassPathXmlApplicationContext backupRemoteContext
          = new ClassPathXmlApplicationContext("cluster/provider-app-special.xml");
        backupRemoteContext.start();
    });
}

Теперь у нас есть стандартный «быстрый провайдер», который реагирует немедленно, и специальный «медленный провайдер», который спит по 5 секунд на каждый запрос.

После 6 раз выполнения стратегии циклического перебора мы ожидаем, что среднее время отклика составит не менее 2,5 секунд:

@Test
public void givenProviderCluster_whenConsumerSaysHi_thenResponseBalanced() {
    ClassPathXmlApplicationContext localContext
      = new ClassPathXmlApplicationContext("cluster/consumer-app-lb.xml");
    localContext.start();
    GreetingsService greetingsService
      = (GreetingsService) localContext.getBean("greetingsService");

    List elapseList = new ArrayList<>(6);
    for (int i = 0; i < 6; i++) {
        long current = System.currentTimeMillis();
        String hiMessage = greetingsService.sayHi("example");
        assertNotNull(hiMessage);
        elapseList.add(System.currentTimeMillis() - current);
    }

    OptionalDouble avgElapse = elapseList
      .stream()
      .mapToLong(e -> e)
      .average();
    assertTrue(avgElapse.isPresent());
    assertTrue(avgElapse.getAsDouble() > 2500.0);
}

Кроме того, динамическое распределение нагрузки принимается. Следующий пример демонстрирует, что при использовании стратегии циклического перебора потребитель автоматически выбирает нового поставщика услуг в качестве кандидата, когда новый поставщик подключается к сети.

«Медленный провайдер» регистрируется через 2 секунды после запуска системы:

@Before
public void initRemote() {
    ExecutorService executorService = Executors.newFixedThreadPool(2);
    executorService.submit(() -> {
        ClassPathXmlApplicationContext remoteContext
          = new ClassPathXmlApplicationContext("cluster/provider-app-default.xml");
        remoteContext.start();
    });
    executorService.submit(() -> {
        SECONDS.sleep(2);
        ClassPathXmlApplicationContext backupRemoteContext
          = new ClassPathXmlApplicationContext("cluster/provider-app-special.xml");
        backupRemoteContext.start();
        return null;
    });
}

Потребитель вызывает удаленную службу один раз в секунду. Ожидается, что после 6 раз среднее время отклика превысит 1,6 секунды:

@Test
public void givenProviderCluster_whenConsumerSaysHi_thenResponseBalanced()
  throws InterruptedException {
    ClassPathXmlApplicationContext localContext
      = new ClassPathXmlApplicationContext("cluster/consumer-app-lb.xml");
    localContext.start();
    GreetingsService greetingsService
      = (GreetingsService) localContext.getBean("greetingsService");
    List elapseList = new ArrayList<>(6);
    for (int i = 0; i < 6; i++) {
        long current = System.currentTimeMillis();
        String hiMessage = greetingsService.sayHi("example");
        assertNotNull(hiMessage);
        elapseList.add(System.currentTimeMillis() - current);
        SECONDS.sleep(1);
    }

    OptionalDouble avgElapse = elapseList
      .stream()
      .mapToLong(e -> e)
      .average();

    assertTrue(avgElapse.isPresent());
    assertTrue(avgElapse.getAsDouble() > 1666.0);
}

Обратите внимание, что балансировщик нагрузки можно настроить как на стороне потребителя, так и на стороне поставщика. Вот пример конфигурации на стороне потребителя:

7.2. Отказоустойчивость

В Dubbo поддерживаются несколько стратегий отказоустойчивости, в том числе:

  • отказоустойчивость

  • Безотказный

  • отказоустойчивость быстро

  • Fail-обратно

  • разветвление.

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

Стратегии отказоустойчивости настраиваются для поставщиков услуг следующим образом:

Чтобы продемонстрировать отработку отказа службы в действии, давайте создадим реализацию отработки отказаGreetingsService:

public class GreetingsFailoverServiceImpl implements GreetingsService {

    @Override
    public String sayHi(String name) {
        return "hi, failover " + name;
    }
}

Мы можем вспомнить, что наша реализация специальной службыGreetingsServiceSpecialImpl спит 5 секунд на каждый запрос.

Когда любой ответ, который занимает более 2 секунд, рассматривается как сбой запроса для потребителя, у нас есть сценарий отработки отказа:

После запуска двух провайдеров мы можем проверить поведение при сбое с помощью следующего фрагмента:

@Test
public void whenConsumerSaysHi_thenGotFailoverResponse() {
    ClassPathXmlApplicationContext localContext
      = new ClassPathXmlApplicationContext(
      "cluster/consumer-app-failtest.xml");
    localContext.start();
    GreetingsService greetingsService
      = (GreetingsService) localContext.getBean("greetingsService");
    String hiMessage = greetingsService.sayHi("example");

    assertNotNull(hiMessage);
    assertEquals("hi, failover example", hiMessage);
}

8. Резюме

В этом уроке мы взяли небольшой кусочек Даббо. Большинство пользователей привлекают своей простотой и богатыми и мощными функциями.

Помимо того, что мы представили в этой статье, платформа имеет ряд функций, которые еще предстоит изучить, таких как проверка параметров, уведомление и обратный вызов, обобщенная реализация и ссылки, удаленная группировка и объединение результатов, обновление службы и обратная совместимость, и так далее. немного.

Как всегда, полную реализацию можно найти вover on Github.