什么是Dubbo
简介
Apache Dubbo 是一款 RPC 服务开发框架,用于解决微服务架构下的服务治理与通信问题,官方提供了 Java、Golang 等多语言 SDK 实现。使用 Dubbo 开发的微服务原生具备相互之间的远程地址发现与通信能力, 利用 Dubbo 提供的丰富服务治理特性,可以实现诸如服务发现、负载均衡、流量调度等服务治理诉求。Dubbo 被设计为高度可扩展,用户可以方便的实现流量拦截、选址的各种定制逻辑。
在云原生时代,Dubbo 相继衍生出了 Dubbo3、Proxyless Mesh 等架构与解决方案,在易用性、超大规模微服务实践、云原生基础设施适配、安全性等几大方向上进行了全面升级。
架构图
以上是Dubbo的工作原理图,从抽象架构上分为两层,分别是服务治理抽象控制面和Dubbo数据面。
服务治理控制面:对Dubbo治理体系的抽象表达,包含注册中心、流量管控策略、Dubbo Admin 控制台等。
Dubbo数据面:代表集群部署的所有Dubbo进程。Dubbo定义了微服务应用开发与调用规范并负责完成数据传输的编码工作。
基于spring boot的第一个dubbo项目
项目框架搭建
一个Dubbo Demo项目需要定义三个东西,分别是定义接口的interface,服务提供者provider和服务消费者consumer。所以我在项目中新建了三个子模块分别完成上述功能。
依赖引入
我在父项目中定义了一些公共的依赖,指定了整个项目使用的spring boot和dubbo版本,避免由于版本不一致带来的问题。同时,demo使用了zookeeper作为注册中心,所以在项目中还引入了对zookeeper客户端的依赖。父项目pom关键部分如下:
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.6.13</spring-boot.version>
<dubbo.version>3.2.0-beta.4</dubbo.version>
</properties>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
</dependency>
</dependencies>
<!-- dependencyManagement 只是声明依赖的版本,不会引入实际的依赖-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- dubbo-->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-bom</artifactId>
<version>${dubbo.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--注册中心依赖,此处使用zookeeper-->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-dependencies-zookeeper-curator5</artifactId>
<version>${dubbo.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
在demo中,针对不同的子模块我们引入了大致相同的依赖,其pom关键部分如下。此外consumer和provider因需要使用interface提供的接口,所以在consumer和provier中还需加入对interface模块的依赖。
<parent>
<artifactId>dubbo-demo</artifactId>
<groupId>com.example</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>dubbo-provider</artifactId>
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>dubbo-interface</artifactId>
<version>${project.parent.version}</version>
</dependency>
<!-- dubbo -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-dependencies-zookeeper-curator5</artifactId>
<exclusions>
<exclusion>
<artifactId>slf4j-reload4j</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- spring boot starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>
编写demo代码
编写interface
本模块较为简单,只是简单定义了一个DemoService接口,该接口定义了一个 sayHi(String name)方法用于打招呼。
public interface DemoService {
String sayHi(String name);
}
编写provider
provider作为服务提供者需要实现两个目的:实现接口与注册服务
实现接口
demo中对DemoService接口进行了简单实现,实现代码如下。此实现的sayHi(String name)方法接收一个name并返回打招呼信息。
@DubboService
public class DemoServiceImpl implements DemoService {
@Override
public String sayHi(String name) {
return String.format("Hi %s, I am interface service provider", name);
}
}
注册服务
- 启用Dubbo支持: 在SpringBoot中,使用@EnableDubbo注解可以启用Dubbo支持、自动配置和注解扫描,该注解可以放在主类上。
@SpringBootApplication
@EnableDubbo
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class, args);
}
}
- 定义需要注册的接口: 在Dubbo3中,使用@DubboService注解标注的方法将被注册到注册中心中。(在实现接口中已标注)
- 配置Dubbo参数(每个参数的注意事项已标注在配置文件上),注意一个在这篇文章中提到的坑,使用虚机或其他远端环境部署zookeeper时,连接zookeeper可能超时导致服务启动失败,此时需修改如下的两个字段。
dubbo:
application:
name: dubbo-provider #服务名称
qos-port: 20780 #运维命令服务端口, 可不配置
protocol:
name: dubbo #RPC 协议
port: 20880 #RPC 端口 -1代表使用默认端口20880
registry:
address: zookeeper://${zookeeper_ip}:${zookeeper_service_port} #注册中心类型和地址
timeout: 250000 #连接注册中心超时时间,单位毫秒
parameters:
blockUntilConnectedWait: 250 #连接注册中心超时时间,单位秒, 默认10
subscribe: false #只注册,不订阅
编写consumer
consumer作为服务消费者需要实现两个目的:订阅服务和消费接口
订阅服务
- 启用Dubbo支持:
@SpringBootApplication
@EnableDubbo
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
- 使用代理接口:使用@DubboReference注解可以将依赖标注为使用Dubbo代理,Dubbo会自动从Provider处寻找实现。
@Component
public class DemoServiceTask implements CommandLineRunner {
// CommandLineRunner接口, 实现此接口的类的run方法会在springboot context加载完成后被调用。
@DubboReference(loadbalance = "roundrobin")
private DemoService demoService;
@Override
public void run(String... args) throws Exception {
for (int i = 0; i < 10; i++) {
String name = "test" + i;
String sayHi = demoService.sayHi(name);
System.out.println(sayHi);
}
}
}
- 配置参数:和provider类似,consumer也需要配置Dubbo参数且配置大同小异。consumer需要订阅服务所以dubbo.registry.sybscribe不能配置为false。
dubbo:
application:
name: dubbo-consumer
protocol:
name: dubbo
port: 20870
registry:
address: zookeeper://${zookeeper_ip}:${zookeeper_service_port}
register: false # 只订阅服务,不注册
timeout: 250000
parameters:
blockUntilConnectedWait: 250
消费接口
在订阅服务.使用代理接口处已展示消费接口使用的代码,该Task实现了CommandLineRunner,其run(String... args)方法将会在SpringBoot加载完上下文后执行。该代码将会调用10次provider提供的sayHi(String name)实现,如果有多个provider,则采用轮询机制作为负载均衡。
测试结果
各模块运行结果
- provider出现如下标识,代表provider启动成功。
- consumer启动成功并成功调用provider的服务
- provider处也有相关日志
zookeeper节点分析
在上面的demo中使用了zookeeper作为注册中心,那么dubbo数据具体在zookeeper中怎么组织的呢?本例使用了ZooInspector查看了zookeeper的节点信息,截图如下。
/dubbo
:组名称,默认为dubbo,如果在项目中配置dubbo.registry.group中修改为其他值,那么此处节点名称也为其他值。
/dubbo/com.example.dinterface.DemonService
:接口节点,下面包含了接口的配置,路由,消费者列表和提供者列表,其中提供者列表以URL的形式给出,本例中的URL信息如下:
dubbo://172.25.16.39:20880/com.example.dinterface.DemoService?anyhost=true&application=dubbo-provider&background=false&deprecated=false&dubbo=2.0.2&dynamic=true&executor-management-mode=default&file.cache=true&generic=false&interface=com.example.dinterface.DemoService&methods=sayHi&pid=17664&prefer.serialization=fastjson2,hessian2&release=3.2.0-beta.4&service-name-mapping=true&side=provider×tamp=1721965937083
/dubbo/mapping
:服务映射,将接口映射为服务(provider)名称
/dubbo/metadata
: 关于接口,providers和consumers的元信息
/dubbo/config
: 和dubbo动态配置相关
/dubbo/org.apache.dubbo.meta.MetadataService
: dubbo自带的元数据服务相关信息
/service
: 用于组织和管理服务提供者的注册信息
应用级注册
在dubbo中,服务注册是以接口为单位的,当服务者提供者数量或接口数量过多时对于注册中心压力较大。所以在dubbo3中引入以应用为单位的服务注册。
在Demo中我们定义了一个app-provider模块,该模块设置dubbo.registry.register-mode为instance标识该模块以应用为单位在注册中心进行注册。该模块也实现了DemoService的sayHi(String name)方法,实现的代码如下
@DubboService
public class DemoServiceImpl implements DemoService {
@Override
public String sayHi(String name) {
return String.format("Hello %s. I am app service provider", name);
}
}
启动app-provider模块,原本运行的comsumer模块打印日志,从注册中心获得通知,得到了新的服务信息。
zookeeper节点更新为以下情况,
dubbo3默认情况下会同时以应用级和接口级进行注册(如 provider)用于兼容和过渡,而我们的app-provider指定了只进行应用级注册所以/service/dubbo-app-provider
项目新增一个节点而/dubbo/com.example.dinterface.DemoService/providers
下则没有新增提供者。此外/dubbo/metadata/com.example.dinterface.DemonService/provider
下页新增dubbo-app-provider服务提供者用于标识该服务实现了com.example.dinterface.DemoService接口,这些元数据是通过MetaDataService获取的。
负载均衡
当集群中存在多个provider都实现并注册了同一接口时,客户端可根据配置来决定自己想要优先使用哪个provider提供的服务,其中一个配置便是负载均衡(loadBalance)。在上面的consumer实现中已经指定了调用DemoService的负载均衡策略为轮询(roundrobin)。接下来基于上面的两个providers,Demo尝试十次调用结果如下。