Dubbo在SpringBoot中的实践

Apache Dubbo,一款高性能、轻量级的RPC框架。这里就其在Spring Boot下的实践进行介绍

abstract.png

注册中心:ZooKeeper

这里我们选用ZooKeeper作为Dubbo的注册中心。Docker命令如下所示

1
2
3
4
5
6
7
8
# 拉取ZooKeeper镜像
docker pull zookeeper
# 启动ZooKeeper容器
docker run --name ZooKeeper-1 -p 2181:2181 -d \
-v /Users/zgh/Docker/ZooKeeper/ZooKeeper-1/conf:/conf \
-v /Users/zgh/Docker/ZooKeeper/ZooKeeper-1/data:/data \
-v /Users/zgh/Docker/ZooKeeper/ZooKeeper-1/datalog:/datalog \
-v logs:/logs zookeeper

基于Maven的聚合工程

为了便于演示,先建立一个基于Maven的聚合工程,其含有三个子模块:common-api、provider和consumer

figure 1.jpeg

子模块:common-api

该模块用于提供服务提供者、消费者的公共类、接口。这里,我们提供一个学生信息的服务接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.aaron.api.service;

import com.aaron.api.pojo.ResultData;
import com.aaron.api.pojo.Student;

/**
* 公共服务接口: 学生服务接口
*/
public interface StudentService{
/**
* 通过ID获取学生信息
* @param id
* @return
*/
ResultData<Student> getStuById(Integer id);
}

该公共服务接口中涉及到相关类,实现如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package com.aaron.api.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;

/**
* 结果封装类
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ResultData<T> implements Serializable{

/**
* 状态码
*/
private String code;

/**
* 状态描述
*/
private String msg;

/**
* 结果数据
*/
private T data;
}
...
package com.aaron.api.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;

/**
* 学生
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student implements Serializable {

/**
* 学号
*/
private Integer id;

/**
* 姓名
*/
private String name;

/**
* 专业
*/
private String major;
}

最后,将该模块打成jar包并安装到Maven的本地仓库下

子模块:provider

该模块作为服务的提供者,该模块的POM文件部分配置如下。可以看到我们不仅引入了Dubbo、ZooKeeper客户端、Netty等相关组件的依赖,还需要引入公共服务接口common-api的依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
...
<dependencies>

<!--Spring Boot-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.1.4.RELEASE</version>
</dependency>

<!--Dubbo-->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.8</version>
</dependency>

<!--ZooKeeper客户端-->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.8.0</version>
</dependency>

<!--Netty-->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.0.35.Final</version>
</dependency>

<!--公共服务接口-->
<dependency>
<groupId>com.aaron</groupId>
<artifactId>common-api</artifactId>
<version>1.0</version>
</dependency>

</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.aaron.provider.ProdiverApplication</mainClass>
</configuration>
</plugin>
</plugins>
</build>
...

现在我们提供StudentService接口的实现类——StudentServiceImpl,代码如下所示。值得一提的是,@Service是org.apache.dubbo.config.annotation.Service包下的注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.aaron.provider.service;

import com.aaron.api.pojo.ResultData;
import com.aaron.api.pojo.Student;
import com.aaron.api.service.StudentService;
import org.apache.dubbo.config.annotation.Service;
import java.util.HashMap;
import java.util.Map;

@Service
public class StudentServiceImpl implements StudentService {

private static Map<Integer, Student> map = new HashMap();

static {
map.put( 1, new Student(1, "Aaon", "计算机科学") );
map.put( 2, new Student(2, "Tony", "美容美发") );
map.put( 3, new Student(3, "David", "生物工程") );
}

@Override
public ResultData<Student> getStuById(Integer id) {
ResultData<Student> studentResultData = new ResultData<>("400", "无数据", null);

Student student = map.get(id);
if( student != null ) {
studentResultData = new ResultData<>("200","成功, 有数据", student);
}
return studentResultData;
}
}

对于该模块的配置文件application.yml而言,会进行Dubbo相关的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 服务端口
server:
port: 8091

dubbo:
application:
# 配置别名
name: Dubbo-Demo1-Provider
# Dubbo 使用的协议、端口
protocol:
name: dubbo
port: 20880
# 注册中心 ZooKeeper 地址、端口
registry:
address: zookeeper://127.0.0.1:2181
# 指定所提供的服务所在的包
scan:
base-packages: com.aaron.provider.service

子模块:consumer

该模块作为服务的消费者,其所需的依赖与provider模块的依赖完全一致。故其POM文件就不再赘述。然后,我们写一个Controller来使用provider所提供的服务。在依赖注入时,可通过@Reference注解引入远程服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.aaron.consumer.controller;

import com.aaron.api.pojo.ResultData;
import com.aaron.api.pojo.Student;
import com.aaron.api.service.StudentService;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class StudentController {

@Reference
private StudentService studentService;

@RequestMapping(value = "/StuInfoController")
public ResultData<Student> findStudentById(@RequestParam Integer id) {
ResultData<Student> studentResultData = studentService.getStuById(id);
return studentResultData;
}
}

对于该模块的配置文件application.yml而言,配置如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 服务端口
server:
port: 8093

dubbo:
application:
# 配置别名
name: Dubbo-Demo1-Consumer
# Dubbo 使用的协议、端口
protocol:
name: dubbo
port: 20880
# 注册中心 ZooKeeper 地址、端口
registry:
address: zookeeper://127.0.0.1:2181

验证

至此,本工程涉及到的核心代码就已经开发完成了。现在我们分别启动provider、consumer服务来进行验证、测试工作。测试结果如下所示,符合预期

figure 2.jpeg

Dubbo可视化:dubbo-admin

dubbo-admin,作为dubbo自带的可视化工具。这里我们通过Docker来进行部署。其中-e选项用于设置ZOOKEEPER_SERVER环境变量,配置注册中心的地址、端口信息(通过ifconfig命令可知,本机IP为192.168.1.106)。值得一提的是,启动dubbo-admin容器时,必须保证ZooKeeper服务已经处于运行状态

1
2
3
4
5
6
7
# 拉取dubbo-admin镜像
docker pull webuilder/dubboadmin

# 启动dubbo-admin容器
docker run --name dubbo-admin \
-p 8080:8080 -d \
-e ZOOKEEPER_SERVER=192.168.1.106:2181 webuilder/dubboadmin

现在就可以通过 http://127.0.0.1:8080 来访问Web页面。其中,root、guest账号的密码分别为root、guest。效果如下所示

figure 3.jpeg

直连

在前面的例子中,服务的消费者是通过注册中心来获取所需服务的提供者信息。而有时候,在开发、联调的过程中,我们希望能够直接调用指定的服务提供者,而不经过注册中心。即所谓的直连。从上图dubbo-admin的页面,我们可以看到StudentService接口提供者的地址信息:dubbo://192.168.1.106:20880/com.aaron.api.service.StudentService

现在为了验证不使用注册中心,直连也是有效的。我们一方面停止ZooKeeper容器,另一方面修改provider、consumer模块的配置文件,将注册中心的地址设为N/A

1
2
3
4
5
6
...
dubbo:
...
registry:
address: N/A
...

然后,在消费者的@Reference注解中,通过url属性来指定相关服务的地址即可

1
2
3
4
5
6
7
8
9
...
@RestController
public class StudentController {

@Reference(url = "dubbo://192.168.1.106:20880/com.aaron.api.service.StudentService")
private StudentService studentService;
...
}
...

修改完毕后,重启provider、consumer服务。通过dubbo-admin的管理页面可以看到,注册中心已经下线了

figure 4.jpeg

现在,让我们测试下,结果符合预期

figure 5.jpeg

Note

  1. 在生产者、消费者之间进行网络传输的类需要实现序列化接口Serializable
0%