1. Introducción
Dubbo es un marco de microservicio y RPC de código abierto de Alibaba.
Entre otras cosas, ayuda a mejorar la gobernanza del servicio y hace posible que las aplicaciones monolíticas tradicionales se refactoricen sin problemas a una arquitectura distribuida escalable.
En este artículo, daremos una introducción a Dubbo y sus características más importantes.
2. Arquitectura
Dubbo distingue algunos roles:
- Proveedor: donde está expuesto el servicio; un proveedor registrará su servicio en el registro
- Contenedor: donde se inicia, carga y ejecuta el servicio
- Consumidor: que invoca servicios remotos; un consumidor se suscribirá al servicio necesario en el registro
- Registro: donde se registrará y descubrirá el servicio
- Supervisar: registrar estadísticas de servicios, por ejemplo, frecuencia de invocación de servicios en un intervalo de tiempo determinado

(fuente: //dubbo.io/images/dubbo-architecture.png)
Las conexiones entre un proveedor, un consumidor y un registro son persistentes, por lo que siempre que un proveedor de servicios no funciona, el registro puede detectar la falla y notificar a los consumidores.
El registro y el monitor son opcionales. Los consumidores podrían conectarse directamente con los proveedores de servicios, pero la estabilidad de todo el sistema se vería afectada.
3. Dependencia de Maven
Antes de sumergirnos, agreguemos la siguiente dependencia a nuestro pom.xml :
com.alibaba dubbo 2.5.7
La última versión se puede encontrar aquí.
4. Bootstrapping
Ahora probemos las funciones básicas de Dubbo.
Este es un marco mínimamente invasivo y muchas de sus características dependen de configuraciones externas o anotaciones.
Se sugiere oficialmente que deberíamos usar el archivo de configuración XML porque depende de un contenedor Spring (actualmente Spring 4.3.10).
Demostraremos la mayoría de sus características usando la configuración XML.
4.1. Registro de multidifusión: proveedor de servicios
Para empezar, solo necesitaremos un proveedor de servicios, un consumidor y un registro "invisible". El registro es invisible porque estamos usando una red de multidifusión.
En el siguiente ejemplo, el proveedor solo dice "hola" a sus consumidores:
public interface GreetingsService { String sayHi(String name); } public class GreetingsServiceImpl implements GreetingsService { @Override public String sayHi(String name) { return "hi, " + name; } }
Para realizar una llamada a procedimiento remoto, el consumidor debe compartir una interfaz común con el proveedor de servicios, por lo que la interfaz GreetingsService debe compartirse con el consumidor.
4.2. Registro de multidifusión: registro de servicio
Ahora registremos GreetingsService en el registro. Una forma muy conveniente es utilizar un registro de multidifusión si tanto los proveedores como los consumidores están en la misma red local:
Con la configuración de beans anterior, acabamos de exponer nuestro GreetingsService a una URL en dubbo: //127.0.0.1: 20880 y registramos el servicio en una dirección de multidifusión especificada en.
En la configuración del proveedor, también declaramos los metadatos de nuestra aplicación, la interfaz a publicar y su implementación respectivamente por , y .
El protocolo dubbo es uno de los muchos protocolos que admite el marco. Está construido sobre la función de no bloqueo de Java NIO y es el protocolo predeterminado utilizado.
Lo discutiremos con más detalle más adelante en este artículo.
4.3. Registro de multidifusión: consumidor de servicios
Generalmente, el consumidor necesita especificar la interfaz a invocar y la dirección del servicio remoto, y eso es exactamente lo que necesita un consumidor:
Ahora que todo está configurado, veamos cómo funcionan en acción:
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); } }
Cuando se inicia el remoteContext del proveedor , Dubbo cargará automáticamente GreetingsService y lo registrará en un registro determinado. En este caso, es un registro de multidifusión.
El consumidor se suscribe al registro de multidifusión y crea un proxy de GreetingsService en el contexto. Cuando nuestro cliente local invoca el método sayHi , está invocando de forma transparente un servicio remoto.
Mencionamos que el registro es opcional, lo que significa que el consumidor podría conectarse directamente al proveedor, a través del puerto expuesto:
Básicamente, el procedimiento es similar al servicio web tradicional, pero Dubbo lo hace simple, simple y ligero.
4.4. Registro simple
Tenga en cuenta que cuando se utiliza un registro de multidifusión "invisible", el servicio de registro no es independiente. Sin embargo, solo es aplicable a una red local restringida.
Para configurar explícitamente un registro manejable, podemos usar SimpleRegistryService .
Después de cargar la siguiente configuración de beans en el contexto de Spring, se inicia un servicio de registro simple:
Tenga en cuenta que la clase SimpleRegistryService no está contenida en el artefacto, por lo que copiamos el código fuente directamente desde el repositorio de 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
En este tutorial, dimos un pequeño bocado a Dubbo. La mayoría de los usuarios se sienten atraídos por su simplicidad y características ricas y poderosas.
Además de lo que presentamos en este artículo, el marco tiene una serie de características aún por explorar, como validación de parámetros, notificación y devolución de llamada, implementación y referencia generalizadas, agrupación y fusión de resultados remotos, actualización de servicios y compatibilidad con versiones anteriores, por nombrar solo unos pocos.
Como siempre, la implementación completa se puede encontrar en Github.