Einführung in Dubbo

1. Einleitung

Dubbo ist ein Open-Source-RPC- und Microservice-Framework von Alibaba.

Dies trägt unter anderem zur Verbesserung der Service-Governance bei und ermöglicht die reibungslose Umgestaltung traditioneller Monolith-Anwendungen zu einer skalierbaren verteilten Architektur.

In diesem Artikel geben wir eine Einführung in Dubbo und seine wichtigsten Funktionen.

2. Architektur

Dubbo unterscheidet einige Rollen:

  1. Anbieter - wo der Dienst ausgesetzt ist; Ein Anbieter registriert seinen Dienst bei der Registrierung
  2. Container - wo der Dienst initiiert, geladen und ausgeführt wird
  3. Verbraucher - der Remote-Dienste aufruft; Ein Verbraucher abonniert den in der Registrierung benötigten Dienst
  4. Registrierung - Hier wird der Dienst registriert und entdeckt
  5. Überwachen - Aufzeichnen von Statistiken für Dienste, z. B. Häufigkeit des Dienstaufrufs in einem bestimmten Zeitintervall

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

Die Verbindungen zwischen einem Anbieter, einem Verbraucher und einer Registrierung sind dauerhaft. Wenn also ein Dienstanbieter ausfällt, kann die Registrierung den Fehler erkennen und die Verbraucher benachrichtigen.

Die Registrierung und der Monitor sind optional. Verbraucher könnten sich direkt mit Dienstanbietern verbinden, aber die Stabilität des gesamten Systems würde beeinträchtigt.

3. Maven-Abhängigkeit

Bevor wir eintauchen, fügen wir unserer pom.xml die folgende Abhängigkeit hinzu :

 com.alibaba dubbo 2.5.7 

Die neueste Version finden Sie hier.

4. Bootstrapping

Probieren wir nun die Grundfunktionen von Dubbo aus.

Dies ist ein minimal invasives Framework, und viele seiner Funktionen hängen von externen Konfigurationen oder Anmerkungen ab.

Es wird offiziell empfohlen, eine XML-Konfigurationsdatei zu verwenden, da diese von einem Spring-Container abhängt (derzeit Spring 4.3.10).

Wir werden die meisten Funktionen anhand der XML-Konfiguration demonstrieren.

4.1. Multicast-Registrierung - Dienstanbieter

Für einen schnellen Start benötigen wir nur einen Dienstanbieter, einen Verbraucher und eine „unsichtbare“ Registrierung. Die Registrierung ist unsichtbar, da wir ein Multicast-Netzwerk verwenden.

Im folgenden Beispiel sagt der Anbieter seinen Verbrauchern nur „Hallo“:

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

Um einen Remoteprozeduraufruf durchzuführen, muss der Verbraucher eine gemeinsame Schnittstelle mit dem Dienstanbieter gemeinsam nutzen. Daher muss die Schnittstelle GreetingsService für den Verbraucher freigegeben werden.

4.2. Multicast-Registrierung - Dienstregistrierung

Registrieren wir nun GreetingsService in der Registrierung. Eine sehr bequeme Möglichkeit ist die Verwendung einer Multicast-Registrierung, wenn sich sowohl Anbieter als auch Verbraucher im selben lokalen Netzwerk befinden:

Mit der obigen Beans-Konfiguration haben wir unseren GreetingsService gerade einer URL unter dubbo: //127.0.0.1: 20880 ausgesetzt und den Service unter einer in angegebenen Multicast-Adresse registriert.

In der Konfiguration des Anbieters haben wir auch unsere Anwendungsmetadaten deklariert, die zu veröffentlichende Schnittstelle und deren Implementierung , und .

Das Dubbo- Protokoll ist eines von vielen Protokollen, die das Framework unterstützt. Es basiert auf der nicht blockierenden Java NIO-Funktion und ist das verwendete Standardprotokoll.

Wir werden es später in diesem Artikel genauer besprechen.

4.3. Multicast-Registrierung - Service Consumer

Im Allgemeinen muss der Verbraucher die aufzurufende Schnittstelle und die Adresse des Remote-Dienstes angeben, und genau das wird für einen Verbraucher benötigt:

Jetzt ist alles eingerichtet. Mal sehen, wie sie in Aktion funktionieren:

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

Wenn der remoteContext des Anbieters gestartet wird , lädt Dubbo automatisch GreetingsService und registriert ihn in einer bestimmten Registrierung. In diesem Fall handelt es sich um eine Multicast-Registrierung.

Der Verbraucher abonniert die Multicast-Registrierung und erstellt im Kontext einen Proxy von GreetingsService . Wenn unser lokaler Client die sayHi- Methode aufruft, ruft er transparent einen Remotedienst auf.

Wir haben erwähnt, dass die Registrierung optional ist, was bedeutet, dass der Verbraucher über den exponierten Port eine direkte Verbindung zum Anbieter herstellen kann:

Grundsätzlich ähnelt das Verfahren dem herkömmlichen Webdienst, aber Dubbo macht es einfach, leicht und leicht.

4.4. Einfache Registrierung

Beachten Sie, dass bei Verwendung einer „unsichtbaren“ Multicast-Registrierung der Registrierungsdienst nicht eigenständig ist. Dies gilt jedoch nur für ein eingeschränktes lokales Netzwerk.

Um eine verwaltbare Registrierung explizit einzurichten, können wir einen SimpleRegistryService verwenden .

Nach dem Laden der folgenden Beans-Konfiguration in den Spring-Kontext wird ein einfacher Registrierungsdienst gestartet:

Beachten Sie, dass die SimpleRegistryService- Klasse nicht im Artefakt enthalten ist. Daher haben wir den Quellcode direkt aus dem Github-Repository kopiert.

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

In diesem Tutorial haben wir einen kleinen Bissen von Dubbo genommen. Die meisten Benutzer sind von der Einfachheit und den umfangreichen und leistungsstarken Funktionen angezogen.

Abgesehen von dem, was wir in diesem Artikel vorgestellt haben, verfügt das Framework über eine Reihe von Funktionen, die noch untersucht werden müssen, z. B. Parameterüberprüfung, Benachrichtigung und Rückruf, allgemeine Implementierung und Referenz, Gruppierung und Zusammenführung von Remote-Ergebnissen, Service-Upgrade und Abwärtskompatibilität, um nur einige zu nennen ein paar.

Wie immer finden Sie die vollständige Implementierung auf Github.