Dubbo3.0入门教程

962 阅读16分钟

1. 介绍

官网地址:cn.dubbo.apache.org/zh-cn/overv…

主要用于服务于服务之间的调用,通过Dubbo实现。自带负载均衡机制,在服务消费者调用接口时会自动进行负载均衡。当存在多个提供相同服务的服务提供者时,Dubbo会根据一定的负载均衡策略自动选择其中一个提供者来处理请求。

  • Dubbo提供了一组功能强大的远程调用、负载均衡和容错机制,以及服务注册和发现、动态配置等特性。

  • Dubbo 提供几乎所有主流语言的 SDK 实现,定义了一套统一的微服务开发范式。比如在 Java 语言体系下,你可以使用 dubbo-spring-boot-starter 来开发符合 Spring、Spring Boot 模式的微服务应用

  • Dubbo 微服务间远程通信实现细节,支持 HTTP、HTTP/2、gRPC、TCP 等所有主流通信协议。与普通 RPC 框架不同,Dubbo 不是某个单一 RPC 协议的实现,它通过上层的 RPC 抽象可以将任意 RPC 协议接入 Dubbo 的开发、治理体系。

    • 高性能数据传输:Dubbo2、Triple 两款高性能通信协议

      • Dubbo2 是基于 TCP 传输协议之上构建的二进制私有 RPC 通信协议,是一款非常简单、紧凑、高效的通信协议。
      • Triple 是基于 HTTP/2 的新一代 RPC 通信协议,在网关穿透性、通用性以及 Streaming 通信上具备优势,Triple 完全兼容 gRPC 协议。

protocols

2. 核心功能模块

Dubbo-common

Dubbo-common 对 Dubbo 内部通用工具类进行了封装;

Dubbo-config

Dubbo-config 对 Dubbo 启动的默认配置进行了封装;

Dubbo-metadata-report

Dubbo-metadata-report 提供了 Dubbo 服务元数据的基本结构,在“dubbo注册中心”模块中会用到;

Dubbo-registry-api

Dubbo-registry-api 模块提供了不同的注册中心接口的抽象,如:Zookeeper、Redis;

Dubbo-registry-default

Dubbo-registry-default 模块提供了默认的注册中心实现,可以通过相关配置激活对应的注册中心适配器。

3. Dubbo框架中有以下几个核心角色

  1. 服务提供者(Provider):服务提供者是具体实现和提供某个服务的应用程序,它将自己的服务注册到注册中心,并向消费者提供服务。
  2. 服务消费者(Consumer):服务消费者是需要调用某个服务的应用程序,它从注册中心订阅服务提供者的地址列表,并通过代理方式调用远程的服务。
  3. 注册中心(Registry):注册中心是服务提供者和服务消费者之间的中心化管理和协调机构。它负责服务提供者的注册与发现,维护服务提供者的地址信息,以及将服务消费者的请求路由到合适的服务提供者。
  4. 监控中心(Monitor):监控中心是可选的组件,用于收集和展示Dubbo框架内部的运行时状态和统计信息。它可以帮助开发者监控系统的性能、资源使用情况、请求响应时间等指标。

image.png

image.png

2023-07-21_170311.png

4. SpringBoot集成Dubbo

4.1 样例需求

新建两个SpringBoot项目,一个叫consumer(消费者),一个叫provider(提供者)

4.2 添加依赖

<!-- 消费者和提供者都需要添加 -->
<!-- dubbo的依赖 -->
<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo-rpc-dubbo</artifactId>
    <version>3.0.7</version>
</dependency>

<!-- 注册中心 -->
<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo-registry-zookeeper</artifactId>
    <version>3.0.7</version>
</dependency>

4.3 配置

# Dubbo应用配置
dubbo.application.name=your-application-name    # 应用名称

# Dubbo注册中心配置
dubbo.registry.address=zookeeper://127.0.0.1:2181   # 注册中心地址,这里使用ZooKeeper作为例子

# Dubbo协议配置
dubbo.protocol.name=dubbo        # 服务协议名
dubbo.protocol.port=20880       # 服务端口号

# Dubbo提供者配置
# 扫描指定包下的Dubbo服务接口,多个包名使用逗号分隔
dubbo.scan.base-packages=com.example.service

# Dubbo消费者配置
# 消费者引用服务超时时间,单位毫秒
dubbo.consumer.timeout=3000

# Dubbo监控配置
dubbo.monitor.protocol=registry       # 监控中心使用注册中心来保存统计数据
dubbo.monitor.address=zookeeper://127.0.0.1:2181       # 监控中心地址

4.4 代码编写

4.4.1 定义接口的API

定义一个获取用户的接口,提供者实现这个接口提供服务。消费者调用这个接口实现远程调用

public interface UserService {
    public User getUser(String uid);
}

4.4.2 provider代码实现

实现定义的接口并使用@DubboService注解,实现定义的接口。

@DubboService

public class UserServiceImpl implements UserService {

    public User getUser(String uid) {
        User zhouyu = new User(uid, "zhouyu");
        return zhouyu;
    }
}

4.4.3 consumer调用实现

在定义的接口上使用@DubboReference注解,就可以调用provider项目所提供的接口,原理通过注册中心找到provider服务的IP和端口以及接口信息,通过RPC进行调用。

@Service
public class OrderService {

    @DubboReference
    private UserService userService;

    public String createOrder(){
        User user = userService.getUser("1");
        System.out.println("创建订单");
        return user.toString()+" succeeded in creating the order";
    }

}

4.5 开启Dubbo

在Application上加上@EnableDubbo(scanBasePackages = "com.cl.service"),表示Dubbo会去扫描某个路径下的@DubboService,从而对外提供该Dubbo服务。

@SpringBootApplication
@EnableDubbo(scanBasePackages = "com.cl.service")
public class ProviderApplication {

    public static void main(String[] args) {
        SpringApplication.run(ProviderApplication.class);
    }
}

4.6 总结

  1. 添加pom依赖
  2. 配置dubbo应用名、协议、注册中心
  3. 定义服务接口和实现类
  4. 使用@DubboService来定义一个Dubbo服务
  5. 使用@DubboReference来使用一个Dubbo服务
  6. 使用@EnableDubbo开启Dubbo

5. Dubbo 注册模型的基本原理

  1. 注册中心:Dubbo 的注册模型基于第三方的注册中心,用于集中管理服务的注册与发现。常见的注册中心有 ZooKeeper、Etcd、Consul、nacos 等。
  2. 服务提供者注册:服务提供者在启动时,会将自己提供的服务信息(如接口名称、版本号、地址等)注册到注册中心。注册中心会保存这些信息,并提供查询接口供其他服务消费者使用。
  3. 服务消费者订阅:服务消费者在启动时,会向注册中心订阅自己所需的服务。注册中心会返回符合条件的服务提供者列表给消费者。
  4. 服务调用:消费者获取到服务提供者列表后,可以根据负载均衡策略选择其中一个提供者进行调用。Dubbo 支持多种负载均衡算法,如随机、轮询、一致性哈希等。
  5. 心跳与失效检测:注册中心会定期向服务提供者发送心跳请求,用于检测提供者是否存活。如果某个提供者长时间未发送心跳,则认为该提供者失效,将其从服务列表中移除。
  6. 动态更新:当服务提供者的地址或状态发生变化时,注册中心会及时通知消费者,使消费者能够感知到服务的变化,并及时更新本地的服务列表。

在 Dubbo 2.7.x 和 3.0 版本中,接口的注册方式略有不同。下面是对两个版本的注册模型数据进行说明:

5.1 Dubbo 2.7.x 注册模型数据:

属于接口级注册,把接口名以及对应应用的IP地址和所绑定的端口注册到注册中心

相当于key是接口名,value是ip+port

5.2 Dubbo 3.0 注册模型数据:

属于应用级注册,把应用名以及应用所在服务器的IP地址和应用所绑定的端口注册到注册中心

相当于key是应用名,value是ip+port

所以在Dubbo2.7中,一个应用如果提供了10个Dubbo服务,那么注册中心中就会存储10对keyvalue。

所以Dubbo3.0中将注册模型也改为了应用级注册,提升效率节省资源的同时,通过统一注册模型,也为各个微服务框架的互通打下了基础。

6. Dubbo相关协议规范

RPC:(Remote Procedure Call)既可以指代一种通信协议,也可以指代一种调用的过程规范

  • 作为协议:RPC定义了客户端和服务器之间进行远程调用的通信规则和格式。它规定了请求和响应的格式、数据的编码方式、传输的协议等。常见的RPC协议包括XML-RPC、JSON-RPC和gRPC等。

  • 作为调用的过程规范:RPC定义了客户端如何发起远程调用,服务器如何接收请求并执行相应的过程或函数,然后将结果返回给客户端。它隐藏了底层的网络通信细节,使得远程调用就像本地调用一样简单和透明。

gRPC:(Google Remote Procedure Call)是一个特定的RPC框架,由Google开发并开源。

  • 作为协议:它使用HTTP/2作为底层的传输协议,使用Protocol Buffers作为接口定义语言(IDL)来描述服务接口和消息格式。gRPC提供了诸如双向流、流式传输、元数据传递等丰富的特性,以实现高效、可靠和跨平台的远程调用。

  • 作为调用的过程规范:gRPC定义了客户端如何生成与服务接口对应的代码。通过使用自动生成的代码,客户端可以像调用本地函数一样调用远程服务,而不需要关注底层的网络通信细节。

Triple:是字节跳动开源的一种基于gRPC的服务框架。Triple利用gRPC作为底层的通信协议,并在此基础上提供了更多的分布式系统开发功能。通过RPC机制,可以在客户端和服务器之间传输Triple数据。例如,可以使用RPC调用从服务器获取Triple数据,或者将Triple数据作为参数传递给远程函数进行处理。在这种情况下,RPC主要用于在客户端和服务器之间传输和处理Triple数据。

6.1 RPC 协议通常关键组成部分

  1. 接口定义语言(IDL):定义了远程接口的方法、参数和返回值的数据类型。常见的 IDL 工具有 Thrift、Protocol Buffers(protobuf)、gRPC 等。
  2. 通信协议:确定了客户端和服务器之间的数据传输格式和通信规则。常见的通信协议有 HTTP、TCP、UDP 等。
  3. 序列化与反序列化:将方法调用的参数和返回值转换为字节流进行传输,并在接收端将字节流还原为对应的数据类型。常见的序列化框架有 JSON、XML、Thrift、Protocol Buffers 等。
  4. 传输协议:定义了数据在网络中的传输方式,包括数据的分包、组包和传输顺序等。常见的传输协议有基于文本的协议(如HTTP)、基于二进制的协议(如TCP)等。
  5. 远程代理:在客户端和服务器之间起到桥接作用,隐藏了网络通信的细节,使得客户端可以像调用本地方法一样调用远程方法。

6.2 RPC 协议的工作流程如下:

  1. 客户端调用本地代理对象的方法。
  2. 本地代理对象将方法调用转化为符合协议规范的请求。
  3. 请求通过网络传输到远程服务器。
  4. 服务器接收到请求后,根据请求的方法和参数执行相应的操作。
  5. 服务器将执行结果转化为符合协议规范的响应。
  6. 响应通过网络传输回客户端。
  7. 客户端接收到响应后,将响应结果返回给调用方。

通过 RPC 协议,可以实现分布式系统中的服务调用和数据交互,提高系统的可扩展性和性能。常见的 RPC 框架有 Dubbo、gRPC、Apache Thrift 等。

6.3 RPC与HTTP对比

RPC 和 HTTP 不是一个层级的东西,所以严格意义上这两个没有可比性,也不应该来作比较

HTTP 只是传输协议,协议只是规范了一定的交流格式。而且RPC 是早于 HTTP 的。

RPC 对比的是本地过程调用,是用来作为分布式系统之间的通信,它可以用 HTTP 来传输,也可以基于 TCP 自定义协议传输。所以这两个不是一个层级的东西,没有可比性,然后再表现一下,可以说 HTTP 协议比较冗余,所以 RPC 大多都是基于 TCP 自定义协议,定制化的才是最适合自己的。

当然也有基于 HTTP 协议的 RPC 框架,毕竟 HTTP 是公开的协议,比较通用,像 HTTP2 已经做了相应的压缩了,而且系统之间的调用都在内网,所以说影响也不会很大。

7 Triple协议

Triple 协议是 Dubbo3 推出的主力协议。

流式通信支持。Triple 协议支持 Request StreamResponse StreamBi-direction Stream

  • Request Stream:是指客户端可以向服务器发送流式数据,而不需要一次性将所有数据都发送到服务器。这种功能可以用于实时流式处理场景,例如在线游戏、实时音视频处理等。
  • Response Stream:是指服务器可以向客户端发送流式数据,而不需要一次性将所有数据都发送到客户端。这种功能可以用于数据分发场景,例如分布式存储、日志收集等。
  • Bi-direction Stream:是指客户端和服务器之间可以建立双向流式连接,使得双方都可以向对方发送流式数据。这种功能可以用于实时聊天、在线协作等场景。

7.1 Dubbo 3.0 三种方式

Dubbo 3.0 中支持三种方式调用 Triple 协议,分别是 UNARYSERVER_STREAMCLIENT_STREAM/BI_STREAM。下面是对这三种方式的简要介绍:

  1. UNARY:单向调用方式,客户端只发送一个请求给服务端,服务端处理完请求后返回响应数据包给客户端。

  2. SERVER_STREAM:服务端流调用方式。在 SERVER_STREAM 协议中,服务端可以向客户端发送多个消息,而客户端只能发送单个响应。这种协议适用于服务端有大量数据需要发送给客户端的场景,如实时数据推送、视频流等。

  3. CLIENT_STREAM 协议是一种客户端流协议。在 CLIENT_Stream 协议中,客户端可以向服务端发送多个消息,而服务端只能发送单个响应。这种协议适用于客户端有大量数据需要发送给服务端的场景,如文件上传、日志采集等。

  4. BI_STREAM:客户端/双向流调用方式,客户端向服务端发送多次请求流或双向流,服务端处理完请求后返回响应流或双向流给客户端。这种协议适用于需要双向通信的场景,如在线游戏、实时聊天等。

public interface DemoService {

        // UNARY
        String sayHello(String name);

        // SERVER_STREAM
        default void sayHelloServerStream(String name, StreamObserver<String> response) {
        }

        // CLIENT_STREAM / BI_STREAM
        default StreamObserver<String> sayHelloStream(StreamObserver<String> response) {
            return response;
        }
}

7.2 UNARY 单向调用方式

unary,正常的调用方法

服务实现类对应的方法:

// UNARY 服务端
@Override
public String sayHello(String name) {
    return "Hello " + name;
}

// 服务消费者调用方式
@DubboReference
private DemoService demoService;

String result = demoService.sayHello("zhouyu");

7.3 SERVER_STREAM服务端流调用方式

定义一个方法,参数中StreamObserver response的响应流,通过该流客户端可多次接收服务端的数据

// SERVER_STREAM 
// 服务实现类对应的方法
@Override

public void sayHelloServerStream(String name, StreamObserver<String> response) {
    // 多次响应数据
    response.onNext(name + " hello");
    // 响应数据
    response.onNext(name + " world");
    // 响应完成
    response.onCompleted();
}


// 服务消费者调用方式
demoService.sayHelloServerStream("zhouyu", new StreamObserver<String>() {

    @Override
    public void onNext(String data) {
        // 服务端返回的数据
        System.out.println(data);
    }

    @Override
    public void onError(Throwable throwable) {}


    @Override
    public void onCompleted() {
        System.out.println("complete");
    }

});

7.4 CLIENT_STREAM客户端流调用方式

// 服务实现类对应的方法
// CLIENT_STREAM

@Override

public StreamObserver<String> sayHelloStream(StreamObserver<String> response) {

    return new StreamObserver<String>() {

    @Override
    public void onNext(String data) {
        // 接收客户端发送过来的数据,然后返回数据给客户端
        response.onNext("result:" + data);
    }

    @Override
    public void onError(Throwable throwable) {}

    @Override
    public void onCompleted() {
        System.out.println("completed");
    }

    };
}


// 服务消费者调用方式
StreamObserver<String> streamObserver = demoService.sayHelloStream(new StreamObserver<String>() {

    @Override
    public void onNext(String data) {
        System.out.println("接收到响应数据:"+ data);
    }

    @Override
    public void onError(Throwable throwable) {}

    @Override
    public void onCompleted() {
        System.out.println("接收到响应数据完毕");
    }
});


// 发送数据
streamObserver.onNext("request zhouyu hello");
streamObserver.onNext("request zhouyu world");
streamObserver.onCompleted();

7.5 BI_STREAM双向流调用方式

和CLIENT_STREAM一样

8. Dubbo3.0跨语言调用

在开发一个Dubbo服务时,会把服务接口提成一个单独的API接口,提供者在实现这个接口。而消费者可以通过API的接口去调用提供者定义的接口。

当消费者和提供者使用的不是同一种语言时,那么消费者在定义本地的接口时就无法知道提供者的接口。这时就可以使用protobuf来定义接口。通过不同语言的protobuf编译器将接口编译为特定语言的实现。

8.1 protobuf定义接口

我们可以通过protobuf来定义接口,然后通过protobuf的编译器将接口编译为特定语言的实现。

类似Corba的IDL文件的方式

在provider项目中定义一个userservice.proto文件,路径为src/main/proto/userservice.proto:

syntax = "proto3";

package api;

option go_package = "./;api";

option java_multiple_files = true;
option java_package = "com.zhouyu";
option java_outer_classname = "UserServiceProto";

service UserService {
    rpc GetUser (UserRequest) returns (User) {}
}

// The response message containing the greetings
message UserRequest {
    string uid = 1;
}

// The response message containing the greetings
message User {
    string uid = 1;
    string username = 2;
}

相当于定义了一个HelloService服务,并且定义了一个getUser方法,接收UserRequest类型的参数,返回User类型的对象。

8.2 编译成Java

可以使用Maven插件protobuf-maven-plugin来将Protocol Buffer编译成Java

<build>

    <extensions>
        
        <extension>
            <groupId>kr.motd.maven</groupId>
            <artifactId>os-maven-plugin</artifactId>
            <version>1.6.1</version>
        </extension>

    </extensions>

    <plugins>

        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.7.0</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
            </configuration>
        </plugin>

        <plugin>

        <groupId>org.xolstice.maven.plugins</groupId>
        <artifactId>protobuf-maven-plugin</artifactId>
        <version>0.6.1</version>
            
        <configuration>
     
            <!--指定了使用的Protocol Buffer编译器的groupId、artifactId和版本-->
            <protocArtifact>com.google.protobuf:protoc:3.7.1:exe:${os.detected.classifier}</protocArtifact>
 
            <!-- 指定了生成的Java文件的输出目录 -->
            <outputDirectory>build/generated/source/proto/main/java</outputDirectory>
            
            <!--指定了是否清除输出目录中的旧文件-->
            <clearOutputDirectory>false</clearOutputDirectory>
            
            <!--指定了使用的Protocol Buffer编译器插件,例如`dubbo-compiler`插件。该插件可以将Protocol Buffer编译成Java,并且支持在编译时添加Dubbo服务编译器插件,以生成Dubbo服务的Java代码。-->
            <protocPlugins>
                <protocPlugin>
                    <id>dubbo</id>
                    <groupId>org.apache.dubbo</groupId>
                    <artifactId>dubbo-compiler</artifactId>
                    <version>0.0.3</version>
                    <mainClass>org.apache.dubbo.gen.dubbo.Dubbo3Generator</mainClass>
                </protocPlugin>
            </protocPlugins>
            
        </configuration>
            
        <executions>
            <execution>
                <goals>
                    <goal>compile</goal>
                    <goal>test-compile</goal>
                </goals>
            </execution>
        </executions>
        </plugin>

        <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>build-helper-maven-plugin</artifactId>
        <version>3.0.0</version>
        <executions>
            <execution>
            <phase>generate-sources</phase>
            <goals>
                <goal>add-source</goal>
            </goals>
            <configuration>
                <sources>
                    <source>build/generated/source/proto/main/java</source>
                </sources>
            </configuration>
            </execution>
        </executions>
        </plugin>
        
    </plugins>

</build>

然后运行maven的编译provider中lifecycle的compile进行编译

image.png

并且会编译出对应的接口等信息,编译完成后,会生成一些类:
image.png

其中就包括了一个UserService接口,所以我们的UserServiceImpl就可以实现这个接口了

@DubboService
public class UserServiceImpl implements UserService {
​
    public User getUser(UserRequest userRequest) {
        User user = User.newBuilder().setUid(userRequest.getUid()).setUsername("zhouyu").build();
        return user;
    }
    
}

而对于想要调用UserService服务的消费者而言,其实也是一样的改造,只需要使用同一份userservice.proto进行编译就可以了,比如现在有一个go语言的消费者。

8.2 go消费者调用java服务

首先,在IDEA中新建一个go模块:

image.png

然后把userservice.proto复制到go-consumer/proto下,然后进行编译,编译成为go语言对应的服务代码,只不过go语言中没有maven这种东西可以帮助我们编译,我们只能用原生的protobuf的编译器进行编译。

这就需要大家在机器上下载、安装protobuf的编译器:protoc

  1. 下载地址:github.com/protocolbuf…
  1. 解压之后,把protoc-3.20.1-win64\bin添加到环境变量中去
  1. 在cmd中执行protoc --version,能正常看到版本号即表示安装成功

另外还需要安装go:

  1. 下载地址:studygolang.com/dl/golang/g…
  1. 然后直接下一步安装
  1. 在cmd中(新开一个cmd窗口)执行go version,能正常看到版本号即表示安装成功

然后在go-consumer下新建文件夹api,进入到go-consumer/proto下,运行:

go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn,direct
​
​
go get -u github.com/dubbogo/tools/cmd/protoc-gen-go-triple
go install github.com/golang/protobuf/protoc-gen-go
go install github.com/dubbogo/tools/cmd/protoc-gen-go-triple
​
protoc -I. userservice.proto --go_out=../api --go-triple_out=../api

这样就会在go-consumer/api下生成一个userservice.pb.go文件和userservice_triple.pb.go文件

如果IDEA中提示要安装插件,就安装一下:

image.png

安装完之后,代码可能会报错,可以在go-consumer目录下执行命令下载依赖:

go mod tidy

然后就可以写go语言的服务消费者了,新建一个consumer.go,内容为:

package main
​
import (
    "context"
    "dubbo.apache.org/dubbo-go/v3/common/logger"
    "dubbo.apache.org/dubbo-go/v3/config"
    _ "dubbo.apache.org/dubbo-go/v3/imports"
    "go-consumer/api"
)
​
var userServiceImpl = new(api.UserServiceClientImpl)
​
// export DUBBO_GO_CONFIG_PATH=conf/dubbogo.yml
func main() {
    config.SetConsumerService(userServiceImpl)
    config.Load()
​
    logger.Info("start to test dubbo")
    req := &api.UserRequest{
        Uid: "1",
    }
​
    user, err := userServiceImpl.GetUser(context.Background(), req)
​
    if err != nil {
        logger.Error(err)
    }
​
    logger.Infof("client response result: %v\n", user)
}

然后在go-consumer下新建conf/dubbogo.yml,用来配置注册中心:

dubbo:
  registries:
    demoZK:
      protocol: zookeeper
      address: 127.0.0.1:2181
  consumer:
    references:
      UserServiceClientImpl:
        protocol: tri
        interface: com.zhouyu.UserService

注意这里配置的协议为tri,而不是dubbo,在provider端也得把协议改为tri:

image.png

然后就可以运行consumer.go了,只不过需要在environment中添加一个参数:

image.png

运行成功:

image.png