基于Dubbo的分布式项目搭建

前言

关于分布式是什么我们为什么要弄分布式这里不赘述,这里只总结一下分布式项目从 0 搭建的方法和注意事项,本项目各个模块之间调用是基于 Dubbo+Zookeeper 来实现的

一般情况下我们都是按照服务来切分的,用以各个服务之间的解耦和切分部署,父项目为普通的 Maven 项目,删掉了源代码文件夹和资源文件夹,只留下光秃秃的 pom.xml,来对各个服务依赖进行统筹管理

这里不使用 SpringBoot 作为父项项目的原因是因为 SpringBoot 总会继承自父项 SpringBoot,子模块必然会继承相关依赖,建立空的 Maven 项目更加合适

代码地址

https://github.com/bwensun/Embryo/tree/master/BLOG

项目搭建

项目结构

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
BLOG
├─ .gitignore
├─ BLOG_API
│ ├─ BLOG_API.iml
│ ├─ pom.xml
│ ├─ src
│ │ └─ main
│ │ └─ java
├─ BLOG_USER_SERVICE
│ ├─ BLOG_USER_SERVICE.iml
│ ├─ pom.xml
│ ├─ src
│ │ ├─ main
│ │ │ ├─ java
│ │ │ └─ resources
│ │ └─ test
│ │ └─ java
├─ BLOG_WEB
│ ├─ BLOG_WEB.iml
│ ├─ pom.xml
│ ├─ src
│ │ └─ main
│ │ ├─ java
│ │ └─ resources
└─ pom.xml

项目建立

建立 Maven 项目父工程 BLOG
正常输入 GAV,groupId 为项目名,artifactId 为倒置的域名,版本号随意,我一般填入 1.0,选择默认 1.0 快照版本也可以的
image.png

这里对项目的依赖做了统筹管理,在父 pom 中规定了依赖的版本,而子模块中只需写入 GA 信息即可,这样整个工程的依赖版本更迭时只需要修改父工程的依赖版本即可,另外父 pom 中也规定了整个项目共用了的依赖,通过依赖传递让子模块自动继承

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<packaging>pom</packaging>
<modules>
<module>BLOG_API</module>
<module>BLOG_USER_SERVICE</module>
<module>BLOG_WEB</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.bwensun</groupId>
<artifactId>blog</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>blog</name>
<description>个人博客项目</description>

<properties>
<java.version>1.8</java.version>
<main.class>com.bwensun.blog.BlogApplication</main.class>

<!--maven依赖版本控制-->
<mybatis-spring-boot-starter>1.3.0</mybatis-spring-boot-starter>
<dubbo-spring-boot-starter>0.2.0</dubbo-spring-boot-starter>
<mybatis-generator-core>1.3.7</mybatis-generator-core>
<druid-spring-boot-starter>1.1.10</druid-spring-boot-starter>
<mysql-connector-java>5.1.44</mysql-connector-java>
<lombok>1.16.18</lombok>
<pagehelper>5.1.10</pagehelper>
<springfox-swagger2>2.6.1</springfox-swagger2>
</properties>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.bwensun</groupId>
<artifactId>BLOG_API</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis-spring-boot-starter}</version>
</dependency>
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>${dubbo-spring-boot-starter}</version>
</dependency>
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>${mybatis-generator-core}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid-spring-boot-starter}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql-connector-java}</version>
</dependency>
</dependencies>
</dependencyManagement>

<!--公有依赖-->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok}</version>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>${pagehelper}</version>
</dependency>
<!-- swagger2 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>${springfox-swagger2}</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>${springfox-swagger2}</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>${main.class}</mainClass>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

**
建立 BLOG_API 模块
Dubbo 官网服务化最佳实践中有提及,建议将服务接口、服务模型、服务异常等均放在 API 包中,我们也遵循其规范,右键项目 New Module -> 名为 BLOG_API 和父项目类似,注意设置 Maven Repository 设置,版本号和父项目一致,可以从 pom.xml 中看到继承自父项目
API 模块用于定义一些公有的实体类、工具类、异常类和服务接口,调用方引入该模块,通过接口调用服务而不用关心到底是哪一个 Service 是实现了该功能
建立完毕后注意执行 mvn clean install 安装到本地仓库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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>blog</artifactId>
<groupId>com.bwensun</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>BLOG_API</artifactId>

</project>

**
建立 BLOG_USER_SERVICE 模块和 BLOG_Web 模块
建立模块过程和之前方法一致
这里需要注意的是这两个项目我重新引入了 SpringBoot 相关的依赖,标记文件夹,并添加启动类,和配置文件使之成为一个单独的 SpringBoot 项目,用以启动和调用服务
Zookeeper 的安装和启动连接:略
以下是pom.xmlapplication.yaml配置,启动类略
BLOG_USER_SERVICE 模块
pom.xml

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
62
63
64
65
66
67
68
69
<?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>blog</artifactId>
<groupId>com.bwensun</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>BLOG_USER_SERVICE</artifactId>

<dependencies>
<dependency>
<groupId>com.bwensun</groupId>
<artifactId>BLOG_API</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
</dependency>
<!--druid连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.7</version>
<configuration>
<!--自定义路径,否则会自动识别GeneratorConfig.xml文件-->
<!-- <configurationFiile>${basedir}/src/main/resources/generatorConfig.xml</configurationFile>-->
<!--是为了运行mvn mybatis-generator:generate -e时显示具体过程-->
<verbose>true</verbose>
<!--为了生成文件时后一次生成的文件覆盖前一次生成的同名文件-->
<overwrite>true</overwrite>
</configuration>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.44</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</project>

yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
dubbo:
application:
name: boot-order-service-consumer
registry:
#address: zookeeper://localhost:2181ip
address: zookeeper://ip:2181?backup=47.103.215.103:2182,47.103.215.103:2183
file: dubbo-registry/dubbo-registry.properties

# monitor:
# protocol: registry
server:
port: 8081
spring:
profiles:
include: druid
mybatis:
mapper-locations: classpath:mapper/*.xml

启动类
注意@EnableDubbo 这个注解要加上

1
2
3
4
5
6
7
@SpringBootApplication
@EnableDubbo
public class BlogUserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(BlogUserServiceApplication.class, args);
}
}

BLOG_WEB 模块
pom.xml

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
<?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>blog</artifactId>
<groupId>com.bwensun</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>BLOG_WEB</artifactId>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.bwensun</groupId>
<artifactId>BLOG_API</artifactId>
</dependency>
</dependencies>
</project>

yaml

1
2
3
4
5
6
7
8
9
10
11
dubbo:
application:
name: boot-order-service-consumer
registry:
address: zookeeper://IP:2181?backup=47.103.215.103:2182,47.103.215.103:2183
file: dubbo-registry/dubbo-registry.properties

# monitor:
# protocol: registry
server:
port: 8081

启动类

注意@EnableDubbo 这个注解要加上

1
2
3
4
5
6
7
@SpringBootApplication
@EnableDubbo
public class BlogWebApplication {
public static void main(String[] args) {
SpringApplication.run(BlogWebApplication.class, args);
}
}

**

写个 Demo

BLOG_API 添加实体类、接口

  • 添加实体类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Getter
@Setter
@ToString
public class User implements Serializable {
/**
* 主键
*/
private Integer id;

/**
* 用户名
*/
private String userName;

/**
* 密码
*/
private String password;

private static final long serialVersionUID = 1L;
}
  • 添加服务接口
1
2
3
4
5
6
7
8
9
@Component
public interface UserService {

/**
* 用户信息分页查询
* @return 分页用户对象
*/
PageInfo<User> getUserList();
}

BLOG_USER_SERVICE 实现该接口

  • 实现该服务接口

注意:
Service 为import ``com.alibaba.dubbo.config.annotation.``Service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Component
@Service
public class UserServiceImpl implements UserService {

@Resource
UserMapper userMapper;

@Override
public PageInfo<User> getUserList() {
PageHelper.startPage(1, 10);
List<User> users = userMapper.selectAll();
return new PageInfo<>(users);
}

  • 具体 Dao 层略

**

BLOG_WEB 添加控制器

1
2
3
4
5
6
7
8
9
10
11
12
13
@RestController
@RequestMapping("user")
public class UserController {

@Reference
UserService userService;

@RequestMapping("userList")
public PageInfo<User> getUserList(){
return userService.getUserList();
}
}

测试

  1. 启动BLOG_USER_SERVICE项目来注册到 ZK 之中,控制台打印如下

image.png

  1. 启动BLOG_WEB项目,用于接受客户端的请求,同样他会连接 ZK 来获取服务image.png
  2. 访问localhost:8081/user/userList可以发现已经接受了返回的查询结果image.png

问题和解决

事实上,在实际测试中遇到了非常多问题,我把这些都记录下来,放到了

相关

  1. Dubbo 官方文档
  2. Spring Boot-整合 Dubbo
作者

孙博文

发布于

2019-12-19

更新于

2021-07-18

许可协议

评论