Vue d'ensemble de l'architecture
L'architecture de Dubbo repose sur cinq composants principaux :
- Provider : Le nœud qui expose les services disponibles.
- Consumer : L'application client qui invoque les services distants.
- Registry : Le centre d'enregistrement pour la publication et la découverte des services.
- Monitor : Le centre de surveillance collectant les statistiques d'appels et les temps de réponse.
- Container : Le conteneur d'exécution qui héberge les services.
Dynamique des interactions
Le cycle de vie et les échanges entre composants suivent cette séquence :
- Le conteneur d'exécution initialise, charge et lance les fournisseurs de services.
- Au démarrage, chaque provider s'enregistre auprès du registry avec la liste des services qu'il expose.
- Simultanément, les consumers souscrivent aux services dont ils ont besoin auprès du même registry.
- Le registry transmet la liste des adresses des providers au consumer. En cas de modification, il propage les changements via une connexion persistante.
- Le consumer applique un algorithme de répartition de charge logicielle sur la liste d'adresses pour sélectionner un provider spécifique. Si l'appel échoue, un autre nœud est tenté.
- Les appels effectués et leurs durées sont accumulés en mémoire par les consumers et providers, puis synchronisés avec le monitor chaque minute.
- Des connexions longues sont maintenues entre le registry, les providers et les consumers. Le monitor fait exception.
- Le registry détecte la présence des providers via la connexion. Si un provider tombe en panne, le registry notifie immédiatement les consumers affectés.
- L'indisponibilité du registry ou du monitor n'affecte pas le fonctionnement des couples provider-consumer déjà établis, car les consumers conservent une copie locale de la liste des providers.
Processus d'invocation
La mécanique d'appel des services implique plusieurs abstractions :
0. L'objet Invoker est une abstraction d'un service invocable chez le Provider. Il encapsule son adresse et l'interface du service.
1. Un Directory représente une collection d'Invoker, similaire à une Liste, mais dont le contenu peut évoluer dynamiquement (ex: notification du registry).
2. Le Cluster masque la collection d'Invoker du Directory en un Invoker unique pour la couche supérieure. Cette abstraction intègre la logique de tolérance aux pannes (réessai sur un autre nœud).
3. Le Router sélectionne un sous-ensemble d'Invoker selon des règles de routage (ex: isolation d'applications, séparation lecture/écriture).
4. Le LoadBalance désigne un Invoker spécifique pour l'appel courant, en appliquant un algorithme de répartition. En cas d'échec, une nouvelle sélection est opérée.
Priorité de configuration
Paramètres de service
La granularité la plus fine l'emporte : méthode > interface > configuration globale. À niveau égal, la configuration du consumer est prioritaire sur celle du provider.
Fichiers de configuration
L'ordre de priorité est : paramètres JVM (options -D) > fichiers XML > fichiers properties. Les paramètres JVM permettent une surcharge au déploiement. Le XML remplace les propriétés correspondantes. Les properties servent de valeurs par défaut partagées.
Fonctionnalités clés
Vérification au démarrage
Dubbo vérifie par défaut la disponibilité des services dépendants au démarrage (check=true). Une exception est levée si un service est indisponible, bloquant l'initialisation du conteneur Spring.
# Pour un chargement paresseux ou une référence différée via l'API, désactivez la vérification.
# Sinon, une indisponibilité temporaire entraînera une exception et une référence nulle.
# Avec check=false, la référence est toujours retournée et la connexion s'établira automatiquement quand le service sera rétabli.
# Utile pour contourner des dépendances cycliques ou en phase de test.
<dubbo:reference interface="com.example.UserService" check="false" />
Stratégies de tolérance aux pannes
Failover Cluster (par défaut)
# Basculement automatique vers un autre serveur en cas d'échec, avec tentatives de réessai.
# Idéal pour les opérations de lecture. Augmente la latence. Configurez le nombre de rétentatives (retries="2").
Failfast Cluster
# Échec immédiat sans réessai. Pour les opérations non idempotentes comme l'écriture.
Failsafe Cluster
# Ignorer l'exception. Pour les opérations sans impact critique (ex: journal d'audit).
Failback Cluster
# Enregistrement de la requête échouée et réenvoi asynchrone. Pour la notification.
Forking Cluster
# Appels parallèles à plusieurs serveurs ; retour dès la première réponse. Pour les lectures critiques. Coût en ressources.
Broadcast Cluster
# Appel séquentiel à tous les providers ; échec si un échoue. Pour la synchronisation de caches.
Note importante : Limitez systématiquement le nombre de tentatives (ex: retries="2") pour éviter des rafales d'appels en cas de panne généralisée.
Limitation des tentatives via XML :
<dubbo:service retries="2" />
Ou par méthode :
<dubbo:reference>
<dubbo:method name="getProduct" retries="2" />
</dubbo:reference>
Répartition de charge (Load Balancing)
Random (Aléatoire)
# Sélection aléatoire pondérée. La distribution devient uniforme avec le volume d'appels. Permet un ajustement dynamique des poids.
RoundRobin (Tourniquet)
# Cycle équitable basé sur les poids normalisés. Risque de blocage sur un nœud lent.
LeastActive (Moins actif)
# Priorise les nœuds avec le moins d'appels en cours. Protège les nœuds lents.
ConsistentHash (Hachage cohérent)
# Les requêtes identiques (paramètres) sont routées vers le même nœud. En cas de panne, la charge est redistribuée uniformément.
# Configuration : hachage sur les arguments (hash.arguments) et nombre de nœuds virtuels (hash.nodes).
Modèle de threads
Traitement des événements : Si la logique est rapide et sans E/S, exécuter directement sur le thread I/O. Sinon, déléguer à un pool pour éviter le blocage.
Stratégies de dispatch (défaut : all)
all : Tous les messages (requêtes, réponses, événements) au pool de threads.
direct : Tout traité sur le thread I/O.
message : Seules les requêtes/réponses au pool ; les événements sur le thread I/O.
execution : Seules les requêtes au pool ; les réponses et événements sur le thread I/O.
connection : Les événements de connexion sont mis en file d'attente et traités séquentiellement ; les autres au pool.
Types de pool de threads (défaut : fixed)
fixed : Taille fixe, threads créés au démarrage.
cached : Pool élastique avec suppression des threads inactifs après 1 minute.
limited : Pool extensible mais non rétractable (évite les pics de charge lors de la réduction).
Exemple de configuration du protocole :
<dubbo:protocol name="dubbo" dispatcher="all" threadpool="fixed" threads="100" />
Paramètres importants :
threads : Taille du pool de service (fixed).
iothreads : Taille du pool I/O (défaut : nombre de cœurs CPU + 1, pour epoll).
Protocoles multiples
Différents services peuvent utiliser des protocoles adaptés : connexion courte pour les gros volumes, connexion longue pour les hautes fréquences. Dubbo utilise le protocole dubbo (connexion longue, limité en taille). Pour les fichiers larges, privilégiez RMI.
Enregistrements, groupes et versions multiples
Regroupement d'appels (Group-Aggregation) :
<dubbo:reference interface="com.example.MenuService" group="*">
<dubbo:method name="getItems" merger=".addAll" />
</dubbo:reference>
Le merge peut être spécifié par une méthode (merger="myMerge") ou par référence à une méthode du type de retour (merger=".addAll").
Validation avec JSR-303
Dépendances :
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
Activation :
<dubbo:reference interface="com.example.ValidatedService" validation="true" />
Mise en cache des résultats
Dubbo propose une mise en cache déclarative (LRU) pour réduire les accès fréquents :
<dubbo:reference interface="com.example.BarService">
<dubbo:method name="findBar" cache="lru" />
</dubbo:reference>
Appels génériques
Utilisation de Map à la place des POJO pour l'invocation dynamique.
Test d'écho
Tous les services implémentent automatiquement l'interface EchoService. Un cast suffit pour tester la connectivité :
MemberService member = ctx.getBean(MemberService.class);
EchoService echo = (EchoService) member;
String status = echo.$echo("OK");
Contexte et paramètres implicites
Toutes les configurations sont converties en paramètres d'URL (style HTTP). Le contexte RpcContext est un état ThreadLocal qui évolue avec chaque requête RPC entrante ou sortante.
RpcContext.getContext().setAttachment("cle", "valeur");
Exposition retardée
Par défaut, les services sont exposés dès l'analyse XML par Spring, avant la fin de l'initialisation du conteneur.
1. Évitez les appels à applicationContext.getBean() dans l'implémentation ; privilégiez l'injection.
2. Pour retarder l'exposition après l'initialisation complète : <dubbo:provider delay="-1" />
3. Une isolation via un conteneur Spring dédié pour les services Dubbo peut simplifier la gestion.
Arrêt gracieux
Côté provider : Arrêt de l'acceptation des nouvelles requêtes (erreur immédiate pour réessai client). Attente de la fin des traitements en cours dans le pool de threads (avec timeout).
Côté consumer : Arrêt des nouvelles invocations. Attente des réponses en cours (avec timeout).
L'arrêt gracieux est déclenché par le ShutdownHook JVM (commande kill PID). Une interruption forcée (kill -9 PID) le contourne.
Conteneur d'exécution
Un programme autonome (main) qui charge un conteneur Spring pour exposer les services, sans nécessiter de serveur web (Tomcat, etc.).
Spring Container : Charge les XML depuis META-INF/spring/.
dubbo.spring.config=classpath*:META-INF/spring/*.xml
Jetty Container : Démarre un Jetty intégré pour l'état (pages log, status, system).
dubbo.jetty.port=8080
dubbo.jetty.directory=/foo/bar
dubbo.jetty.page=log,status,system
Log4j Container : Configure automatiquement Log4j avec séparation des logs par processus.
dubbo.log4j.file=/foo/bar.log
dubbo.log4j.level=WARN
dubbo.log4j.subdirectory=20880