Dubbo 的特性及开发示例

1 Dubbo 简介

Doubbo 是国内最早开源的 RPC 框架,在 2008 年成为 Alibaba 公司的 SOA 解决方案,在 2011 年对外开源,开源时仅支持 Java 语言,2014 年 10 月停止维护。在停止维护期间,部分互联网公司接替阿里开源了自行维护的 Dubbo 版本,例如当当维护的 Dubbox,在这段时间内,使用 Dubbox 的公司日益增加,因此,2017 年 9 月,阿里重启了 Dubbo 项目,随后 Dubbo 的迭代就变得非常频繁。2018 年 2 月,Alibaba 将 Dubbo 捐献给了 Apache 基金会,并于 2019 年 5 月 20 日结束孵化,成为了 Apache 的顶级项目。

Dubbo 生态支持许多微服务架构领域中的技术,并且它设计了一套自己的 SPI (Service Provider Interface) 扩展机制来保证 Dubbo 的可可扩展性,社区也在持续地支持各种技术,比如光注册中心 Dubbo 就支持 Zookeeper、Nacos、Consul、etcd 等。

Dubbo 除了提供 RPC 本身的能力,还支持许多服务治理方面的特性,比如路由规则、优雅下线等。

1.1 Dubbo 分支

以下是几个 Dubbo 的重要分支:

  • 2.5.x:已停止维护;
  • 2.6.x:Alibaba 捐献给 Apache 之前的版本,现在仅仅处理一些 bug,不再集成新特性;
  • master:目前最活跃的分支,包名是 org.apache,即 Alibaba 捐献给 Apache 的版本;
  • 3.x:目前最新的默认分支,致力于云原生的支持,例如应用注册和发现、协议改造等。

1.2 Dubbo 特性

Dubbo 在十多年的发展历程中,新增了许多特性,这些特性主要分为以下三个方面。

1.2.1 扩展性

Dubbo 作为一个 RPC 框架,最基础的就是提供 RPC 能力,目前 RPC 各个组成部分都有许多优秀的开源框架可供选择,例如网络通信框架有 Netty、Grizzly,序列化框架有 Hessian、Kryo 等,Dubbo 并不是仅仅选择其中一种方案,而是实现了许多方案的组合以供开发者选择,这归功于 Dubbo 设计的 SPI 协议带来的强大的扩展性。

我们都知道 JDK 也有自己的 SPI 设计,在面向对象的设计中,模块之间推荐基于接口编程,而不是对实现类进行硬编码,这样做也是基于模块设计的可插拔原则。为了避免在模块装配的时候在程序里指定实现类,就需要引入一种服务发现机制:JDK 提供了一个工具类 java.util.ServiceLoader,它会加载 META-INF/service/ 目录下所有相关的配置文件,以获取接口支持的实现类。

Dubbo 在 JDK 的 SPI 基础上做了以下改进:

  1. 实现了扩展点的按需加载。我们都知道 JDK 标准的 SPI 只能通过遍历来查找扩展点和实例化,所以有可能导致一次性加载了所有的扩展点,导致资源的浪费。
  2. META-INF/xxx/ 配置文件中扩展实现的格式修改成了 Key-Value 型,例如:xxx = com.demo.XxxProtocol。如此一来可以更精确的定位到问题,抛出的异常也更精确。
  3. 增加了对 Spring IoC、AOP 的支持。一个扩展点可以直接通过 setter 注入其他扩展点。

依托于 SPI 设计,Dubbo 在执行 RPC 调用时可以通过配置选择使用哪种底层方案,极大的提高了扩展性。

1.2.2 服务治理能力

Dubbo 虽然是一个 RPC 框架,但同样是一个服务治理框架,除了提供 RPC 能力,他还提供了形形色色的服务治理能力。

当服务达到了一定量级,服务治理就显得至关重要了,此时人工运维这些服务的难度极大,错误率会直线上升。Dubbo 提供了许多能力来支持运维以及服务管理,比如提供了 telnet 命令、Dubbo Admin 等。

除了服务的运维方面,Dubbo 还提供了多种路由规则、多种负载均衡策略、多种集群容错规则等。比如路由规则有条件路由规则、集群容错规则、标签路由规则、脚本路由规则等,负载均衡策略有基于权重随机算法的策略、基于最少活跃调用数算法的策略、基于 Hash 一致性算法的策略、基于加权轮训算法的策略。

除了这些,Dubbo 还提供了链路追踪、链路监控等适配能力,实现更完善的服务治理。

1.2.3 配置粒度精细

Dubbo 的许多特性都是可配置的,比如超时配置,重试次数等。Dubbo 在提供这些特性的同时,还支持了不同粒度的配置。例如当配置了一个超时配置时,可以控制该超时配置的受用对象是谁,是某个接口的下的一个方法,还是整个接口/应用。

除了对配置粒度的划分,Dubbo 在服务发现和服务注册上也支持接口粒度和应用粒度。接口粒度是最先支持的方案,后来为了对接云原生思想,Dubbo 支持了应用粒度的服务发现和服务注册。

2 Dubbo 开发示例

Dubbo 支持三种开发方式,分别是 XML 开发方式、注解开发方式和 API 开发方式。

这里我们以 XML 开发方式为例,XML 配置可以做到透明化介入应用,对应用没有任何 API 入侵,只需用 Spring 加载 Dubbo 配置即可。

2.1 POM 依赖

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<properties>
    <dubbo.version>3.0.10</dubbo.version>
    <spring.version>5.3.22</spring.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.apache.dubbo</groupId>
        <artifactId>dubbo</artifactId>
        <version>${dubbo.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>${spring.version}</version>
    </dependency>
</dependencies>

2.2 服务提供者

  1. 创建用于暴露的接口 DemoService.java

    1
    2
    3
    4
    5
    
    package io.maling.api;
    
    public interface DemoService {
        String sayHello(String name);
    }
  2. 创建接口的实现类

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    
    package io.maling.api.impl;
    
    import io.maling.api.DemoService;
    import org.apache.dubbo.rpc.RpcContext;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    public class DemoServiceImpl implements DemoService {
        @Override
        public String sayHello(String name) {
            System.out.println("[" 
                    + new SimpleDateFormat("HH:mm:ss").format(new Date())
                    + "] Hello " + name + ", response from provider: " 
                    + RpcContext.getServiceContext());
    
            return "Hello " + name + ", response from provider: " 
                + RpcContext.getServiceContext().getLocalAddressString();
        }
    }
  3. 通过 XML 暴露 Dubbo 服务

    创建 dubbo-provider.xml:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    <?xml version="1.0" encoding="UTF-8" ?>
    <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
        xmlns="http://www.springframework.org/schema/beans"
        xmlns:context="http://www.springframework.org/schema/context"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://dubbo.apache.org/schema/dubbo
            http://dubbo.apache.org/schema/dubbo/dubbo.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd">
        <context:property-placeholder/>
    
        <dubbo:application name="provider"/>
        <dubbo:registry address="multicast://224.1.1.1:1234"/>
        <dubbo:protocol name="dubbo" port="20880"/>
        <bean id="demoService" class="io.maling.api.impl.DemoServiceImpl"/>
        <dubbo:service interface="io.maling.api.DemoService" ref="demoService" group="test"/>
    </beans>
    • 这里直接使用 multicast 暴露 Dubbo 服务的服务地址,原因是 Dubbo 目前与注册中心强相关,必须配置 Registry。
  4. 创建启动类 DemoProvider.java

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    
    package io.maling;
    
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    import java.util.concurrent.CountDownLatch;
    
    public class DemoProvider {
        public static void main(String[] args) throws InterruptedException {
            ClassPathXmlApplicationContext context =
                    new ClassPathXmlApplicationContext("dubbo-provider.xml");
            context.start();
            System.out.println("Dubbo Provider Started!");
            new CountDownLatch(1).await();
        }
    }
  5. 启动 Provider

    启动后打印如下:

    1
    2
    
    信息:  [DUBBO] Dubbo Application[1.1](provider) is ready., dubbo version: 3.0.10, current host: 192.168.1.13
    Dubbo Provider Started!

    可知,我们的 Provider 暴露的地址为 192.168.1.13,这里记下来供 Consumer 使用。

2.3 服务消费者

  1. 编写用做服务引用的 DemoService 接口

    1
    2
    3
    4
    5
    
    package io.maling.api;
    
    public interface DemoService {
        String sayHello(String name);
    }
    • 需要注意,这里只是创建了一个规范与 Provider 相同的接口,但并未创建其实现类,真正的实现在 Provider 端。
  2. 编写 dubbo-consumer.xml

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    
    <?xml version="1.0" encoding="UTF-8" ?>
    <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
        xmlns="http://www.springframework.org/schema/beans"
        xmlns:context="http://www.springframework.org/schema/context"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://dubbo.apache.org/schema/dubbo
            http://dubbo.apache.org/schema/dubbo/dubbo.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd">
        <context:property-placeholder/>
    
        <dubbo:application name="consumer"/>
        <dubbo:reference interface="io.maling.api.DemoService"
            id="demoService" group="test" url="192.168.1.13:20880"/>
    </beans>
    • 该示例采用直联式,不通过注册中心发现,所以 url 直接指向 Provider 的 IP:192.168.1.13。
  3. 编写消费服务的逻辑,也就是发起 RPC 调用的过程 DemoConsumer.java

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    
    package io.maling;
    
    import io.maling.api.DemoService;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class DemoConsumer {
        public static void main(String[] args) {
            ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("dubbo-consumer.xml");
            context.start();
            DemoService demoService = (DemoService) context.getBean("demoService");
            String hello = demoService.sayHello("maling");
            System.out.println(hello);
        }
    }

2.4 运行测试

运行 io.maling.DemoConsumer#main 后,Provider 端与 Consumer 端分别有如下打印:

1
2
3
4
5
# Provider:
[23:03:36] Hello maling, response from provider: org.apache.dubbo.rpc.RpcServiceContext@69b9101b

# Consumer:
Hello maling, response from provider: 192.168.1.13:20880

这就意味着我们成功的发起了一次 RPC 调用。


欢迎关注我的公众号,第一时间获取文章更新:

微信公众号

相关内容