Introduction to Dubbo – 杜波简介

最后修改: 2017年 12月 14日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.介绍

Dubbo is an open-source RPC and microservice framework from Alibaba.

Dubbo是阿里巴巴的一个开源的RPC和微服务框架。

Among other things, it helps enhance service governance and makes it possible for a traditional monolith applications to be refactored smoothly to a scalable distributed architecture.

在其他方面,它有助于加强服务治理,并使传统的单片机应用有可能顺利地重构为可扩展的分布式架构。

In this article, we’ll give an introduction to Dubbo and its most important features.

在这篇文章中,我们将介绍Dubbo和它最重要的特点。

2. Architecture

2.建筑

Dubbo distinguishes a few roles:

杜波区分了几个角色。

  1. Provider – where service is exposed; a provider will register its service to registry
  2. Container – where the service is initiated, loaded and run
  3. Consumer – who invokes remote services; a consumer will subscribe to the service needed in the registry
  4. Registry – where service will be registered and discovered
  5. Monitor – record statistics for services, for example, frequency of service invocation in a given time interval

Dubbo Architecture

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

(来源:http://dubbo.io/images/dubbo-architecture.png)

Connections between a provider, a consumer and a registry are persistent, so whenever a service provider is down, the registry can detect the failure and notify the consumers.

提供者、消费者和注册中心之间的连接是持久的,因此,每当服务提供者发生故障时,注册中心可以检测到故障并通知消费者。

The registry and monitor are optional. Consumers could connect directly to service providers, but the stability of the whole system would be affected.

登记处和监视器是可选的。消费者可以直接连接到服务提供商,但整个系统的稳定性会受到影响。

3. Maven Dependency

3.Maven的依赖性

Before we dive in, let’s add the following dependency to our pom.xml:

在我们深入研究之前,让我们在pom.xml中添加以下依赖关系。

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>dubbo</artifactId>
    <version>2.5.7</version>
</dependency>

The latest version can be found here.

最新版本可以在这里找到。

4. Bootstrapping

4.引导

Now let’s try out the basic features of Dubbo.

现在我们来试试Dubbo的基本功能。

This is a minimally invasive framework, and lots of its features depend on external configurations or annotations.

这是一个微创的框架,它的很多功能都依赖于外部配置或注释。

It’s officially suggested that we should use XML configuration file because it depends on a Spring container (currently Spring 4.3.10).

官方建议我们使用XML配置文件,因为它依赖于一个Spring容器(目前是Spring 4.3.10)。

We’ll demonstrate most of its features using XML configuration.

我们将使用XML配置来演示它的大部分功能。

4.1. Multicast Registry – Service Provider

4.1.多播登记处 – 服务提供者

As a quick start, we’ll only need a service provider, a consumer and an “invisible” registry. The registry is invisible because we are using a multicast network.

作为一个快速的开始,我们只需要一个服务提供者、一个消费者和一个 “看不见的 “注册表。注册表是不可见的,因为我们使用的是一个多播网络。

In the following example, the provider only says “hi” to its consumers:

在下面的例子中,提供者只对其消费者说 “嗨”。

public interface GreetingsService {
    String sayHi(String name);
}

public class GreetingsServiceImpl implements GreetingsService {

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

To make a remote procedure call, the consumer must share a common interface with the service provider, thus the interface GreetingsService must be shared with the consumer.

为了进行远程过程调用,消费者必须与服务提供者共享一个公共接口,因此必须与消费者共享接口GreetingsService

4.2. Multicast Registry – Service Registration

4.2.多播注册–服务注册

Let’s now register GreetingsService to the registry. A very convenient way is to use a multicast registry if both providers and consumers are on the same local network:

现在让我们把GreetingsService注册到注册表。如果提供者和消费者都在同一个本地网络上,一个非常方便的方法是使用多播注册表。

<dubbo:application name="demo-provider" version="1.0"/>
<dubbo:registry address="multicast://224.1.1.1:9090"/>
<dubbo:protocol name="dubbo" port="20880"/>
<bean id="greetingsService" class="com.baeldung.dubbo.remote.GreetingsServiceImpl"/>
<dubbo:service interface="com.baeldung.dubbo.remote.GreetingsService"
  ref="greetingsService"/>

With the beans configuration above, we have just exposed our GreetingsService to an url under dubbo://127.0.0.1:20880 and registered the service to a multicast address specified in <dubbo:registry />.

通过上面的bean配置,我们刚刚将我们的GreetingsService暴露在dubbo:/127.0.0.1:20880下的一个url,并将服务注册到<dubbo:registry />中指定的多播地址。

In the provider’s configuration, we also declared our application metadata, the interface to publish and its implementation respectively by <dubbo:application />, <dubbo:service /> and <beans />.

在提供者的配置中,我们也声明了我们的应用元数据,要发布的接口和它的实现分别由<dubbo:application /><dubbo:service /><beans />

The dubbo protocol is one of many protocols the framework supports. It is built on top of the Java NIO non-blocking feature and it’s the default protocol used.

dubbo协议是该框架支持的众多协议之一。它是建立在Java NIO非阻塞功能之上的,而且是使用的默认协议。

We’ll discuss it in more detail later in this article.

我们将在本文后面更详细地讨论。

4.3. Multicast Registry – Service Consumer

4.3.多播登记处 – 服务消费者

Generally, the consumer needs to specify the interface to invoke and the address of remote service, and that’s exactly what’s needed for a consumer:

一般来说,消费者需要指定调用的接口和远程服务的地址,而这正是消费者需要的。

<dubbo:application name="demo-consumer" version="1.0"/>
<dubbo:registry address="multicast://224.1.1.1:9090"/>
<dubbo:reference interface="com.baeldung.dubbo.remote.GreetingsService"
  id="greetingsService"/>

Now everything’s set up, let’s see how they work in 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);
    }
}

When the provider’s remoteContext starts, Dubbo will automatically load GreetingsService and register it to a given registry. In this case, it’s a multicast registry.

当提供者的remoteContext启动时,Dubbo将自动加载GreetingsService并将其注册到一个给定的注册表。在这种情况下,它是一个多播注册表。

The consumer subscribes to the multicast registry and creates a proxy of GreetingsService in the context. When our local client invokes the sayHi method, it’s transparently invoking a remote service.

消费者订阅了多播注册表,并在上下文中创建了GreetingsService的代理。当我们的本地客户端调用sayHi方法时,它在透明地调用一个远程服务。

We mentioned that the registry is optional, meaning that the consumer could connect directly to the provider, via the exposed port:

我们提到,注册表是可选的,这意味着消费者可以通过暴露的端口,直接连接到提供者。

<dubbo:reference interface="com.baeldung.dubbo.remote.GreetingsService"
  id="greetingsService" url="dubbo://127.0.0.1:20880"/>

Basically, the procedure is similar to traditional web service, but Dubbo just makes it plain, simple and lightweight.

基本上,程序类似于传统的网络服务,但Dubbo只是让它变得朴素、简单和轻量。

4.4. Simple Registry

4.4.简单的注册表

Note that when using an “invisible” multicast registry, the registry service is not standalone. However, it’s only applicable to a restricted local network.

请注意,当使用 “不可见的 “多播注册表时,注册表服务不是独立的。然而,它只适用于受限制的本地网络。

To explicitly set up a manageable registry, we can use a SimpleRegistryService.

为了明确地设置一个可管理的注册表,我们可以使用一个SimpleRegistryService

After loading the following beans configuration into Spring context, a simple registry service is started:

在将以下Bean配置加载到Spring上下文中后,一个简单的注册服务被启动。

<dubbo:application name="simple-registry" />
<dubbo:protocol port="9090" />
<dubbo:service interface="com.alibaba.dubbo.registry.RegistryService"
  ref="registryService" registry="N/A" ondisconnect="disconnect">
    <dubbo:method name="subscribe">
        <dubbo:argument index="1" callback="true" />
    </dubbo:method>
    <dubbo:method name="unsubscribe">
        <dubbo:argument index="1" callback="true" />
    </dubbo:method>
</dubbo:service>

<bean class="com.alibaba.dubbo.registry.simple.SimpleRegistryService"
  id="registryService" />

Note that the SimpleRegistryService class is not contained in the artifact, so we copied the source code directly from the Github repository.

请注意,SimpleRegistryService类并不包含在工件中,所以我们直接从Github资源库中复制了源代码

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

然后我们将调整提供者和消费者的注册表配置。

<dubbo:registry address="127.0.0.1:9090"/>

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

SimpleRegistryService在测试时可以作为一个独立的注册表使用,但不建议在生产环境中使用。

4.5. Java Configuration

4.5.Java配置

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.

也支持通过Java API、属性文件和注解进行配置。然而,属性文件和注释只适用于我们的架构不是很复杂的情况。

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:

让我们看看我们之前对组播注册的XML配置如何转化为API配置。首先,提供者被设置为如下。

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<GreetingsService> 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<GreetingsService> 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.

虽然上面的片段和前面的XML配置例子一样,工作得很好,但它更琐碎一些。就目前而言,如果我们打算充分利用Dubbo,XML配置应该是首选。

5. Protocol Support

5 协议支持[/strong]

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.

该框架支持多种协议,包括dubboRMIhessianHTTPweb servicethriftmemcachedredis。除了dubbo,大多数协议看起来都很熟悉。让我们来看看这个协议的新内容。

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).

dubbo协议在提供者和消费者之间保持一个持久的连接。长连接和NIO非阻塞网络通信导致在传输小规模数据包(<100K)时具有相当大的性能。

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

有几个可配置的属性,如端口、每个消费者的连接数、最大接受连接数等。

<dubbo:protocol name="dubbo" port="20880"
  connections="2" accepts="1000" />

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

Dubbo还支持通过不同的协议同时暴露服务。

<dubbo:protocol name="dubbo" port="20880" />
<dubbo:protocol name="rmi" port="1099" />

<dubbo:service interface="com.baeldung.dubbo.remote.GreetingsService"
  version="1.0.0" ref="greetingsService" protocol="dubbo" />
<dubbo:service interface="com.bealdung.dubbo.remote.AnotherService"
  version="1.0.0" ref="anotherService" protocol="rmi" />

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

6.结果缓存

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:

支持天然的远程结果缓存,以加快对热数据的访问。这就像在bean引用中添加一个缓存属性一样简单。

<dubbo:reference interface="com.baeldung.dubbo.remote.GreetingsService"
  id="greetingsService" cache="lru" />

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.

在这里,消费者正在调用特殊的服务实现,所以第一次调用需要5秒以上的时间才能完成。当我们再次调用时,sayHi方法几乎立即完成,因为结果已经从缓冲区返回。

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

请注意,线程本地缓存和JCache也被支持。

7. Cluster Support

7.集群支持

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:

Dubbo通过它的负载平衡能力和一些容错策略帮助我们自由地扩展我们的服务。在这里,我们假设我们有Zookeeper作为我们的注册表来管理集群中的服务。供应商可以像这样在Zookeeper中注册他们的服务。

<dubbo:registry address="zookeeper://127.0.0.1:2181"/>

Note that we need these additional dependencies in the POM:

注意,我们在POM中需要这些额外的依赖。

<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.4.11</version>
</dependency>
<dependency>
    <groupId>com.101tec</groupId>
    <artifactId>zkclient</artifactId>
    <version>0.10</version>
</dependency>

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

最新版本的zookeeper依赖性和zkclient可以在这里这里找到。

7.1. Load Balancing

7.1.负载平衡

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.

现在,我们有一个标准的 “快速提供者”,它立即响应,还有一个特殊的 “慢速提供者”,它在每次请求时都会睡上5秒钟。

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

在使用轮流策略运行6次后,我们预计平均响应时间至少为2.5秒。

@Test
public void givenProviderCluster_whenConsumerSaysHi_thenResponseBalanced() {
    ClassPathXmlApplicationContext localContext
      = new ClassPathXmlApplicationContext("cluster/consumer-app-lb.xml");
    localContext.start();
    GreetingsService greetingsService
      = (GreetingsService) localContext.getBean("greetingsService");

    List<Long> elapseList = new ArrayList<>(6);
    for (int i = 0; i < 6; i++) {
        long current = System.currentTimeMillis();
        String hiMessage = greetingsService.sayHi("baeldung");
        assertNotNull(hiMessage);
        elapseList.add(System.currentTimeMillis() - current);
    }

    OptionalDouble avgElapse = elapseList
      .stream()
      .mapToLong(e -> 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:

系统启动2秒后,”慢速提供者 “被登记。

@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:

消费者每秒钟调用一次远程服务。运行6次后,我们预计平均响应时间将大于1.6秒。

@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<Long> elapseList = new ArrayList<>(6);
    for (int i = 0; i < 6; i++) {
        long current = System.currentTimeMillis();
        String hiMessage = greetingsService.sayHi("baeldung");
        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);
}

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:

请注意,负载平衡器可以在消费者一方和提供者一方进行配置。下面是一个消费者端配置的例子。

<dubbo:reference interface="com.baeldung.dubbo.remote.GreetingsService"
  id="greetingsService" loadbalance="roundrobin" />

7.2. Fault Tolerance

7.2.容错性

Several fault tolerance strategies are supported in Dubbo, including:

在Dubbo支持几种容错策略,包括。

  • 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:

容错策略对服务提供者的配置如下。

<dubbo:service interface="com.baeldung.dubbo.remote.GreetingsService"
  ref="greetingsService" cluster="failover"/>

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

为了演示服务故障转移的实际情况,让我们创建一个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.

我们可以回顾一下,我们的特殊服务实现GreetingsServiceSpecialImpl为每个请求睡眠5秒。

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

当任何需要超过2秒的响应被视为消费者的请求失败时,我们就有了一个故障转移的场景。

<dubbo:reference interface="com.baeldung.dubbo.remote.GreetingsService"
  id="greetingsService" retries="2" timeout="2000" />

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

8.总结

In this tutorial, we took a small bite of Dubbo. Most users are attracted by its simplicity and rich and powerful features.

在本教程中,我们对Dubbo进行了一次小规模的研究。大多数用户被它的简单性和丰富而强大的功能所吸引。

Aside from what we introduced in this article, the framework has a number of features yet to be explored, such as parameter validation, notification and callback, generalized implementation and reference, remote result grouping and merging, service upgrade and backward compatibility, to name just a few.

除了我们在这篇文章中介绍的内容,该框架还有许多功能有待探索,如参数验证、通知和回调、通用实现和引用、远程结果分组和合并、服务升级和向后兼容等,仅举几例。

As always, the full implementation can be found over on Github.

一如既往,完整的实现可以在Github上找到