Introduction à Dubbo

Introduction à Dubbo

1. introduction

Dubbo est un framework RPC et microservice open-source d'Alibaba.

Entre autres choses, il contribue à améliorer la gouvernance des services et permet aux applications monolithiques traditionnelles d'être refactorisées en douceur en une architecture distribuée évolutive.

Dans cet article, nous vous présenterons Dubbo et ses fonctionnalités les plus importantes.

2. Architecture

Dubbo distingue quelques rôles:

  1. Fournisseur - où le service est exposé; un fournisseur va enregistrer son service dans un registre

  2. Conteneur - où le service est lancé, chargé et exécuté

  3. Consommateur - qui appelle des services distants; un consommateur s'abonnera au service requis dans le registre

  4. Registre - où le service sera enregistré et découvert

  5. Surveiller - enregistrer des statistiques pour les services, par exemple, la fréquence d’invocation de service dans un intervalle de temps donné

image

Les connexions entre un fournisseur, un consommateur et un registre sont persistantes. Ainsi, chaque fois qu'un fournisseur de services est en panne, le registre peut détecter l'échec et avertir les consommateurs.

Le registre et le moniteur sont facultatifs. Les consommateurs pourraient se connecter directement aux fournisseurs de services, mais la stabilité de l’ensemble du système en serait affectée.

3. Dépendance Maven

Avant de plonger, ajoutons la dépendance suivante à nospom.xml:


    com.alibaba
    dubbo
    2.5.7

La dernière version peut être trouvéehere.

4. Bootstrapping

Essayons maintenant les fonctionnalités de base de Dubbo.

Il s’agit d’un framework peu invasif et nombre de ses fonctionnalités dépendent de configurations ou d’annotations externes.

Il est officiellement suggéré que nous devrions utiliser le fichier de configuration XML car il dépend d'un conteneur Spring (actuellement Spring 4.3.10).

Nous présenterons la plupart de ses fonctionnalités à l'aide de la configuration XML.

4.1. Registre de multidiffusion - Fournisseur de services

Pour commencer rapidement, nous n’aurons besoin que d’un fournisseur de services, d’un consommateur et d’un registre «invisible». Le registre est invisible car nous utilisons un réseau de multidiffusion.

Dans l'exemple suivant, le fournisseur ne dit que «bonjour» à ses consommateurs:

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

public class GreetingsServiceImpl implements GreetingsService {

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

Pour effectuer un appel de procédure à distance, le consommateur doit partager une interface commune avec le fournisseur de services, ainsi l'interfaceGreetingsService doit être partagée avec le consommateur.

4.2. Registre de multidiffusion - Enregistrement de service

Enregistrons maintenantGreetingsService dans le registre. Un moyen très pratique consiste à utiliser un registre de multidiffusion si les fournisseurs et les consommateurs se trouvent sur le même réseau local:





Avec la configuration des beans ci-dessus, nous venons d'exposer nosGreetingsService à une url sousdubbo://127.0.0.1:20880 et avons enregistré le service à une adresse multicast spécifiée dans<dubbo:registry />.

Dans la configuration du fournisseur, nous avons également déclaré les métadonnées de notre application, l'interface à publier et son implémentation respectivement par<dubbo:application />,<dubbo:service /> et<beans />.

Le protocoledubbo est l'un des nombreux protocoles pris en charge par le framework. Il repose sur la fonctionnalité non bloquante Java NIO et c'est le protocole par défaut utilisé.

Nous en discuterons plus en détail plus loin dans cet article.

4.3. Registre de multidiffusion - Consommateur de services

En règle générale, le consommateur doit spécifier l’interface à appeler et l’adresse du service distant, et c’est exactement ce dont le consommateur a besoin:



Maintenant que tout est configuré, voyons comment ils fonctionnent en action:

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

Lorsque le fournisseurremoteContext démarre, Dubbo chargera automatiquementGreetingsService et l'enregistrera dans un registre donné. Dans ce cas, il s’agit d’un registre multicast.

Le consommateur s'abonne au registre de multidiffusion et crée un proxy deGreetingsService dans le contexte. Lorsque notre client local appelle la méthodesayHi, il appelle de manière transparente un service distant.

Nous avons mentionné que le registre est facultatif, ce qui signifie que le consommateur peut se connecter directement au fournisseur, via le port exposé:

Fondamentalement, la procédure est similaire au service Web traditionnel, mais Dubbo la rend simple, simple et légère.

4.4. Registre simple

Notez que lorsque vous utilisez un registre de multidiffusion «invisible», le service de registre n'est pas autonome. Cependant, il ne s’applique qu’à un réseau local restreint.

Pour configurer explicitement un registre gérable, nous pouvons utiliser unSimpleRegistryService.

Après avoir chargé la configuration de beans suivante dans le contexte Spring, un service de registre simple est démarré:




    
        
    
    
        
    


Notez que la classeSimpleRegistryService n'est pas contenue dans l'artefact, nous avons donc copié lessource code directement depuis le référentiel Github.

Ensuite, nous ajusterons la configuration du registre du fournisseur et du consommateur:

SimpleRegistryService peut être utilisé comme registre autonome lors des tests, mais il n'est pas conseillé de l'utiliser dans un environnement de production.

4.5. Configuration Java

La configuration via l'API Java, le fichier de propriétés et les annotations sont également pris en charge. Cependant, le fichier de propriétés et les annotations ne sont applicables que si notre architecture n'est pas très complexe.

Voyons comment nos précédentes configurations XML pour le registre de multidiffusion peuvent être traduites en configuration d'API. Tout d'abord, le fournisseur est configuré comme suit:

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

Maintenant que le service est déjà exposé via le registre de multidiffusion, consommons-le dans un client 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");

Bien que l'extrait ci-dessus fonctionne comme un charme comme dans l'exemple de configuration XML précédent, il est un peu plus trivial. Pour le moment, la configuration XML devrait être le premier choix si nous voulons utiliser pleinement Dubbo.

5. Prise en charge du protocole

Le framework prend en charge plusieurs protocoles, y comprisdubbo,RMI,hessian,HTTP,web service,thrift,memcached etredis. La plupart des protocoles semblent familiers, à l'exception dedubbo. Voyons les nouveautés de ce protocole.

Le protocoledubbo maintient une connexion permanente entre les fournisseurs et les consommateurs. La longue connexion et la communication réseau non bloquante NIO se traduisent par des performances assez élevées lors de la transmission de paquets de données à petite échelle (<100K).

Il existe plusieurs propriétés configurables, telles que le port, le nombre de connexions par consommateur, le nombre maximal de connexions acceptées, etc.

Dubbo prend également en charge l’exposition simultanée de services via différents protocoles:





Et oui, nous pouvons exposer différents services en utilisant différents protocoles, comme indiqué dans l'extrait de code ci-dessus. Les transporteurs sous-jacents, les implémentations de sérialisation et d'autres propriétés communes relatives à la mise en réseau sont également configurables.

6. Mise en cache des résultats

La mise en cache des résultats à distance est prise en charge pour accélérer l’accès aux données dynamiques. C'est aussi simple que d'ajouter un attribut de cache à la référence du bean:

Ici, nous avons configuré un cache le moins récemment utilisé. Pour vérifier le comportement de la mise en cache, nous allons modifier un peu la mise en œuvre standard précédente (appelons-la "mise en œuvre spéciale"):

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

Après le démarrage du fournisseur, nous pouvons vérifier, du côté du consommateur, que le résultat est mis en cache lors de plusieurs appels:

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

Ici, le consommateur appelle l'implémentation du service spécial, il a donc fallu plus de 5 secondes pour que l'invocation soit terminée pour la première fois. Lorsque nous appelons à nouveau, la méthodesayHi se termine presque immédiatement, car le résultat est renvoyé depuis le cache.

Notez que le cache local des threads et JCache sont également pris en charge.

7. Prise en charge des clusters

Dubbo nous aide à élargir librement nos services grâce à sa capacité d’équilibrage de charge et à plusieurs stratégies de tolérance aux pannes. Ici, supposons que nous ayons Zookeeper comme registre pour gérer les services dans un cluster. Les fournisseurs peuvent enregistrer leurs services dans Zookeeper comme ceci:

Notez que nous avons besoin de ces dépendances supplémentaires dans lesPOM:


    org.apache.zookeeper
    zookeeper
    3.4.11


    com.101tec
    zkclient
    0.10

Les dernières versions de la dépendancezookeeper et dezkclient peuvent être trouvéeshere ethere.

7.1. L'équilibrage de charge

Actuellement, le cadre prend en charge quelques stratégies d’équilibrage de charge:

  • au hasard

  • tournoi à la ronde

  • moins actif

  • cohérent-hash.

Dans l'exemple suivant, nous avons deux implémentations de services en tant que fournisseurs dans un cluster. Les demandes sont acheminées en utilisant l'approche à tour de rôle.

Commençons par configurer les fournisseurs de services:

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

Nous avons maintenant un «fournisseur rapide» standard qui répond immédiatement et un «fournisseur lent» spécial qui dort 5 secondes à chaque demande.

Après avoir exécuté 6 fois la stratégie de tournoi à la ronde, nous nous attendons à ce que le temps de réponse moyen soit d'au moins 2,5 secondes:

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

De plus, l'équilibrage dynamique de la charge est adopté. L'exemple suivant montre qu'avec la stratégie alternée, le consommateur choisit automatiquement le nouveau fournisseur de services comme candidat lorsque le nouveau fournisseur se connecte.

Le «fournisseur lent» est enregistré 2 secondes plus tard après le démarrage du système:

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

Le consommateur appelle le service distant une fois par seconde. Après avoir exécuté 6 fois, nous prévoyons que le temps de réponse moyen sera supérieur à 1,6 seconde:

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

Notez que l'équilibreur de charge peut être configuré à la fois du côté du consommateur et du côté du fournisseur. Voici un exemple de configuration côté consommateur:

7.2. Tolérance aux pannes

Dubbo prend en charge plusieurs stratégies de tolérance aux pannes, notamment:

  • basculement

  • sécurité

  • échec rapide

  • retour en arrière

  • forking.

En cas de basculement, lorsqu'un fournisseur échoue, le consommateur peut essayer avec d'autres fournisseurs de services du cluster.

Les stratégies de tolérance aux pannes sont configurées comme suit pour les fournisseurs de services:

Pour illustrer le basculement de service en action, créons une implémentation de basculement deGreetingsService:

public class GreetingsFailoverServiceImpl implements GreetingsService {

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

Nous pouvons rappeler que notre implémentation de service spécialGreetingsServiceSpecialImpl dort 5 secondes pour chaque requête.

Lorsqu'une réponse prenant plus de 2 secondes est considérée comme un échec de la demande pour le consommateur, nous avons un scénario de basculement:

Après avoir démarré deux fournisseurs, nous pouvons vérifier le comportement du basculement avec l'extrait de code suivant:

@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. Sommaire

Dans ce tutoriel, nous avons pris une petite bouchée de Dubbo. La plupart des utilisateurs sont attirés par sa simplicité et ses fonctionnalités riches et puissantes.

Outre ce que nous avons présenté dans cet article, le framework présente un certain nombre de fonctionnalités à explorer, telles que la validation des paramètres, la notification et le rappel, l'implémentation et la référence généralisées, le regroupement et la fusion de résultats à distance, la mise à niveau de services et la compatibilité ascendante, pour ne nommer que quelques.

Comme toujours, l'implémentation complète peut être trouvéeover on Github.