Einführung in Dubbo

Einführung in Dubbo

1. Einführung

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

Unter anderem trägt es 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. Die Architektur

Dubbo unterscheidet einige Rollen:

  1. Provider - wo Service ausgesetzt ist; Ein Anbieter wird seinen Dienst bei der Registrierung registrieren

  2. Container - Hier wird der Dienst initiiert, geladen und ausgeführt

  3. Verbraucher - der entfernte Dienste aufruft; Ein Verbraucher abonniert den in der Registrierung benötigten Dienst

  4. Registry - 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

image

Die Verbindungen zwischen einem Anbieter, einem Verbraucher und einer Registrierung sind dauerhaft. Wenn ein Dienstanbieter inaktiv ist, 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, die Stabilität des gesamten Systems wäre jedoch beeinträchtigt.

3. Maven-Abhängigkeit

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


    com.alibaba
    dubbo
    2.5.7

Die neueste Version finden Sie unterhere.

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 mithilfe der XML-Konfiguration demonstrieren.

4.1. Multicast-Registrierung - Dienstanbieter

Als 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 nur "Hallo" zu seinen Verbrauchern:

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 teilen, daher muss die SchnittstelleGreetingsService mit dem Verbraucher geteilt werden.

4.2. Multicast-Registrierung - Dienstregistrierung

Registrieren wir jetztGreetingsService 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 unsereGreetingsService gerade einer URL unterdubbo://127.0.0.1:20880 ausgesetzt und den Dienst unter einer in<dubbo:registry /> angegebenen Multicast-Adresse registriert.

In der Konfiguration des Anbieters haben wir auch unsere Anwendungsmetadaten, die zu veröffentlichende Schnittstelle und deren Implementierung in<dubbo:application />,<dubbo:service /> und<beans /> deklariert.

Dasdubbo-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 später in diesem Artikel ausführlicher darauf eingehen.

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

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

Wenn dieremoteContext des Anbieters gestartet werden, lädt Dubbo automatischGreetingsService und registriert sie 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 vonGreetingsService. Wenn unser lokaler Client diesayHi-Methode aufruft, ruft er transparent einen Remotedienst auf.

Wir erwähnten, dass die Registrierung optional ist, was bedeutet, dass der Konsument über den exponierten Port eine direkte Verbindung zum Provider herstellen kann:

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

4.4. Einfache Registrierung

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

Um eine verwaltbare Registrierung explizit einzurichten, können wirSimpleRegistryService verwenden.

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




    
        
    
    
        
    


Beachten Sie, dass die KlasseSimpleRegistryServicenicht im Artefakt enthalten ist. Daher haben wir die Klassesource codedirekt aus dem Github-Repository kopiert.

Dann passen wir die Registry-Konfiguration von Provider und Consumer an:

SimpleRegistryService kann beim Testen als eigenständige Registrierung verwendet werden, es wird jedoch nicht empfohlen, sie in der Produktionsumgebung zu verwenden.

4.5. Java-Konfiguration

Die Konfiguration über die Java-API, die Eigenschaftendatei und Anmerkungen wird ebenfalls unterstützt. Eigenschaftendateien und Anmerkungen sind jedoch nur anwendbar, wenn unsere Architektur nicht sehr komplex ist.

Lassen Sie uns sehen, wie unsere früheren XML-Konfigurationen für die Multicast-Registrierung in die API-Konfiguration übersetzt werden können. Zunächst wird der Provider wie folgt eingerichtet:

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

Nachdem der Dienst bereits über die Multicast-Registrierung verfügbar gemacht wurde, können Sie ihn in einem lokalen Client verwenden:

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

Obwohl das obige Snippet wie das vorherige XML-Konfigurationsbeispiel funktioniert, ist es etwas trivialer. Derzeit sollte die XML-Konfiguration die erste Wahl sein, wenn Dubbo in vollem Umfang genutzt werden soll.

5. Protokollunterstützung

Das Framework unterstützt mehrere Protokolle, einschließlichdubbo,RMI,hessian,HTTP,web service,thrift,memcached undredis. Die meisten Protokolle kommen mir bekannt vor, mit Ausnahme vondubbo. Mal sehen, was in diesem Protokoll neu ist.

Dasdubbo-Protokoll hält eine dauerhafte Verbindung zwischen Anbietern und Verbrauchern aufrecht. Die lange Verbindung und die blockierungsfreie NIO-Netzwerkkommunikation führen zu einer relativ hohen Leistung bei der Übertragung von Datenpaketen im kleinen Maßstab (<100 KB).

Es gibt verschiedene konfigurierbare Eigenschaften, wie z. B. Port, Anzahl der Verbindungen pro Consumer, maximal akzeptierte Verbindungen usw.

Dubbo unterstützt auch die gleichzeitige Bereitstellung von Diensten über verschiedene Protokolle:





Und ja, wir können verschiedene Dienste mit verschiedenen Protokollen verfügbar machen, wie im obigen Snippet gezeigt. Die zugrunde liegenden Transporter, Serialisierungsimplementierungen und andere allgemeine Eigenschaften in Bezug auf das Netzwerk sind ebenfalls konfigurierbar.

6. Ergebnis-Caching

Das native Zwischenspeichern von Ergebnissen aus der Ferne wird unterstützt, um den Zugriff auf aktuelle Daten zu beschleunigen. Es ist so einfach wie das Hinzufügen eines Cache-Attributs zur Bean-Referenz:

Hier haben wir einen zuletzt verwendeten Cache konfiguriert. Um das Caching-Verhalten zu überprüfen, werden wir in der vorherigen Standardimplementierung einige Änderungen vornehmen (nennen wir es "spezielle Implementierung"):

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

Nach dem Start des Anbieters können wir auf Verbraucherseite überprüfen, ob das Ergebnis zwischengespeichert wird, wenn mehr als einmal aufgerufen wird:

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

Hier ruft der Verbraucher die Implementierung des speziellen Dienstes auf, sodass es mehr als 5 Sekunden dauerte, bis der Aufruf zum ersten Mal abgeschlossen war. Wenn wir erneut aufrufen, wird die MethodesayHifast sofort abgeschlossen, da das Ergebnis aus dem Cache zurückgegeben wird.

Beachten Sie, dass auch thread-lokaler Cache und JCache unterstützt werden.

7. Cluster-Unterstützung

Mit Dubbo können wir unsere Services durch die Möglichkeit des Lastausgleichs und verschiedene Fehlertoleranzstrategien frei skalieren. Nehmen wir hier an, wir haben Zookeeper als Registrierung für die Verwaltung von Diensten in einem Cluster. Anbieter können ihre Dienste in Zookeeper folgendermaßen registrieren:

Beachten Sie, dass wir diese zusätzlichen Abhängigkeiten inPOM benötigen:


    org.apache.zookeeper
    zookeeper
    3.4.11


    com.101tec
    zkclient
    0.10

Die neuesten Versionen vonzookeeper Abhängigkeit undzkclient finden Sie unterhere undhere.

7.1. Lastverteilung

Derzeit unterstützt das Framework einige Lastausgleichsstrategien:

  • zufällig

  • Round-Robin

  • am wenigsten aktiv

  • konsistenter Hash.

Im folgenden Beispiel haben wir zwei Service-Implementierungen als Provider in einem Cluster. Die Anfragen werden nach dem Round-Robin-Ansatz weitergeleitet.

Richten wir zunächst Dienstanbieter ein:

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

Jetzt haben wir einen standardmäßigen "schnellen Anbieter", der sofort reagiert, und einen speziellen "langsamen Anbieter", der bei jeder Anfrage 5 Sekunden lang schläft.

Nach sechs Durchläufen mit der Round-Robin-Strategie erwarten wir eine durchschnittliche Antwortzeit von mindestens 2,5 Sekunden:

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

Darüber hinaus wird ein dynamischer Lastausgleich angewendet. Das nächste Beispiel zeigt, dass der Verbraucher bei einer Round-Robin-Strategie automatisch den neuen Dienstanbieter als Kandidaten auswählt, wenn der neue Anbieter online geht.

Der "langsame Anbieter" wird 2 Sekunden später nach dem Start des Systems registriert:

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

Der Consumer ruft den Remote-Service einmal pro Sekunde auf. Nach sechs Durchläufen wird eine durchschnittliche Antwortzeit von mehr als 1,6 Sekunden erwartet:

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

Beachten Sie, dass der Load Balancer sowohl auf der Verbraucherseite als auch auf der Anbieterseite konfiguriert werden kann. Hier ist ein Beispiel für eine verbraucherseitige Konfiguration:

7.2. Fehlertoleranz

In Dubbo werden verschiedene Fehlertoleranzstrategien unterstützt, darunter:

  • Failover

  • ausfallsicher

  • ausfallsicher

  • Fail-Back

  • gabeln.

Wenn bei einem Failover ein Anbieter ausfällt, kann der Konsument es mit einigen anderen Dienstanbietern im Cluster versuchen.

Die Fehlertoleranzstrategien sind für Dienstanbieter wie folgt konfiguriert:

Um das Service-Failover in Aktion zu demonstrieren, erstellen wir eine Failover-Implementierung vonGreetingsService:

public class GreetingsFailoverServiceImpl implements GreetingsService {

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

Wir können uns daran erinnern, dass unsere spezielle Service-ImplementierungGreetingsServiceSpecialImpl für jede Anforderung 5 Sekunden lang schläft.

Wenn eine Antwort, die länger als 2 Sekunden dauert, als Anforderungsfehler für den Consumer angesehen wird, liegt ein Failover-Szenario vor:

Nachdem Sie zwei Provider gestartet haben, können Sie das Failover-Verhalten mit dem folgenden Snippet überprüfen:

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

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

Abgesehen von dem, was wir in diesem Artikel vorgestellt haben, muss das Framework noch eine Reihe von Funktionen untersuchen, wie Parameterüberprüfung, Benachrichtigung und Rückruf, allgemeine Implementierung und Referenz, Gruppierung und Zusammenführung von Remote-Ergebnissen, Serviceaktualisierung und Abwärtskompatibilität, um nur einige zu nennen ein paar.

Wie immer kann die vollständige Implementierungover on Github gefunden werden.