Dubbo |ˈdʌbəʊ| 分布式初体验 —— 第一章 Dubbo 基础知识
1. 分布式基础知识
1.1. 什么是分布式系统
《分布式系统原理与范型》定义:
分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像单个相关系统
分布式系统(Distributed system)就是建立在网络之上的软件系统。
随着互联网的发展,网站应用的规模不断扩大,常规 的垂直应用的架构已经无法应对,分布式服务架构以及流动计算架构势在必行,亟需一个 治理系统 来确保系统架构有条不紊地演进。
1.2. 发展演变
单一应用架构:
当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。
垂直应用架构:
当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的 Web 框架(MVC)是关键。
分布式服务架构:
当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式服务框架(RPC)是关键。
流动计算架构:
当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键。
1.3. RPC
1.3.1. 什么是 RPC
RPC(Remote Procedure Call) 是指远程过程调用,是一种进程间通信方式,它是一种技术思想,而不是规范。它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或者是函数,而不用程序员显式编码这个远程调用的细节,即程序员无论是调用本地还是远程函数,本质上编写的调用代码基本相同
1.3.2. RPC 基本原理
sequenceDiagram participant Client participant Client Stub participant Server Stub participant Server Client ->> Client Stub: 1. 客户端调用 Client Stub ->> Client Stub: 2. 序列化 Client Stub ->> Server Stub: 3. 发送消息 Server Stub ->> Server Stub: 4. 反序列化 Server Stub ->> Server: 5. 调用本地服务 Server ->> Server: 6. 服务处理 Server -->> Server Stub: 7. 返回处理结果 Server Stub ->> Server Stub: 8. 将结果序列化 Server Stub -->> Client Stub: 9. 返回消息 Client Stub ->> Client Stub: 10. 反序列化 Client Stub -->> Client: 11. 返回调用结果
RPC 框架的两个核心模块:
- 通信
- 序列化/反序列化
目前市面上 RPC 框架有很多种,例如:Dubbo,gRPC、Thrift、HSF(High Speed Service Framework)
2. Dubbo 核心概念
2.1. Dubbo 简介
Apache Dubbo |ˈdʌbəʊ| 是一款高性能、轻量级的开源 Java RPC 服务框架,提供了六大核心能力:面向接口代理的高性能RPC调用,智能容错和负载均衡,服务自动注册和发现,高度可扩展能力,运行期流量调度,可视化的服务治理与运维。
源码地址:https://github.com/apache/dubbo
2.2. Dubbo 架构:

3. Dubbo 环境搭建
3.1. 安装 Zookeeper(使用 Docker 环境)
在 DockerHub 上面下载 Zookeeper 镜像包
docker pull zookeeper
启动 Zookeeper 镜像并暴露
2181
端口docker run --name zookeeper-01 -p 2181:2181 --restart always -d zookeeper
进入 Docker 环境启用 Zookeeper 客户端
docker exec -it zookeeper bash
./bin/zkCli.sh
3.2. 安装 Dubbo Admin 管理界面
将源码下载下来后,进入
dubbo-admin-develop/dubbo-admin-server/src/main/resources/application.properties
文件中修改admin.registry.address
,admin.config-center
为自己注册中心的地址Maven 构建包,然后直接执行生成的 Jar 包,或者是直接在 IDE 中运行(注意⚠️:JDK 版本一定要选择 1.8,否则会运行错误!)
浏览器打开
127.0.0.1:8080
如果进入此页面则表示安装成功(默认登录用户名和密码均为root
,可以在前面的 properties 配置文件中修改)
4. Dubbo HelloWorld
4.1 提出需求
在某个电商系统中,订单服务需要调用用户服务来获取某个用户的所有地址
我们现在需要创建两个服务模块进行测试
模块 | 功能 |
---|---|
订单服务 Web 模块 | 创建订单等 |
用户服务 Service 模块 | 查询用户地址等 |
测试结果:订单服务 Web 模块在 A 服务器,用户服务模块在 B 服务器,A 可以远程调用 B 的功能。
4.2. 工程架构
根据 Dubbo《服务化最佳实践》
4.2.1. 分包
建议将服务接口,服务模型,服务异常等均放在 API 包中,因为服务模型及异常也是 API 的一部分,同时,这样做也符合分包的规则:重用发布等价原则(REP),共同重用原则(CRP)。
如果需要,也可以考虑在 API 包中放置一份 Spring 的引用配置,这样使用方只需要在 Spring 加载过程中引用此配置即可,配置建议放在模块的包目录下,以免冲突,如:com.yorname.mall.xxx.dubbo-reference.xml
4.2.2. 粒度
服务接口尽可能大粒度,每个服务方法应代表一个功能,而不是某功能的一个步骤,否则将面临分布式事务问题,Dubbo 暂未提供分布式事务支持。
服务接口建议以业务场景为单位划分,并对相近业务做抽象,防止接口数量爆炸。
不建议使用过于抽象的通用接口,如:Map query(Map)
,这样的接口没有明确语义,会给后期维护带来不便。
4.2.3. 版本
每个接口都应定义版本号,为后续不兼容升级提供可能,如: <dubbo:service interface="com.xxx.XxxService" version="1.0" />
。
建议使用两位版本号,因为第三位版本号通常表示兼容升级,只有不兼容时才需要变更服务版本。
当不兼容时,先升级一半提供者为新版本,再将消费者全部升为新版本,然后将剩下的一半提供者升为新版本。
4.2.4. 兼容性
服务接口增加方法,或服务模型增加字段,可向后兼容,删除方法或删除字段,将不兼容,枚举类型新增字段也不兼容,需通过变更版本号升级。
各协议的兼容性不同,参见:服务协议
4.3. 创建模块
mall-interface
:用于存放提供者和消费者的所有的服务接口,JavaBean 实体和异常
user-service-provider
:用于存放服务提供者的所有服务实现
order-service-consumer
:用于存放服务消费者的所有服务实现
4.4 使用 Dubbo
将服务提供者注册到注册中心(暴露服务)
导入 Dubbo 依赖以及操作 Zookeeper 的客户端
pom.xml
<!-- Import dubbo --> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo</artifactId> <version>3.0.5</version> </dependency> <!-- The registry uses zookeeper and introduces a client that operates zookeeper --> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-dependencies-zookeeper</artifactId> <version>3.0.5</version> <type>pom</type> </dependency> <dependency> <groupId>org.javassist</groupId> <artifactId>javassist</artifactId> <version>3.28.0-GA</version> </dependency>
配置服务提供者
指定当前服务/应用的名字(同样的服务名字相同,不要和别的服务同名)
指定注册中心的位置
指定通信规则(通信协议,通信端口)
暴露服务(
ref
:指向服务真正实现的对象)
provider.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://dubbo.apache.org/schema/dubbo" xmlns="http://www.springframework.org/schema/beans" 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://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 1. Specify the name of the current service/APP (The same service has the same name. Do not have the same name as other services)--> <dubbo:application name="user-service-provider"/> <!-- 2. Specify the location of the registry --> <!--<dubbo:registry address="zookeeper://127.0.0.1:2181"></dubbo:registry>--> <dubbo:registry protocol="zookeeper" address="127.0.0.1:2181"/> <!-- 3. Specify communication rules (communication protocol, communication port) --> <dubbo:protocol name="dubbo" port="20080" /> <!-- 4. Exposure services --> <dubbo:service interface="com.gregperlinli.mail.service.UserService" ref="userService"/> <!-- Service implementation --> <bean id="userService" class="com.gregperlinli.mail.service.impl.UserServiceImpl" /> <!-- Set connection timeout --> <dubbo:config-center timeout="10000" /> <!-- Connect to the monitoring center --> <dubbo:monitor protocol="registry" /> </beans>
加载 Spring 配置
com.yourname.mall.MainApplication
/** * @author gregPerlinLi * @since 2022-01-24 */ public class MainApplication { public static void main(String[] args) throws IOException { ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("provider.xml"); app.start(); System.in.read(); } }
- 启动后,在 Dubbo Admin 中可以看到当前运行的提供者
让服务消费者去注册中心订阅服务提供者的服务地址
导入 Dubbo 依赖以及操作 Zookeeper 的客户端
pom.xml
<!-- Import dubbo --> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo</artifactId> <version>3.0.5</version> </dependency> <!-- The registry uses zookeeper and introduces a client that operates zookeeper --> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-dependencies-zookeeper</artifactId> <version>3.0.5</version> <type>pom</type> </dependency> <dependency> <groupId>org.javassist</groupId> <artifactId>javassist</artifactId> <version>3.28.0-GA</version> </dependency>
配置服务消费者
consumer.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://dubbo.apache.org/schema/dubbo" xmlns="http://www.springframework.org/schema/beans" 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://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- Set package scan --> <context:component-scan base-package="com.gregperlinli.mall.service.impl" /> <dubbo:application name="order-service-consumer" /> <dubbo:registry address="zookeeper://127.0.0.1:2181" /> <!-- An excuse for declaring a remote service that needs to be invoked, generate remote service proxy --> <dubbo:reference interface="com.gregperlinli.mall.service.UserService" id="userService" /> <dubbo:config-center timeout="10000" /> <dubbo:monitor protocol="registry" /> </beans>
注入远程服务
com.yourname.mall.MainApplication
/** * * @author gregPerlinLi * @since 2022-01-24 */ @Service public class OrderServiceImpl implements OrderService { @Autowired UserService userService; @Override public void initOrder(String userId) { // 1. Query the receiving address of the user List<UserAddress> addressList = userService.getUserAddressList(userId); System.out.println(addressList); } }
加载 Spring 配置
com.yourname.mall.MainApplication
/** * @author gregPerlinLi * @since 2022-01-24 */ public class MainApplication { public static void main(String[] args) throws IOException { ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("consumer.xml"); OrderService orderService = app.getBean(OrderService.class); orderService.initOrder("1"); System.in.read(); } }
启动后,在 Dubbo Admin 中可以看到当前运行的消费者
5. 监控中心
5.1. Dubbo Admin
图形化的服务管理界面;安装时需要指定注册中心地址,即可从注册中心中获取到所有的提供者/消费者进行配置管理
新版:
旧版:
5.2. Dubbo Monitor Simple
一个简单的 Dubbo 控制中心(Dubbo3.0 后已停止更新)
6. Dubbo 与 SpringBoot 结合
6.1. 将服务提供者注册到注册中心
引入依赖:
导入
dubbo-spring-boot-starter
版本要求:
Versions Java SpringBoot Dubbo 0.2.0
1.8+
2.0.x
2.6.2+
0.1.1
1.7+
1.5.x
2.6.2+
导入 Dubbo 的其他依赖
pom.xml
<dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-spring-boot-starter</artifactId> <version>3.0.5</version> </dependency> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-dependencies-zookeeper</artifactId> <version>3.0.5</version> <type>pom</type> </dependency>
配置 Dubbo
application.yaml
dubbo: application: name: user-service-provider registry: protocol: zookeeper address: 127.0.0.1:2181 protocol: name: dubbo port: 11451 config-center: timeout: 10000 monitor: protocol: registry
暴露服务
com.yourname.mall.service.impl.UserServiceImpl
/** * {@code @DubboService}: Exposure services * * @author gregPerlinLi * @since 2022-01-24 */ @DubboService @Service public class UserServiceImpl implements UserService { @Override public List<UserAddress> getUserAddressList(String userId) { UserAddress address1 = new UserAddress(1, "Address1", "1", "XiaoMing", "1234567890", "Y"); UserAddress address2 = new UserAddress(2, "Address2", "2", "XiaoHong", "9876543210", "Y"); return Arrays.asList(address1, address2); } }
在主程序下启用基于注解的 Dubbo 功能
com.yourname.mall.BootUserServiceProviderApplication
/** * {@code @EnableDubbo}: Enable annotation based Dubbo functionality * * @author gregperlinli */ @SpringBootApplication @EnableDubbo public class BootUserServiceProviderApplication { public static void main(String[] args) { SpringApplication.run(BootUserServiceProviderApplication.class, args); } }
启动主程序
6.2. 让服务消费者去注册中心订阅服务提供者的服务地址
引入依赖
pom.xml
<dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-spring-boot-starter</artifactId> <version>3.0.5</version> </dependency> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-dependencies-zookeeper</artifactId> <version>3.0.5</version> <type>pom</type> </dependency>
配置 Dubbo
application.yaml
server: port: 8060 dubbo: application: name: boot-order-service-consumer registry: address: zookeeper://127.0.0.1:2181 config-center: timeout: 10000 monitor: protocol: registry
声明需要调用的远程服务接口并注入
com.yourname.mall.service.impl.OrderServiceImpl
/** * 1. Register the service provider with the registry (Exposure services) * 1. Import Dubbo dependency and the client (cursor) that operates zookeeper * 2. Configure service providers * 2. Let the service consumer go to the registry to subscribe to the service address of the service provider * * @author gregPerlinLi * @since 2022-01-24 */ @Service public class OrderServiceImpl implements OrderService { @DubboReference UserService userService; @Override public List<UserAddress> initOrder(String userId) { System.out.println("User id: " + userId); // 1. Query the receiving address of the user List<UserAddress> addressList = userService.getUserAddressList(userId); for (UserAddress userAddress : addressList) { System.out.println(userAddress.getUserAddress()); } return addressList; } }
com.yourname.mall.controller.OrderController
/** * @author gregPerlinLi * @since 2022-01-24 */ @Controller public class OrderController { @Autowired OrderService orderService; @ResponseBody @RequestMapping(value = "/init-order") public List<UserAddress> initOrder(@RequestParam("uid") String userId) { return orderService.initOrder(userId); } }
在主程序下启用基于注解的 Dubbo 功能
com.gregperlinli.mall.BootOrderServiceConsumerApplication
/** * @author gregperlinli */ @SpringBootApplication @EnableDubbo public class BootOrderServiceConsumerApplication { public static void main(String[] args) { SpringApplication.run(BootOrderServiceConsumerApplication.class, args); } }
启动主程序