Introdução ao Dubbo

Introdução ao Dubbo

1. Introdução

Dubbo é um RPC de código aberto e estrutura de microsserviço do Alibaba.

Entre outras coisas, ajuda a aprimorar a governança de serviço e possibilita que um aplicativo monolítico tradicional seja refatorado sem problemas para uma arquitetura distribuída e escalável.

Neste artigo, daremos uma introdução ao Dubbo e seus recursos mais importantes.

2. Arquitetura

Dubbo distingue alguns papéis:

  1. Fornecedor - onde o serviço é exposto; um provedor registrará seu serviço no registro

  2. Contêiner - onde o serviço é iniciado, carregado e executado

  3. Consumidor - que invoca serviços remotos; um consumidor assinará o serviço necessário no registro

  4. Registro - onde o serviço será registrado e descoberto

  5. Monitor - registre estatísticas de serviços, por exemplo, frequência de chamada de serviço em um determinado intervalo de tempo

image

As conexões entre um provedor, um consumidor e um registro são persistentes; portanto, sempre que um provedor de serviços está inoperante, o registro pode detectar a falha e notificar os consumidores.

O registro e o monitor são opcionais. Os consumidores poderiam se conectar diretamente aos provedores de serviços, mas a estabilidade de todo o sistema seria afetada.

3. Dependência do Maven

Antes de começarmos, vamos adicionar a seguinte dependência ao nossopom.xml:


    com.alibaba
    dubbo
    2.5.7

A versão mais recente pode ser encontradahere.

4. Bootstrapping

Agora vamos experimentar os recursos básicos do Dubbo.

Essa é uma estrutura minimamente invasiva e muitos de seus recursos dependem de configurações ou anotações externas.

É sugerido oficialmente que devemos usar o arquivo de configuração XML porque ele depende de um contêiner Spring (atualmente Spring 4.3.10).

Vamos demonstrar a maioria de seus recursos usando a configuração XML.

4.1. Registro multicast - provedor de serviços

Para um início rápido, precisaremos apenas de um provedor de serviços, um consumidor e um registro "invisível". O registro é invisível porque estamos usando uma rede multicast.

No exemplo a seguir, o provedor apenas diz "oi" para seus consumidores:

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

public class GreetingsServiceImpl implements GreetingsService {

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

Para fazer uma chamada de procedimento remoto, o consumidor deve compartilhar uma interface comum com o provedor de serviços, portanto, a interfaceGreetingsService deve ser compartilhada com o consumidor.

4.2. Registro de multicast - Registro de serviço

Vamos agora registrarGreetingsService no registro. Uma maneira muito conveniente é usar um registro multicast se fornecedores e consumidores estiverem na mesma rede local:





Com a configuração de beans acima, acabamos de expor nossoGreetingsService a uma url emdubbo://127.0.0.1:20880e registramos o serviço para um endereço multicast especificado em<dubbo:registry />.

Na configuração do provedor, também declaramos nossos metadados de aplicativo, a interface a ser publicada e sua implementação respectivamente por<dubbo:application />,<dubbo:service />e<beans />

O protocolodubbo é um dos muitos protocolos que o framework suporta. Ele é construído em cima do recurso de não bloqueio Java NIO e é o protocolo padrão usado.

Discutiremos isso com mais detalhes posteriormente neste artigo.

4.3. Registro de multicast - consumidor de serviço

Geralmente, o consumidor precisa especificar a interface a ser invocada e o endereço do serviço remoto, e isso é exatamente o que é necessário para um consumidor:



Agora que está tudo configurado, vamos ver como funcionam em ação:

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);
    }
}

Quando oremoteContext do provedor é iniciado, o Dubbo carrega automaticamenteGreetingsService e o registra em um determinado registro. Nesse caso, é um registro multicast.

O consumidor assina o registro multicast e cria um proxy deGreetingsService no contexto. Quando nosso cliente local invoca o métodosayHi, ele invoca um serviço remoto de forma transparente.

Mencionamos que o registro é opcional, o que significa que o consumidor pode se conectar diretamente ao provedor, através da porta exposta:

Basicamente, o procedimento é semelhante ao serviço da Web tradicional, mas o Dubbo apenas o torna claro, simples e leve.

4.4. Registro Simples

Observe que, ao usar um registro multicast “invisível”, o serviço de registro não é autônomo. No entanto, é aplicável apenas a uma rede local restrita.

Para configurar explicitamente um registro gerenciável, podemos usar umSimpleRegistryService.

Após carregar a seguinte configuração de beans no contexto do Spring, um serviço de registro simples é iniciado:




    
        
    
    
        
    


Observe que a classeSimpleRegistryService não está contida no artefato, portanto, copiamossource code diretamente do repositório Github.

Em seguida, ajustaremos a configuração do registro do provedor e consumidor:

SimpleRegistryService pode ser usado como um registro independente durante o teste, mas não é recomendado para ser usado em ambiente de produção.

4.5. Configuração Java

A configuração via API Java, arquivo de propriedades e anotações também são suportadas. No entanto, o arquivo de propriedade e as anotações são aplicáveis ​​apenas se nossa arquitetura não for muito complexa.

Vamos ver como nossas configurações XML anteriores para registro multicast podem ser traduzidas em configuração de API. Primeiro, o provedor é configurado da seguinte maneira:

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();

Agora que o serviço já está exposto por meio do registro multicast, vamos consumi-lo em um cliente local:

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");

Embora o trecho acima funcione como um encanto como o exemplo anterior de configuração XML, é um pouco mais trivial. Por enquanto, a configuração XML deve ser a primeira opção, se pretendermos fazer pleno uso do Dubbo.

5. Suporte de protocolo

A estrutura oferece suporte a vários protocolos, incluindodubbo,RMI,hessian,HTTP,web service,thrift,memcached eredis. A maioria dos protocolos parece familiar, exceto paradubbo. Vamos ver o que há de novo neste protocolo.

O protocolodubbo mantém uma conexão persistente entre provedores e consumidores. A conexão longa e a comunicação de rede sem bloqueio da NIO resultam em um desempenho bastante bom ao transmitir pacotes de dados em pequena escala (<100K).

Existem várias propriedades configuráveis, como porta, número de conexões por consumidor, número máximo de conexões aceitas, etc.

O Dubbo também suporta a exposição de serviços através de diferentes protocolos de uma só vez:





E sim, podemos expor serviços diferentes usando protocolos diferentes, conforme mostrado no snippet acima. Os transportadores subjacentes, implementações de serialização e outras propriedades comuns relacionadas à rede também são configuráveis.

6. Cache de resultados

O cache nativo de resultados remotos é suportado para acelerar o acesso a dados quentes. É tão simples quanto adicionar um atributo cache à referência do bean:

Aqui, configuramos um cache usado menos recentemente. Para verificar o comportamento do cache, vamos mudar um pouco na implementação padrão anterior (vamos chamá-la de “implementação especial”):

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

Depois de inicializar o provedor, podemos verificar do lado do consumidor se o resultado é armazenado em cache ao invocar mais de uma vez:

@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);
}

Aqui, o consumidor está invocando a implementação do serviço especial; portanto, levou mais de 5 segundos para a invocação ser concluída na primeira vez. Quando invocamos novamente, o métodosayHi é concluído quase imediatamente, pois o resultado é retornado do cache.

Observe que o cache local do encadeamento e o JCache também são suportados.

7. Suporte de cluster

O Dubbo nos ajuda a ampliar nossos serviços livremente com sua capacidade de balanceamento de carga e várias estratégias de tolerância a falhas. Aqui, vamos supor que temos o Zookeeper como nosso registro para gerenciar serviços em um cluster. Os fornecedores podem registrar seus serviços no Zookeeper assim:

Observe que precisamos dessas dependências adicionais emPOM:


    org.apache.zookeeper
    zookeeper
    3.4.11


    com.101tec
    zkclient
    0.10

As versões mais recentes da dependência dezookeeper ezkclient podem ser encontradashereehere.

7.1. Balanceamento de carga

Atualmente, a estrutura suporta algumas estratégias de balanceamento de carga:

  • aleatória

  • round-robin

  • menos ativo

  • hash consistente.

No exemplo a seguir, temos duas implementações de serviço como provedores em um cluster. As solicitações são roteadas usando a abordagem round-robin.

Primeiro, vamos configurar os provedores de serviços:

@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();
    });
}

Agora, temos um "provedor rápido" padrão que responde imediatamente e um "provedor lento" especial que dorme por 5 segundos em cada solicitação.

Depois de executar 6 vezes com a estratégia round-robin, esperamos que o tempo médio de resposta seja de pelo menos 2,5 segundos:

@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);
}

Além disso, o balanceamento de carga dinâmico é adotado. O próximo exemplo demonstra que, com a estratégia round-robin, o consumidor escolhe automaticamente o novo provedor de serviços como candidato quando o novo provedor fica online.

O "provedor lento" é registrado 2 segundos depois após o sistema ser iniciado:

@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;
    });
}

O consumidor chama o serviço remoto uma vez por segundo. Após executar seis vezes, esperamos que o tempo médio de resposta seja maior que 1,6 segundos:

@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);
}

Observe que o balanceador de carga pode ser configurado tanto no lado do consumidor quanto no lado do provedor. Aqui está um exemplo de configuração do lado do consumidor:

7.2. Tolerância ao erro

Várias estratégias de tolerância a falhas são suportadas no Dubbo, incluindo:

  • failover

  • à prova de falhas

  • rápido

  • retorno

  • bifurcação.

No caso de failover, quando um provedor falha, o consumidor pode tentar com outros fornecedores de serviços no cluster.

As estratégias de tolerância a falhas são configuradas da seguinte maneira para os provedores de serviços:

Para demonstrar o failover de serviço em ação, vamos criar uma implementação de failover deGreetingsService:

public class GreetingsFailoverServiceImpl implements GreetingsService {

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

Podemos lembrar que nossa implementação de serviço especialGreetingsServiceSpecialImpl dorme 5 segundos para cada solicitação.

Quando qualquer resposta que leva mais de 2 segundos é vista como uma falha de solicitação para o consumidor, temos um cenário de failover:

Após iniciar dois provedores, podemos verificar o comportamento de failover com o seguinte trecho:

@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. Sumário

Neste tutorial, demos uma pequena mordida no Dubbo. A maioria dos usuários é atraída por sua simplicidade e recursos ricos e poderosos.

Além do que introduzimos neste artigo, a estrutura ainda tem vários recursos a serem explorados, como validação de parâmetros, notificação e retorno de chamada, implementação e referência generalizada, agrupamento e mesclagem remota de resultados, atualização de serviço e compatibilidade com versões anteriores, para citar apenas um pouco.

Como sempre, a implementação completa pode ser encontradaover on Github.