从零开始学习dubbo(二)注册中心和监控中心

dubbo(二)注册中心和监控中心

第一节 简介

1.1 dubbo支持的几种注册中心

  • Multicast:不需要启动任何中心节点,只要广播地址一样,就可以互相发现。
  • Zookeeper:Apache Hadoop 的子项目,是一个树型的目录服务,支持变更推送,适合作为 Dubbo 服务的注册中心,工业强度较高,可用于生产环境,并推荐使用。
  • Nacos:Nacos 是 Dubbo 生态系统中重要的注册中心实现,其中 dubbo-registry-nacos 则是 Dubbo 融合 Nacos 注册中心的实现。
  • Redis:使用 Redis 的 Key/Map 结构存储数据结构,使用 Redis 的 Publish/Subscribe 事件通知数据变更。
  • Simple:Simple 注册中心本身就是一个普通的 Dubbo 服务,可以减少第三方依赖,使整体通讯方式一致。

1.2 Zookeeper注册中心

官方推荐的注册中心,是一个树型的目录服务,支持变更推送,适合作为 Dubbo 服务的注册中心,工业强度较高,可用于生产环境。如下图树型结构。

流程说明:

  • 服务提供者启动时: 向 /dubbo/com.foo.BarService/providers 目录下写入自己的 URL 地址
  • 服务消费者启动时: 订阅 /dubbo/com.foo.BarService/providers 目录下的提供者 URL 地址。并向 /dubbo/com.foo.BarService/consumers 目录下写入自己的 URL 地址
  • 监控中心启动时: 订阅 /dubbo/com.foo.BarService 目录下的所有提供者和消费者 URL 地址。

支持以下功能:

  • 当提供者出现断电等异常停机时,注册中心能自动删除提供者信息。
  • 当注册中心重启时,能自动恢复注册数据,以及订阅请求。
  • 当会话过期时,能自动恢复注册数据,以及订阅请求。
  • 当设置 <dubbo:registry check="false" /> 时,记录失败注册和订阅请求,后台定时重试。
  • 可通过 <dubbo:registry username="admin" password="1234" /> 设置 zookeeper 登录信息。
  • 可通过 <dubbo:registry group="dubbo" /> 设置 zookeeper 的根节点,不配置将使用默认的根节点。
  • 支持 * 号通配符 <dubbo:reference group="*" version="*" />,可订阅服务的所有分组和所有版本的提供者。

第二节 使用

学习环境:LG笔记本 + Win10

2.1 注册中心

首先安装Zookeeper注册中心,下载地址:Zookeeper - download

包结构:

  • bin:二进制执行文件,如 zkServer.cmd 可用于Win10执行。
  • conf:配置文件 .cfg
  • contrib:
  • dist-maven:
  • docs:
  • lib:
  • recipes:
  • src:

首次启动 zkServer.cmd 会出错。

找到默认配置文件,创建副本并改名为 zoo.cfg

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
# The number of milliseconds of each tick
tickTime=2000
# The number of ticks that the initial
# synchronization phase can take
initLimit=10
# The number of ticks that can pass between
# sending a request and getting an acknowledgement
syncLimit=5
# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just
# example sakes.
# 数据存储位置
dataDir=../data
# the port at which the clients will connect
clientPort=2181
# the maximum number of client connections.
# increase this if you need to handle more clients
#maxClientCnxns=60
#
# Be sure to read the maintenance section of the
# administrator guide before turning on autopurge.
#
# http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance
#
# The number of snapshots to retain in dataDir
#autopurge.snapRetainCount=3
# Purge task interval in hours
# Set to "0" to disable auto purge feature
#autopurge.purgeInterval=1

再次启动 zkServer.cmd

启动 zkCli.cmd 简单使用以下Zookeeper。

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
# 获取根目录文件
[zk: localhost:2181(CONNECTED) 0] get /

cZxid = 0x0
ctime = Thu Jan 01 08:00:00 CST 1970
mZxid = 0x0
mtime = Thu Jan 01 08:00:00 CST 1970
pZxid = 0x0
cversion = -1
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 0
numChildren = 1
# 查看根目录
[zk: localhost:2181(CONNECTED) 1] ls /
[zookeeper]
# 新建节点test
[zk: localhost:2181(CONNECTED) 2] create -e /test 123456
Created /test
# 再次查看目录
[zk: localhost:2181(CONNECTED) 3] ls /
[zookeeper, test]
# 获取test节点
[zk: localhost:2181(CONNECTED) 4] get /test
123456
cZxid = 0x4
ctime = Tue Sep 08 20:10:35 CST 2020
mZxid = 0x4
mtime = Tue Sep 08 20:10:35 CST 2020
pZxid = 0x4
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x100061cf1510001
dataLength = 6
numChildren = 0

2.2 监控中心

在GitHub找到dubbo项目:apache/dubbo。往下找到ecosystem,首先下载Dubbo Admin这是一个管理控制台。

查看 application.properties 中Zookeeper地址是否正确。

在Idea中运行或用Maven打包启动,注意运行端口。

生产环境配置:

  1. 下载代码: git clone https://github.com/apache/dubbo-admin.git

  2. dubbo-admin-server/src/main/resources/application.properties中指定注册中心地址

  3. 构建

    • mvn clean package
  4. 启动

    • mvn --projects dubbo-admin-server spring-boot:run
      或者
    • cd dubbo-admin-distribution/target; java -jar dubbo-admin-0.1.jar
  5. 访问 http://localhost:8080

开发环境配置:

  • 运行dubbo admin server dubbo admin server是一个标准的spring boot项目, 可以在任何java IDE中运行它
  • 运行dubbo admin ui dubbo admin ui由npm管理和构建,在开发环境中,可以单独运行: npm run dev
  • 页面访问 访问 http://localhost:8081, 由于前后端分开部署,前端支持热加载,任何页面的修改都可以实时反馈,不需要重启应用。

部署完成后,可以访问 http://localhost:8080/swagger-ui.html 来查看所有的restful api。

访问UI对应地址。

默认使用 root/root 登陆。

登录后可以查看Dubbon Admin提供的基础功能。

2.3 实例

(1)需求

描述:某电商系统,其订单服务需要调用用户服务获取指定用户的所有地址。

因此我们需要构建两个服务模块:订单服务Web模块,用户服务Service模块。

实现:订单服务Web模块部署于服务器A,用户服务Service模块部署于服务器B,A可以远程调用B。

(2)架构

根据 dubbo《服务化最佳实践》:

  1. 分包:建议将服务接口,服务模型,服务异常等均放在 API 包中,因为服务模型及异常也是 API 的一部分,同时,这样做也符合分包原则:重用发布等价原则(REP),共同重用原则(CRP)。

    如果需要,也可以考虑在 API 包中放置一份 spring 的引用配置,这样使用方,只需在 spring 加载过程中引用此配置即可,配置建议放在模块的包目录下,以免冲突,如:/xxx/dubbo-reference.xml。

  2. 粒度:服务接口尽可能大粒度,每个服务方法应代表一个功能,而不是某功能的一个步骤,否则将面临分布式事务问题,Dubbo 暂未提供分布式事务支持。

    服务接口建议以业务场景为单位划分,并对相近业务做抽象,防止接口数量爆炸。

    不建议使用过于抽象的通用接口,如:Map query(Map),这样的接口没有明确语义,会给后期维护带来不便。

  3. 版本:每个接口都应定义版本号,为后续不兼容升级提供可能,如: <dubbo:service interface="com.xxx.XxxService" version="1.0" />

    建议使用两位版本号,因为第三位版本号通常表示兼容升级,只有不兼容时才需要变更服务版本。

    当不兼容时,先升级一半提供者为新版本,再将消费者全部升为新版本,然后将剩下的一半提供者升为新版本。

  4. 兼容性:服务接口增加方法,或服务模型增加字段,可向后兼容,删除方法或删除字段,将不兼容,枚举类型新增字段也不兼容,需通过变更版本号升级。

    各协议的兼容性不同,参见:服务协议

  5. 枚举值

    • 如果是完备集,可以用 Enum,比如:ENABLE, DISABLE
    • 如果是业务种类,以后明显会有类型增加,不建议用 Enum,可以用 String 代替。
    • 如果是在返回值中用了 Enum,并新增了 Enum 值,建议先升级服务消费方,这样服务提供方不会返回新值。
    • 如果是在传入参数中用了 Enum,并新增了 Enum 值,建议先升级服务提供方,这样服务消费方不会传入新值。
  6. 序列号

    • 服务参数及返回值建议使用 POJO 对象,即通过 setter, getter 方法表示属性的对象。
    • 服务参数及返回值不建议使用接口,因为数据模型抽象的意义不大,并且序列化需要接口实现类的元信息,并不能起到隐藏实现的意图。
    • 服务参数及返回值都必须是传值调用,而不能是传引用调用,消费方和提供方的参数或返回值引用并不是同一个,只是值相同,Dubbo 不支持引用远程对象。
  7. 异常

    • 建议使用异常汇报错误,而不是返回错误码,异常信息能携带更多信息,并且语义更友好。
    • 如果担心性能问题,在必要时,可以通过 override 掉异常类的 fillInStackTrace() 方法为空方法,使其不拷贝栈信息。
    • 查询方法不建议抛出 checked 异常,否则调用方在查询时将过多的 try...catch,并且不能进行有效处理。
    • 服务提供方不应将 DAO 或 SQL 等异常抛给消费方,应在服务实现中对消费方不关心的异常进行包装,否则可能出现消费方无法反序列化相应异常。
  8. 调用

    • 不要只是因为是 Dubbo 调用,而把调用 try...catch 起来。try...catch 应该加上合适的回滚边界上。
    • Provider 端需要对输入参数进行校验。如有性能上的考虑,服务实现者可以考虑在 API 包上加上服务 Stub 类来完成检验。

最终:

  • gmall-order-web:订单服务Web模块。
  • gmall-user:用户服务Service模块
  • gmall-interface:公共接口层。

(3)编码

创建Maven项目gmall,并分别创建子项目:

  • gmall-order-web:订单服务Web模块。
  • gmall-user:用户服务Service模块
  • gmall-interface:公共接口层。

使用dubbo:

  • 首先引入dubbo和操作zookeeper的客户端的依赖;
  • 创建xml,并在文件中配置服务(即官网所说的用Spring配置暴露服务);
  • 在启动类中加载Spring配置。
1.user-service-provider

pom.xml,分别引入dubbo和zookeeper客户端。

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
<?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>gmall</artifactId>
<groupId>org.xxx</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<groupId>org.lai.gmall</groupId>
<artifactId>user-service-provider</artifactId>

<dependencies>
<dependency>
<groupId>org.lai.gmall</groupId>
<artifactId>gmall-interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>

<!-- 引入dubbo -->
<!-- https://mvnrepository.com/artifact/com.alibaba/dubbo -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.6.2</version>
</dependency>
<!-- 注册中心使用的是zookeeper,引入操作zookeeper的客户端 -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.12.0</version>
</dependency>
</dependencies>
</project>

provider.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
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
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://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">

<!-- 1、指定当前服务/应用的名字(同样的服务名字相同,不要和别的服务同名) -->
<dubbo:application name="user-service-provider"></dubbo:application>

<!-- 2、指定注册中心的位置 -->
<!-- <dubbo:registry address="zookeeper://127.0.0.1:2181"></dubbo:registry> -->
<dubbo:registry protocol="zookeeper" address="127.0.0.1:2181"></dubbo:registry>

<!-- 3、指定通信规则(通信协议?通信端口) -->
<dubbo:protocol name="dubbo" port="20882"></dubbo:protocol>

<!-- 4、暴露服务 ref:指向服务的真正的实现对象 -->
<dubbo:service interface="gmall.service.UserService"
ref="userServiceImpl01" timeout="1000" version="1.0.0">
<dubbo:method name="getUserAddressList" timeout="1000"></dubbo:method>
</dubbo:service>

<!--统一设置服务提供方的规则 -->
<dubbo:provider timeout="1000"></dubbo:provider>

<!-- 服务的实现 -->
<bean id="userServiceImpl01" class="gmall.service.impl.UserServiceImpl"></bean>

<dubbo:service interface="gmall.service.UserService"
ref="userServiceImpl02" timeout="1000" version="2.0.0">
<dubbo:method name="getUserAddressList" timeout="1000"></dubbo:method>
</dubbo:service>
<bean id="userServiceImpl02" class="gmall.service.impl.UserServiceImpl2"></bean>

<!-- 连接监控中心 -->
<dubbo:monitor protocol="registry"></dubbo:monitor>

</beans>

MainApplication

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package gmall;

import java.io.IOException;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainApplication {

public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("provider.xml");
ioc.start();
System.in.read();
}
}

UserServiceImpl

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 gmall.service.impl;

import gmall.bean.UserAddress;
import gmall.service.UserService;
import java.util.Arrays;
import java.util.List;

public class UserServiceImpl implements UserService {

public List<UserAddress> getUserAddressList(String userId) {
System.out.println("UserServiceImpl.....old...");
// TODO Auto-generated method stub
UserAddress address1 = new UserAddress(1, "北京市昌平区宏福科技园综合楼3层", "1", "李老师", "010-56253825", "Y");
UserAddress address2 = new UserAddress(2, "深圳市宝安区西部硅谷大厦B座3层(深圳分校)", "1", "王老师", "010-56253825", "N");
/*try {
Thread.sleep(4000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}*/
return Arrays.asList(address1,address2);
}

}

UserServiceImpl2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package gmall.service.impl;

import gmall.bean.UserAddress;
import gmall.service.UserService;
import java.util.Arrays;
import java.util.List;

public class UserServiceImpl2 implements UserService {

public List<UserAddress> getUserAddressList(String userId) {
System.out.println("UserServiceImpl.....new...");
// TODO Auto-generated method stub
UserAddress address1 = new UserAddress(1, "北京市昌平区宏福科技园综合楼3层", "1", "李老师", "010-56253825", "Y");
UserAddress address2 = new UserAddress(2, "深圳市宝安区西部硅谷大厦B座3层(深圳分校)", "1", "王老师", "010-56253825", "N");

return Arrays.asList(address1,address2);
}

}

UserServiceStub

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
package gmall.service.impl;

import java.util.List;
import gmall.bean.UserAddress;
import gmall.service.UserService;
import org.springframework.util.StringUtils;

public class UserServiceStub implements UserService {
private final UserService userService;

/**
* 传入的是userService远程的代理对象
* @param userService
*/
public UserServiceStub(UserService userService) {
super();
this.userService = userService;
}

public List<UserAddress> getUserAddressList(String userId) {
// TODO Auto-generated method stub
System.out.println("UserServiceStub.....");
if(!StringUtils.isEmpty(userId)) {
return userService.getUserAddressList(userId);
}
return null;
}

}
2.order-service-consumer

pom.xml,也分别引入dubbo和zookeeper。

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
<?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>gmall</artifactId>
<groupId>org.lai</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<groupId>org.lai.gmall</groupId>
<artifactId>order-service-consumer</artifactId>

<dependencies>
<dependency>
<groupId>org.lai.gmall</groupId>
<artifactId>gmall-interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>

<!-- 引入dubbo -->
<!-- https://mvnrepository.com/artifact/com.alibaba/dubbo -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.6.2</version>
</dependency>
<!-- 注册中心使用的是zookeeper,引入操作zookeeper的客户端端 -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.12.0</version>
</dependency>
</dependencies>
</project>

consumer.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
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
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://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd
http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<context:component-scan base-package="gmall.service.impl"></context:component-scan>

<dubbo:application name="order-service-consumer"></dubbo:application>

<dubbo:registry address="zookeeper://127.0.0.1:2181"></dubbo:registry>

<!-- 配置本地存根-->

<!--声明需要调用的远程服务的接口;生成远程服务代理 -->
<!--
1)、精确优先 (方法级优先,接口级次之,全局配置再次之)
2)、消费者设置优先(如果级别一样,则消费方优先,提供方次之)
-->
<!-- timeout="0" 默认是1000ms-->
<!-- retries="":重试次数,不包含第一次调用,0代表不重试-->
<!-- 幂等(设置重试次数)【查询、删除、修改】、非幂等(不能设置重试次数)【新增】 -->
<dubbo:reference interface="gmall.service.UserService"
id="userService" timeout="5000" retries="3" version="*">
<!-- <dubbo:method name="getUserAddressList" timeout="1000"></dubbo:method> -->
</dubbo:reference>

<!-- 配置当前消费者的统一规则:所有的服务都不检查 -->
<dubbo:consumer check="false" timeout="5000"></dubbo:consumer>

<dubbo:monitor protocol="registry"></dubbo:monitor>
<!-- <dubbo:monitor address="127.0.0.1:7070"></dubbo:monitor> -->

</beans>

MainApplication

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package gmall;

import java.io.IOException;

import gmall.service.OrderService;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainApplication {

@SuppressWarnings("resource")
public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("consumer.xml");

OrderService orderService = applicationContext.getBean(OrderService.class);

orderService.initOrder("1");
System.out.println("调用完成....");
System.in.read();
}
}

OrderServiceImpl

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
package gmall.service.impl;

import gmall.bean.UserAddress;
import gmall.service.OrderService;
import gmall.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;

/**
* 1、将服务提供者注册到注册中心(暴露服务)
* 1)、导入dubbo依赖(2.6.2)\操作zookeeper的客户端(curator)
* 2)、配置服务提供者
*
* 2、让服务消费者去注册中心订阅服务提供者的服务地址
* @author lfy
*
*/
@Service
public class OrderServiceImpl implements OrderService {

@Autowired
UserService userService;

public List<UserAddress> initOrder(String userId) {
// TODO Auto-generated method stub
System.out.println("用户id:"+userId);
//1、查询用户的收货地址
List<UserAddress> addressList = userService.getUserAddressList(userId);
for (UserAddress userAddress : addressList) {
System.out.println(userAddress.getUserAddress());
}
return addressList;
}
}
3.gmall-interface

pom.xml

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>gmall</artifactId>
<groupId>org.lai</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<groupId>org.lai.gmall</groupId>
<artifactId>gmall-interface</artifactId>
</project>

UserAddress

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package gmall.bean;

import java.io.Serializable;

/**
* 用户地址
* @author lfy
*
*/
public class UserAddress implements Serializable {
private Integer id;
private String userAddress; //用户地址
private String userId; //用户id
private String consignee; //收货人
private String phoneNum; //电话号码
private String isDefault; //是否为默认地址 Y-是 N-否

//constructor and getter and setter
}

OrderService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package gmall.service;

import gmall.bean.UserAddress;
import java.util.List;


public interface OrderService {

/**
* 初始化订单
* @param userId
*/
public List<UserAddress> initOrder(String userId);

}

UserService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package gmall.service;

import gmall.bean.UserAddress;
import java.util.List;

/**
* 用户服务
* @author lfy
*
*/
public interface UserService {

/**
* 按照用户id返回所有的收货地址
* @param userId
* @return
*/
public List<UserAddress> getUserAddressList(String userId);

}

(4)启动

分别启动服务提供者和消费者,控制台输出如下内容:

1
2
3
4
用户id1
北京市昌平区宏福科技园综合楼3
深圳市宝安区西部硅谷大厦B座3层(深圳分校)
调用完成....

此时再启动dubbo-admin,使用 root / root 登录。


第三节 整合Spring Boot

GitHub地址:apache/dubbo-spring-boot-project

下载项目与依赖,并跑通案例 dubbo-spring-boot-samples

3.1 公共接口 sample-api

首先看 dubbo-spring-boot-samples / sample-api ,它是公共接口层,提供了生产者和消费者要使用的DemoService服务接口。

1
2
3
4
5
6
7
package org.apache.dubbo.spring.boot.sample.consumer;

public interface DemoService {

String sayHello(String name);

}

3.2 自动装配 auto-configure-samples

再看 dubbo-spring-boot-samples / auto-configure-samples

(1)provider

首先看服务提供者provider的内容,配置了应用信息、Dubbo协议信息等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Spring boot application 
# 应用名
spring.application.name=dubbo-auto-configuration-provider-demo
# Base packages to scan Dubbo Component: @org.apache.dubbo.config.annotation.Service
# 扫描的基础包
dubbo.scan.base-packages=org.apache.dubbo.spring.boot.sample.provider.service
# Dubbo Application
## The default value of dubbo.application.name is ${spring.application.name}
## dubbo.application.name=${spring.application.name}

# Protocol地址和端口,指定远程调用协议为dubbo,还可以为rmi、http等
# Dubbo Protocol
dubbo.protocol.name=dubbo
dubbo.protocol.port=12345
## Dubbo Registry
## 注册中心服务器地址,N/A为默认值,表示不使用注册中心,直连客户端,地址可以是一个或多个,多个表示集群
dubbo.registry.address=N/A

provider实现了DemoService:

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
@DubboService(version = "1.0.0")
public class DefaultDemoService implements DemoService {

private final Logger logger = LoggerFactory.getLogger(getClass());

private final Random costTimeRandom = new Random();

/**
* The default value of ${dubbo.application.name} is ${spring.application.name}
*/
@Value("${dubbo.application.name}")
private String serviceName;

@Override
public String sayHello(String name) {
//先简单休眠一下
await();
//自己添加的更直观的控制台输出
System.out.println("sayHello: " + name);
//打印serviceName和实参
return String.format("[%s] : Hello, %s", serviceName, name);
}

private void await() {
try {
long timeInMillisToWait = costTimeRandom.nextInt(500);
Thread.sleep(timeInMillisToWait);
logger.info("execution time : " + timeInMillisToWait + " ms.");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}

provider的启动类:

1
2
3
4
5
6
7
8
9
@EnableAutoConfiguration
public class DubboAutoConfigurationProviderBootstrap {

public static void main(String[] args) {
// new SpringApplicationBuilder(DubboAutoConfigurationProviderBootstrap.class)
// .run(args);
SpringApplication.run(DubboAutoConfigurationProviderBootstrap.class,args);
}
}

(2)consumer

再来看服务消费者consumer,只是配置了应用名。

1
2
3
spring:
application:
name: dubbo-auto-configure-consumer-sample

consumer的启动类:

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
@EnableAutoConfiguration
public class DubboAutoConfigurationConsumerBootstrap {

private final Logger logger = LoggerFactory.getLogger(getClass());

//服务提供者注册的地址等信息
@DubboReference(
version = "1.0.0",
url = "dubbo://127.0.0.1:12345",
timeout = 100,
methods = {
@Method(name = "sayHello", timeout = 300)
}
)
private DemoService demoService;

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

@Bean
public ApplicationRunner runner() {
System.out.println("runner()");
//调用公共接口demoService,通过指定的服务实现远程调用服务
return args -> logger.info(demoService.sayHello("mercyblitz"));
}
}

启动并观察是否正确打印出结果。

3.3 外部化配置 externalized-configuration-samples

(1)provider

provider与 auto-configure-samples 中唯一不同的是多了服务版本的配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Spring boot application
spring.application.name=dubbo-externalized-configuration-provider-sample
# Base packages to scan Dubbo Component: @org.apache.dubbo.config.annotation.Service
dubbo.scan.base-packages=org.apache.dubbo.spring.boot.sample.provider.service

# Dubbo Application
## The default value of dubbo.application.name is ${spring.application.name}
## dubbo.application.name=${spring.application.name}

# Dubbo Protocol
dubbo.protocol.name=dubbo
dubbo.protocol.port=12345

## Dubbo Registry
dubbo.registry.address=N/A

## DemoService version
demo.service.version=1.0.0

provider的服务实现,不同的是服务的版本号从配置文件中获取,并且没有sleep。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@DubboService(version = "${demo.service.version}")
public class DefaultDemoService implements DemoService {

/**
* The default value of ${dubbo.application.name} is ${spring.application.name}
*/
@Value("${dubbo.application.name}")
private String serviceName;

@Override
public String sayHello(String name) {
return String.format("[%s] : Hello, %s", serviceName, name);
}
}

provider的启动类完全一致。

(2)consumer

消费者consumer则增加了一些配置,启动了一些dubbo端点

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
spring:
application:
name: dubbo-externalized-configuration-consumer-sample

management:
endpoints:
web:
exposure:
include: '*'
endpoint:
dubbo:
enabled: true
dubboshutdown:
enabled: true
dubboconfigs:
enabled: true
dubboservices:
enabled: true
dubboreferences:
enabled: true
dubboproperties:
enabled: true
security:
## Deprecated 2.x
enabled: false

## For Spring Boot 1.x demo
endpoints:
dubbo:
enabled: true
sensitive: false
dubboshutdown:
enabled: true
dubboconfigs:
enabled: true
dubboservices:
enabled: true
dubboreferences:
enabled: true
dubboproperties:
enabled: true

demo:
service:
version: 1.0.0
url: dubbo://localhost:12345

consumer的启动类中 @DubboReference 也改为从配置文件读取,并且增加了Get请求接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@EnableAutoConfiguration
@RestController
public class DubboExternalizedConfigurationConsumerBootstrap {

private final Logger logger = LoggerFactory.getLogger(getClass());

@DubboReference(version = "${demo.service.version}", url = "${demo.service.url}")
private DemoService demoService;

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

@RequestMapping(value = "/say-hello", method = GET)
public String sayHello(@RequestParam String name) {
return demoService.sayHello(name);
}

@Bean
public ApplicationRunner runner() {
return args -> logger.info(demoService.sayHello("mercyblitz"));
}
}

分别启动 provider 和 consumer 并访问服务接口:

3.4 注册中心 registry-samples

这个实例提供了Dubbo最常用的两种注册中心:Zookeeper 和 Nacos 。

(1)Zookeeper

配置文件如下,有些许变更:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Spring boot application
spring.application.name=dubbo-registry-zookeeper-provider-sample
# Base packages to scan Dubbo Component: @org.apache.dubbo.config.annotation.Service
dubbo.scan.base-packages=org.apache.dubbo.spring.boot.sample.provider.service

# Dubbo Application
## The default value of dubbo.application.name is ${spring.application.name}
## dubbo.application.name=${spring.application.name}

# Dubbo Protocol
dubbo.protocol.name=dubbo
## Random port 此处改为随机端口,会分配一个没有被占用的端口
dubbo.protocol.port=-1

## Dubbo Registry
## 指定了注册中心地址
dubbo.registry.address=zookeeper://127.0.0.1:2181
## 使用文件缓存注册中心地址列表及服务提供者列表,应用重启时将基于此文件恢复,注意:两个注册中心不能使用同一文件存储
dubbo.registry.file = ${user.home}/dubbo-cache/${spring.application.name}/dubbo.cache

## DemoService version
demo.service.version=1.0.0

服务实现没有变更:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@DubboService(version = "${demo.service.version}")
public class DefaultDemoService implements DemoService {

/**
* The default value of ${dubbo.application.name} is ${spring.application.name}
*/
@Value("${dubbo.application.name}")
private String serviceName;

@Override
public String sayHello(String name) {
System.out.println("sayHello : " + name);
return String.format("[%s] : Hello, %s", serviceName, name);
}
}

启动类没有变更:

1
2
3
4
5
6
7
@EnableAutoConfiguration
public class DubboRegistryZooKeeperProviderBootstrap {

public static void main(String[] args) {
new SpringApplicationBuilder(DubboRegistryZooKeeperProviderBootstrap.class).run(args);
}
}

consumer中配置文件中增加了注册中心的配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
spring:
application:
name: dubbo-registry-zookeeper-consumer-sample

demo:
service:
version: 1.0.0

embedded:
zookeeper:
port: 2181

dubbo:
registry:
address: zookeeper://127.0.0.1:${embedded.zookeeper.port}
file: ${user.home}/dubbo-cache/${spring.application.name}/dubbo.cache

启动类中 @DubboReference 只配置了服务版本,因为之前是通过客户端直连,所以需要指定客户端地址,此时使用注册中心,只须关注服务接口即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@EnableAutoConfiguration
public class DubboRegistryZooKeeperConsumerBootstrap {

private final Logger logger = LoggerFactory.getLogger(getClass());

@DubboReference(version = "${demo.service.version}")
private DemoService demoService;

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

@Bean
public ApplicationRunner runner() {
return args -> logger.info(demoService.sayHello("mercyblitz"));
}
}

启动并观察控制台。

(2)Nacos

内容基本一致,暂略。

3.5 服务自省 service-introspection-samples

(1)provider

增加了 registry-type

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# Spring boot application
spring.application.name=dubbo-zookeeper-service-introspection-provider-sample

# Zookeeper Server Configuration
zookeeper.server.host = 127.0.0.1
zookeeper.server.port = 2181

# Dubbo Spring Boot Externalization Configuration
## Base packages to scan Dubbo Component: @org.apache.dubbo.config.annotation.Service
dubbo.scan.base-packages=org.apache.dubbo.spring.boot.sample.provider.service

## The default value of dubbo.application.name is ${spring.application.name}
## dubbo.application.name=${spring.application.name}

## Dubbo Protocol
dubbo.protocol.name=dubbo
## Random port
dubbo.protocol.port=-1

## Dubbo Registry
dubbo.registry.address=zookeeper://${zookeeper.server.host}:${zookeeper.server.port}?registry-type=service

## DemoService version
demo.service.version=1.0.0

服务实现和启动类没有变化。

(2)consumer

增加了 registry-type

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
spring:
application:
name: dubbo-zookeeper-service-introspection-consumer-sample

demo:
service:
version: 1.0.0

embedded:
zookeeper:
port: 2181

dubbo:
registry:
address: zookeeper://127.0.0.1:${embedded.zookeeper.port}/?registry-type=service
file: ${user.home}/dubbo-cache/${spring.application.name}/dubbo.cache

启动类无变更。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@EnableAutoConfiguration
public class DubboZooKeeperServiceIntrospectionConsumerBootstrap {

private final Logger logger = LoggerFactory.getLogger(getClass());

@DubboReference(version = "${demo.service.version}")
private DemoService demoService;

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

@Bean
public ApplicationRunner runner() {
return args -> logger.info(demoService.sayHello("mercyblitz"));
}
}

3.6 servlet容器 servlet-container-samples

服务提供者也开放了服务的直接访问接口。

(1)provider

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Spring boot application
spring.application.name=dubbo-spring-boot-servlet-container-provider-sample
# Base packages to scan Dubbo Component: @org.apache.dubbo.config.annotation.Service
dubbo.scan.base-packages=org.apache.dubbo.spring.boot.sample.provider.service

# Dubbo Application

# Dubbo Protocol
dubbo.protocol.name=dubbo
dubbo.protocol.port=23456

## Dubbo Registry
dubbo.registry.address=N/A

## DemoService version
demo.service.version=1.0.0

服务实现无变更。

启动类增加了服务接口,配置了 WebApplicationType ,表示应用是基于Servlet的Web程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@EnableAutoConfiguration
@RestController
public class DubboServletContainerProviderBootstrap extends SpringBootServletInitializer {

@Autowired
private DemoService demoService;

@RequestMapping("/say/{name}")
public String say(@PathVariable String name) {
return demoService.sayHello(name);
}

@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(getClass());
}

public static void main(String[] args) { // Run as the generic Spring Boot Web(Servlet) Application
SpringApplication application = new SpringApplication(DubboServletContainerProviderBootstrap.class);
application.setWebApplicationType(WebApplicationType.SERVLET);
application.run(args);
}
}

(2)consumer

配置无变化。

1
2
3
4
5
6
7
8
spring:
application:
name: dubbo-servlet-container-consumer-sample

demo:
service:
version: 1.0.0
url: dubbo://127.0.0.1:23456

启动类开放GET访问接口,通过读取配置文件地址直接远程调用客户端(服务提供者)服务实现,同样设置了 WebApplicationType

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@EnableAutoConfiguration
@RestController
public class DubboServletContainerConsumerBootstrap extends SpringBootServletInitializer {

@DubboReference(version = "${demo.service.version}", url = "${demo.service.url}")
private DemoService demoService;

@RequestMapping(value = "/say-hello", method = GET)
public String sayHello(@RequestParam String name) {
return demoService.sayHello(name);
}

@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(getClass());
}

public static void main(String[] args) { // Run as the generic Spring Boot Web(Servlet) Application
SpringApplication application = new SpringApplication(DubboServletContainerConsumerBootstrap.class);
application.setWebApplicationType(WebApplicationType.SERVLET);
application.run(args);
}
}

参考:

🔗 Dubbo官网-文档

🔗 尚硅谷-dubbo