SpringBoot 集成和使用 Dubbo

发布时间 2023-07-11 22:36:56作者: 乔京飞

Dubbo 是阿里开源的产品,采用二进制通信,相比 OpenFeign 的 http 通信,具有性能优势,可以轻松集成到 SpringBoot 和 Spring Cloud 中使用,对于性能要求比较高的场景,使用比较广泛。早期的 Dubbo 都采用 Zookeeper 作为注册中心,现在基本上大家都使用 Nacos 作为注册中心,毕竟 Dubbo 和 Nacos 都是阿里的产品,Nacos 也确实非常强大。

本篇博客主要通过代码 Demo 介绍如何使用 SpringBoot 集成 Dubbo,在博客最后会提供源代码下载。


一、准备工作

在前面的博客中,已经介绍了如何搭建 Nacos,这里不再赘述,我在自己的虚拟机中搭建了单机版 Nacos,虚拟机的 ip 地址是 192.168.216.128,搭建好之后默认不需要登录就可以访问,端口是 8848,这里全都保持不变。

阿里官方为 Dubbo 提供了一个管理后台 Dubbo Admin,网址:https://github.com/apache/dubbo-admin

下载最新的 DubboAdmin ,使用 IDEA 打开进行打包,在 dubbo-admin-server 下的 target 目录下能够获取 jar 包。

我把 jar 包名字修改为 dubbo-admin.jar ,上传到虚拟机的 /app/dubbo-admin 目录下。

将 dubbo-admin-server 项目下的 application.properties 复制一份,也上传到虚拟机的 /app/dubbo-admin 目录下。

修改 application.properties 文件,主要修改启动端口,登录的账号密码,以及其注册的 Nacos 地址,内容如下:

# 我这里使用 8181 作为 dubbo admin 网站的启动端口
server.port=8181
admin.registry.address=nacos://192.168.216.128:8848
admin.config-center=nacos://192.168.216.128:8848
admin.metadata-report.address=nacos://192.168.216.128:8848

admin.root.user.name=root
admin.root.user.password=root

然后进入 /etc/systemd/system 目录下,新建一个文件 dubboAdminServer.service ,填写内容如下:

[Unit]
Description=DubboAdminServer
After=syslog.target network.target

[Service]
Type=simple

#注意:ExecStart 后面的命令脚本都是写在一行中,没有手动进行换行,只是横向空间不够,自动换行了
ExecStart=/app/jdk1.8/bin/java -jar /app/dubbo-admin/dubbo-admin.jar --spring.config.location=/app/dubbo-admin/application.properties
ExecStop=/bin/kill -15 $MAINPID

User=root
Group=root

[Install]
WantedBy=multi-user.target

然后执行以下命令,将 dubbo admin 安装为 linux 服务,后续维护就比较方便了

# 只要修改了 dubboAdminServer.service 文件就必须指定下面这个命令
systemctl daemon-reload

# 启动服务
systemctl start dubboAdminServer

# 将服务设置为开机启动
systemctl enable dubboAdminServer

我们配置的 dubbo admin 网站的端口是 8181,账号密码都是 root ,访问 http://192.168.216.128:8181 登录后界面如下:

image

然后访问 Nacos 的网址:http://192.168.216.128:8848/nacos 就能够看到 dubbo admin 注册的服务信息:

image


二、搭建工程

搭建一个 SpringBoot 父工程,里面包含 3 个子工程,如下图所示:

image

dubbo_common 是抽取出来的公共项目,里面是公用的实体类和接口方法

dubbo_provider 是 dubbo 服务接口的提供者,实现 dubbo_common 中定义的接口

dubbo_consumer 是 dubbo 服务的消费者,用来调用 dubbo 服务提供的 tcp 接口

父工程的 pom 文件内容如下所示,对于 3 个子工程都需要引入的 jar 包依赖,可以写在父工程的 pom 文件中:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.jobs</groupId>
    <artifactId>springboot_dubbo</artifactId>
    <version>1.0</version>
    <modules>
        <module>dubbo_common</module>
        <module>dubbo_provider</module>
        <module>dubbo_consumer</module>
    </modules>
    <packaging>pom</packaging>
    <!-- 这里使用的 SpringBoot 的版本是 2.4.5 -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.5</version>
    </parent>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <!-- 引入该依赖,主要在创建实体对象时,省去写 get 和 set 方法的麻烦,同时使用其日志打印功能 -->
    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>
</project>

三、公共实体类和接口

由于 Dubbo 采用 Tcp 协议通信,实体类数据对象在服务器之间传输,需要进行序列化和反序列化,因此实体类必须实现 Serializable 接口。dubbo_common 的实体类和接口代码细节如下所示:

//报销费用
@Data
public class Cost implements Serializable {

    private Integer id;

    //报销类别
    private String category;

    //报销金额
    private Integer money;

    //报销人
    private Employee employee;
}

//员工
@Data
public class Employee implements Serializable {

    private Integer id;

    //姓名
    private String name;

    //部门
    private String depart;
}

//本博客的 Demo 中,接口中只定义了一个方法:通过 id 获取员工信息
public interface EmployeeService {

    Employee getEmployeeById(Integer id);
}

四、服务提供者

对于服务的提供者,由于提供的是 Tcp 接口,不需要提供 Http 接口,因此引入最简单的 SpringBoot 依赖即可:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>springboot_dubbo</artifactId>
        <groupId>com.jobs</groupId>
        <version>1.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>dubbo_provider</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <!--如果该 springboot 程序只是做 dubbo 的服务端提供 tcp 接口,
        不对外提供 http 接口的话,那么只需要引用 springboot 的起步依赖即可。-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <!--引入 dubbo 的起步依赖-->
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>2.7.8</version>
        </dependency>
        <!--我们使用 nacos 作为 dubbo 的注册中心,因此需要引入以下依赖包-->
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-registry-nacos</artifactId>
            <version>2.7.8</version>
        </dependency>
        <!--引入公共的实体类和接口-->
        <dependency>
            <groupId>com.jobs</groupId>
            <artifactId>dubbo_common</artifactId>
            <version>1.0</version>
        </dependency>
    </dependencies>
</project>

SpringBoot 集成 Dubbo 需要引入 dubbo-spring-boot-starter 依赖,由于使用 Nacos 作为服务的注册中心,因此需要引入 dubbo-registry-nacos 依赖,然后定义一个类 EmployeeServiceImpl 实现 dubbo_common 中的 EmployeeService 接口,在实现类上需要使用 @DubboService 即可,内容如下:

package com.jobs.service.impl;

import com.jobs.pojo.Employee;
import com.jobs.service.EmployeeService;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboService;
import java.util.Random;

//可以指定接口的版本
//@DubboService(version = "1.0")
//@DubboService(version = "2.0")
@Slf4j
@DubboService
public class EmployeeServiceImpl implements EmployeeService {

    @Override
    public Employee getEmployeeById(Integer id) {
        Employee employee = new Employee();
        employee.setId(id);

        //随机生成汉字
        int temp1 = 16 + new Random().nextInt(40);
        int temp2 = 1 + new Random().nextInt(94);
        byte[] bytes = new byte[2];
        bytes[0] = (byte) (0xa0 + temp1);
        bytes[1] = (byte) (0xa0 + temp2);
        String ch;
        try {
            ch = new String(bytes, "gb2312");
        } catch (Exception ex) {
            ch = "null";
        }
        employee.setName("小" + ch);

        String[] departArr = {"研发部", "财务部", "销售部", "运营部", "人事部", "宣传部"};
        Random random = new Random();
        int index = random.nextInt(6);
        employee.setDepart(departArr[index]);

        log.info("返回员工的 id 为:" + id);
        return employee;
    }
}

然后在服务提供者 dubbo_provider 项目的 application.yml 对 dubbo 服务进行相关配置:

spring:
  application:
    name: dubbo-provider

# 配置 dubbo 提供者信息:采用 dubbo 协议,指定服务的 tcp 端口
dubbo:
  protocol:
    name: dubbo
    port: 22222
  # 使用 nacos 作为注册中心
  registry:
    address: nacos://192.168.216.128:8848
  # dubbo 扫描服务接口的包,一般扫描接口所在的包即可
  scan:
    base-packages: com.jobs.service

这里使用 22222 作为 dubbo 服务的 tcp 端口,因此服务提供者所部署的服务器有防火墙的话,需要开放这个端口,本篇博客由于服务提供者和服务消费者都是在本地的机器上,因此不存在防火墙端口不同的问题,在实际生产环境中,服务提供者和消费者很有可能会部署在不同的服务器上。


五、服务消费者

对于服务的消费者 dubbo_consumer 来说,由于需要提供 Http 接口让我们进行测试,因此需要引入 SpringBoot 的 Web 依赖,除此之外其它的依赖包跟 dubbo_provider 提供者引入的 jar 包依赖完全相同:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>springboot_dubbo</artifactId>
        <groupId>com.jobs</groupId>
        <version>1.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>dubbo_consumer</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <!--这里的 dubbo 消费者对外提供 http 接口,用于通过浏览器测试,
        因此引入了 springboot web 的依赖包-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--引入 dubbo 的起步依赖-->
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>2.7.8</version>
        </dependency>
        <!--我们使用 nacos 作为 dubbo 的注册中心,因此需要引入以下依赖包-->
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-registry-nacos</artifactId>
            <version>2.7.8</version>
        </dependency>
        <!--引入公共的实体类和接口-->
        <dependency>
            <groupId>com.jobs</groupId>
            <artifactId>dubbo_common</artifactId>
            <version>1.0</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

定义一个 Controller 提供一个 Get 请求方式的 Http 接口,供我们在浏览器地址栏上直接敲击测试使用:

package com.jobs.controller;

import com.jobs.pojo.Cost;
import com.jobs.pojo.Employee;
import com.jobs.service.EmployeeService;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Random;

@RequestMapping("/emp")
@RestController
public class EmployeeController {

    //引用 dubbo 提供的服务,可以指定版本,指定负载均衡算法,重试次数,超时毫秒等。
    //重试次数 retries 可以统一在 application.yml 进行配置,对于增删改操作,建议不要重试
    //负载均衡默认为 random ,还可以配置为:roundrobin 、leastactive 、consistenthash
    //超时毫秒数 timeout ,可以统一在 application.yml 进行配置,在具体服务上做个性化配置
    @DubboReference(loadbalance = "random")
    private EmployeeService employeeService;

    @GetMapping("/{id}")
    public Cost queryCostDetail(@PathVariable("id") Integer id) {
        Cost cost = new Cost();
        cost.setId(id);

        String[] arr = {"打车费", "团建费", "交通费", "招待费", "电话费", "住宿费"};
        Random rd = new Random();
        int index = rd.nextInt(6);
        cost.setCategory(arr[index]);
        cost.setMoney(rd.nextInt(500));

        Employee emp = employeeService.getEmployeeById(id);
        cost.setEmployee(emp);

        return cost;
    }
}

然后在 application.yml 中配置上 dubbo 相关的信息,另外可以从全局的角度配置上请求超时时间、重试次数、在启动消费者程序时,是否到注册中心检查服务提供者是否存在等信息,具体内容如下:

server:
  port: 8888
spring:
  application:
    name: dubbo-consumer

# 配置 dubbo 消费者信息
dubbo:
  registry:
    address: nacos://192.168.216.128:8848
  consumer:
    # 关闭了启动检查,这样消费者启动时,不会到 nacos 中检查服务提供者是否存在
    check: false
    # 建议在这里统一配置为不重试请求,对于查询来说可以在代码中单独配置重试次数
    retries: 0
    # 默认情况下限制请求必须在 1000 毫秒内完成,对于具体服务可以在代码中单独配置
    timeout: 1000

六、验证成果

启动 dubbo_provider 和 dubbo_consumer 程序,然后在 Dubbo Admin 上都可以看到服务信息,你也可以点击详情进行查看。

image

在 Nacos 上能够看到【服务提供者】和【服务消费者】都注册上去了,你也可以点击详情进行查看细节:

image

由于我们配置的服务消费者网站启动的端口是 8888 ,因此你可以调用一下消费者提供的 http 接口进行测试。

比如调用 http://localhost:8888/emp/1 查询 id 为 1 的员工报销款信息,消费者会调用提供者的 dubbo 接口获取员工信息。

image


本篇博客的 Demo 源代码下载地址:https://files.cnblogs.com/files/blogs/699532/springboot_dubbo.zip