Dubbo简介

2025/03/31

1. 简介

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

除此之外,它有助于加强服务治理,并使传统的整体应用程序能够顺利重构为可扩展的分布式架构。

在本文中,我们将介绍Dubbo及其最重要的功能。

2. 架构

Dubbo区分了几个角色:

  1. 提供者:服务公开的地方;提供者将其服务注册到注册中心
  2. 容器:服务启动、加载和运行的地方
  3. 消费者:调用远程服务;消费者将订阅注册中心中所需的服务
  4. 注册中心:服务注册和发现的地方
  5. 监控:记录服务的统计信息,例如给定时间间隔内的服务调用频率

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

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

注册中心和监控器都是可选的,消费者可以直接连接服务提供者,但是这样会影响整个系统的稳定性。

3. Maven依赖

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

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

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

4. 引导

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

这是一个微侵入式框架,它的很多功能依赖于外部配置或注解。

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

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

4.1 多播注册中心–服务提供者

作为快速入门,我们只需要一个服务提供者、一个消费者和一个“不可见”的注册中心。由于我们使用的是多播网络,因此注册中心是不可见的。

在以下示例中,提供者仅向其消费者说“hi”:

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

public class GreetingsServiceImpl implements GreetingsService {

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

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

4.2 多播注册中心–服务注册

现在让我们将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="cn.tuyucheng.taketoday.dubbo.remote.GreetingsServiceImpl"/>
<dubbo:service interface="cn.tuyucheng.taketoday.dubbo.remote.GreetingsService"
    ref="greetingsService"/>

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

在提供者的配置中,我们还分别通过<dubbo:application/>,<dubbo:service/>和<Beans/>声明了我们的应用程序元数据,要发布的接口及其实现。

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

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

4.3 多播注册中心–服务消费者

一般来说,消费者需要指定调用的接口,以及远程服务的地址,下面就是消费者需要的内容:

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

现在一切都设置好了,让我们看看它们是如何运作的:

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

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

当提供者的remoteContext启动时,Dubbo将自动加载GreetingsService并将其注册到给定的注册中心。在本例中,它是一个多播注册中心。

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

我们提到注册中心是可选的,这意味着消费者可以通过公开的端口直接连接到提供者:

<dubbo:reference interface="cn.tuyucheng.taketoday.dubbo.remote.GreetingsService"
    id="greetingsService" url="dubbo://127.0.0.1:20880"/>

基本上,该过程与传统的Web服务类似,但Dubbo使其变得简单、朴素和轻量级。

4.4 简单注册中心

请注意,使用“隐形”多播注册中心时,注册中心服务不是独立的。但是,它仅适用于受限的本地网络。

为了明确设置可管理的注册中心,我们可以使用SimpleRegistryService

将以下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" />

请注意,SimpleRegistryService类不包含在工件中,因此我们直接从GitHub仓库复制源代码

然后我们需要调整提供者和消费者的注册中心配置:

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

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

4.5 Java配置

还支持通过Java API、属性文件和注解进行配置。但是,属性文件和注解仅适用于我们的架构不太复杂的情况。

让我们看看如何将之前的多播注册中心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();

现在该服务已经通过多播注册中心公开,让我们在本地客户端中使用它:

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

虽然上面的代码片段和前面的XML配置示例一样好用,但它还是有点简单。目前,如果我们想充分利用Dubbo,XML配置应该是首选。

5. 协议支持

该框架支持多种协议,包括dubbo、RMI、hessian、HTTP、web service、thrift、memcached和redis。除了dubbo,大多数协议看起来都很熟悉。让我们看看这个协议有什么新东西。

dubbo协议在服务提供方和消费者之间保持长连接,长连接以及NIO非阻塞网络通信使得在传输小规模数据包(<100K)时有相当好的性能。

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

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

Dubbo还支持同时通过不同的协议公开服务:

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

<dubbo:service interface="cn.tuyucheng.taketoday.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" />

是的,我们可以使用不同的协议公开不同的服务,如上面的代码片段所示。底层传输器、序列化实现和其他与网络相关的常见属性也是可配置的。

6. 结果缓存

支持本地远程结果缓存,以加快对热门数据的访问。只需向Bean引用添加cache属性即可:

<dubbo:reference interface="cn.tuyucheng.taketoday.dubbo.remote.GreetingsService"
    id="greetingsService" cache="lru" />

这里我们配置了一个最近最少使用的缓存。为了验证缓存行为,我们将在之前的标准实现中做一些改动(我们称之为“特殊实现”):

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

启动提供者后,我们可以在消费者端验证,在多次调用时结果是否被缓存:

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

    long timeElapsed = System.currentTimeMillis() - before;
    assertTrue(timeElapsed > 5000);
    assertNotNull(hiMessage);
    assertEquals("hi, tuyucheng", hiMessage);

    before = System.currentTimeMillis();
    hiMessage = greetingsService.sayHi("tuyucheng");
    timeElapsed = System.currentTimeMillis() - before;

    assertTrue(timeElapsed < 1000);
    assertNotNull(hiMessage);
    assertEquals("hi, tuyucheng", hiMessage);
}

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

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

7. 集群支持

Dubbo具有负载均衡能力和多种容错策略,可帮助我们自由扩展服务。这里假设我们有Zookeeper作为注册中心来管理集群中的服务,提供者可以像这样在Zookeeper中注册他们的服务:

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

请注意,我们需要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>

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

7.1 负载均衡

目前,框架支持几种负载平衡策略:

  • 随机
  • 循环
  • 最不活跃
  • 一致性哈希

在以下示例中,我们在集群中有两个服务实现作为提供者,请求使用循环方法进行路由。

首先,让我们设置服务提供者:

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

现在我们有一个可以立即响应的标准“快速提供者”,还有一个在每次请求时休眠5秒的特殊“慢速提供者”。

采用循环策略运行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("tuyucheng");
        assertNotNull(hiMessage);
        elapseList.add(System.currentTimeMillis() - current);
    }

    OptionalDouble avgElapse = elapseList
            .stream()
            .mapToLong(e -> e)
            .average();
    assertTrue(avgElapse.isPresent());
    assertTrue(avgElapse.getAsDouble() > 2500.0);
}

此外,还采用了动态负载均衡。下一个示例演示了使用循环策略,当新的服务提供者上线时,消费者会自动选择新的服务提供者作为候选。

系统启动后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;
    });
}

消费者每秒调用一次远程服务,运行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("tuyucheng");
        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);
}

请注意,负载均衡器既可以在消费者端配置,也可以在提供者端配置。以下是消费者端配置的示例:

<dubbo:reference interface="cn.tuyucheng.taketoday.dubbo.remote.GreetingsService"
    id="greetingsService" loadbalance="roundrobin" />

7.2 容错

Dubbo支持多种容错策略,包括:

  • 故障转移
  • 故障安全
  • 快速失败
  • 故障恢复
  • 分叉

在故障转移的情况下,当一个提供者发生故障时,消费者可以尝试集群中的其他服务提供者。

服务提供方的容错策略配置如下:

<dubbo:service interface="cn.tuyucheng.taketoday.dubbo.remote.GreetingsService"
    ref="greetingsService" cluster="failover"/>

为了演示服务故障转移的实际操作,让我们创建GreetingsService的故障转移实现:

public class GreetingsFailoverServiceImpl implements GreetingsService {

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

我们可以回想一下,我们的特殊服务实现GreetingsServiceSpecialImpl对于每个请求都会休眠5秒。

当任何响应时间超过2秒时,对于消费者来说都会被视为请求失败,我们会有一个故障转移场景:

<dubbo:reference interface="cn.tuyucheng.taketoday.dubbo.remote.GreetingsService"
    id="greetingsService" retries="2" timeout="2000" />

启动两个提供者后,我们可以使用以下代码片段验证故障转移行为:

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

    assertNotNull(hiMessage);
    assertEquals("hi, failover tuyucheng", hiMessage);
}

8. 总结

在本教程中,我们简单了解了Dubbo,大多数用户被它的简洁和丰富强大的功能所吸引。

除了本文介绍的内容之外,该框架还有许多功能尚待探索,例如参数验证、通知和回调、通用实现和引用、远程结果分组和合并、服务升级和向后兼容等等。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章