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 vers 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 enregistrera son service dans le registre
  2. Conteneur - où le service est lancé, chargé et exécuté
  3. Consommateur - qui invoque des services à distance; un consommateur s'abonnera au service requis dans le registre
  4. Registre - où le service sera enregistré et découvert
  5. Moniteur - enregistre les statistiques des services, par exemple, la fréquence d'appel de service dans un intervalle de temps donné

(source: //dubbo.io/images/dubbo-architecture.png)

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 de Maven

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

 com.alibaba dubbo 2.5.7 

La dernière version peut être trouvée ici.

4. Amorçage

Essayons maintenant les fonctionnalités de base de Dubbo.

Il s'agit d'un cadre minimalement invasif, et bon nombre de ses fonctionnalités dépendent de configurations externes ou d'annotations.

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

Nous démontrerons la plupart de ses fonctionnalités en utilisant 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 multicast.

Dans l'exemple suivant, le fournisseur dit uniquement «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'interface GreetingsService doit être partagée avec le consommateur.

4.2. Registre de multidiffusion - Enregistrement de service

Enregistrons maintenant GreetingsService 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 notre GreetingsService à une URL sous dubbo: //127.0.0.1: 20880 et avons enregistré le service à une adresse de multidiffusion spécifiée dans.

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 , et .

Le protocole dubbo est l'un des nombreux protocoles pris en charge par le framework. Il est construit 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 un consommateur a besoin:

Maintenant que tout est mis en place, 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("baeldung"); assertNotNull(hiMessage); assertEquals("hi, baeldung", hiMessage); } }

Lorsque le remoteContext du fournisseur démarre, Dubbo chargera automatiquement GreetingsService 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 de GreetingsService dans le contexte. Lorsque notre client local appelle la méthode sayHi , 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 le rend simplement clair, simple et léger.

4.4. Registre simple

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

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

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

Notez que la classe SimpleRegistryService n'est pas contenue dans l'artefact, nous avons donc copié le code source directement à partir du référentiel Github.

Then we shall adjust the registry configuration of the provider and consumer:

SimpleRegistryService can be used as a standalone registry when testing, but it is not advised to be used in production environment.

4.5. Java Configuration

Configuration via Java API, property file, and annotations are also supported. However, property file and annotations are only applicable if our architecture isn't very complex.

Let's see how our previous XML configurations for multicast registry can be translated into API configuration. First, the provider is set up as follows:

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

Now that the service is already exposed via the multicast registry, let's consume it in a local client:

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

Though the snippet above works like a charm as the previous XML configuration example, it is a little more trivial. For the time being, XML configuration should be the first choice if we intend to make full use of Dubbo.

5. Protocol Support

The framework supports multiple protocols, including dubbo, RMI, hessian, HTTP, web service, thrift, memcached and redis. Most of the protocols looks familiar, except for dubbo. Let's see what's new in this protocol.

The dubbo protocol keeps a persistent connection between providers and consumers. The long connection and NIO non-blocking network communication result in a fairly great performance while transmitting small-scale data packets (<100K).

There are several configurable properties, such as port, number of connections per consumer, maximum accepted connections, etc.

Dubbo also supports exposing services via different protocols all at once:

And yes, we can expose different services using different protocols, as shown in the snippet above. The underlying transporters, serialization implementations and other common properties relating to networking are configurable as well.

6. Result Caching

Natively remote result caching is supported to speed up access to hot data. It's as simple as adding a cache attribute to the bean reference:

Here we configured a least-recently-used cache. To verify the caching behavior, we'll change a bit in the previous standard implementation (let's call it “special implementation”):

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

After starting up provider, we can verify on the consumer's side, that the result is cached when invoking more than once:

@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("baeldung"); long timeElapsed = System.currentTimeMillis() - before; assertTrue(timeElapsed > 5000); assertNotNull(hiMessage); assertEquals("hi, baeldung", hiMessage); before = System.currentTimeMillis(); hiMessage = greetingsService.sayHi("baeldung"); timeElapsed = System.currentTimeMillis() - before; assertTrue(timeElapsed < 1000); assertNotNull(hiMessage); assertEquals("hi, baeldung", hiMessage); }

Here the consumer is invoking the special service implementation, so it took more than 5 seconds for the invocation to complete the first time. When we invoke again, the sayHi method completes almost immediately, as the result is returned from the cache.

Note that thread-local cache and JCache are also supported.

7. Cluster Support

Dubbo helps us scale up our services freely with its ability of load balancing and several fault tolerance strategies. Here, let's assume we have Zookeeper as our registry to manage services in a cluster. Providers can register their services in Zookeeper like this:

Note that we need these additional dependencies in the POM:

 org.apache.zookeeper zookeeper 3.4.11   com.101tec zkclient 0.10 

The latest versions of zookeeper dependency and zkclient can be found here and here.

7.1. Load Balancing

Currently, the framework supports a few load-balancing strategies:

  • random
  • round-robin
  • least-active
  • consistent-hash.

In the following example, we have two service implementations as providers in a cluster. The requests are routed using the round-robin approach.

First, let's set up service providers:

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

Now we have a standard “fast provider” that responds immediately, and a special “slow provider” who sleeps for 5 seconds on every request.

After running 6 times with the round-robin strategy, we expect the average response time to be at least 2.5 seconds:

@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  e) .average(); assertTrue(avgElapse.isPresent()); assertTrue(avgElapse.getAsDouble() > 2500.0); }

Moreover, dynamic load balancing is adopted. The next example demonstrates that, with round-robin strategy, the consumer automatically chooses the new service provider as a candidate when the new provider comes online.

The “slow provider” is registered 2 seconds later after the system starts:

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

The consumer invokes the remote service once per second. After running 6 times, we expect the average response time to be greater than 1.6 seconds:

@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  e) .average(); assertTrue(avgElapse.isPresent()); assertTrue(avgElapse.getAsDouble() > 1666.0); }

Note that the load balancer can be configured both on the consumer's side and on the provider's side. Here's an example of consumer-side configuration:

7.2. Fault Tolerance

Several fault tolerance strategies are supported in Dubbo, including:

  • fail-over
  • fail-safe
  • fail-fast
  • fail-back
  • forking.

In the case of fail-over, when one provider fails, the consumer can try with some other service providers in the cluster.

The fault tolerance strategies are configured like the following for service providers:

To demonstrate service fail-over in action, let's create a fail-over implementation of GreetingsService:

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

We can recall that our special service implementation GreetingsServiceSpecialImpl sleeps 5 seconds for each request.

When any response that takes more than 2 seconds is seen as a request failure for the consumer, we have a fail-over scenario:

After starting two providers, we can verify the fail-over behavior with the following snippet:

@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("baeldung"); assertNotNull(hiMessage); assertEquals("hi, failover baeldung", hiMessage); }

8. Summary

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 cadre a encore 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 du service et la compatibilité descendante, pour ne citer que quelques.

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