深入理解Dubbo原理系列(一)-Dubbo简介和基本使用
一. Dubbo简介
Dubbo是阿里SOA服务化治理方案的一个核心框架,下面是Dubbo的一个架构图:
1.1 Dubbo架构
角色分配如下:
- Provider:暴露服务的服务提供方。
- Consumer:调用远程服务的服务消费方。
- Registry:服务注册与发现的注册中心。
- Monitor:统计服务的调用次数和调用时间的监控中心。
- Container:服务运行的容器。
线条含义如下:
- 虚线:异步访问。
- 紫色虚线:在启动时就完成的功能。
- 青色虚线:程序运行时执行的功能。
- 实线:同步访问。
图中流程如下:
- Provider在启动的时候会向注册中心把自己的元数据注册上去,例如服务的IP、端口。
- Consumer在启动的时候从注册中心订阅服务提供方的元数据(第一次订阅则会拉取全量的数据)。
- 注册中心中若发生了数据的变更,则会推送给订阅的Consumer。
- Consumer可以发起RPC调用,在RPC调用的前后会向监控中心上报统计信息(并发数、调用的接口等)。
1.2 Dubbo的特性
从官网的文档来看,Dubbo的特性是非常丰富的,比如:
- 面向接口代理的高性能RPC调用。
提供高性能的基于代理的远程调用能力,服务以接口为粒度。
- 服务自动注册与发现。
支持多种注册中心服务,服务实例上下线实时的感知。
- 运行期流量的调度。
内置条件、脚本等路由策略,可以通过配置不同的路由规则,实现灰度发布、同机房优先等功能。
- 智能的负载均衡。
内置多种负载均衡策略,智能感知下游节点的健康状况,显著减少调用延迟,提高系统吞吐量。
- 高度可扩展能力。
遵循微内核+插件的设计思想,所有核心能力,例如:Protocol、Transport、Serialization。都被设计为扩展点,平等对待内置的实现和第三方实现。
- 可视化的服务治理和运维。
提供丰富的服务治理、运维工具。随时查询服务的元数据、服务健康状态和调用统计。
1.3 Dubbo总体分层
Dubbo总体上可以分成三层(细分则10层):
- 业务层(Biz):Service。
- RPC层:Config、Proxy、Registry、Cluster、Monitor、Protocol。
- Remote层:Exchange、Transport、Serialize。
如下:
表面上(即对于我们使用者而言)可以分为:
API层:包括Service和Config两层,因为我们使用者在用Dubbo的时候,只需要进行相关的配置和编写业务代码即可。
SPI层:剩余的所有层结合在一起,主要提供给扩展者使用,即用户可以基于Dubbo做一个二次开发,扩展功能。
具体的每一层做什么,则在下文一一列举。
1.3.1 Dubbo核心组件
上文的每一层其实就是一个个组件,是这些不同的组件构成了整个Dubbo框架。
层次名 | 作用 |
---|---|
Service | 业务层,负责业务代码的编写 |
Config | 配置层,负责暴露的服务配置和引用的服务配置,初始化配置信息,负责管理整个Dubbo的配置 |
Proxy | 服务代理层,负责自动做远程调用并返回结果,无论是生产者还是消费者,都会生成一个代理类 |
Registry | 注册层,负责Dubbo的服务注册和发现。服务的上下线由注册中心感知并且通知给订阅方 |
Cluster | 集群容错层,负责调用失败时的容错策略,选择具体调用节点时的负载均衡策略,特殊调用路径的路由策略 |
Monitor | 监控层,负责监控统计调用次数和调用时间等信息 |
Protocol | 远程调用层,封装RPC调用的具体过程,是Invoker暴露(服务暴露)和引用(引用一个服务到本地)的主功能入口。 |
Exchange | 信息交换层,建立Request-Response模型,封装请求的响应模式 |
Transport | 网络传输层,将网络传输抽象为统一的接口 |
Serialize | 序列化层,若数据要通过网络进行发送,那么需要先做序列化,变成二进制流 |
注1(集群容错层):
容错策略:失败重试、快速失败等。
负载均衡策略:随机、一致Hash等。
路由策略:某个消费者只会调用某个IP的生产者等
注2(远程调用层):
1.远程调用层负责管理Invoker的整个生命周期。
2.Invoker是Dubbo的一个核心模型,框架中所有的其他模型都向他靠拢或者转换成他,他代表一个可执行体、
3.允许向Invoker发起一个Invoke调用,Invoker可能是执行一个本地的接口实现、远程实现、集群实现。
1.3.2 Dubbo总体调用过程
首先说下几个重要组件的含义:
-
Proxy组件:Dubbo中需要引用一个接口就可以调用远程的服务。而具体实施时Dubbo为我们生成了一个代理类,调用到方法其实是Proxy组件生成的代理方法,它会自动的发起远程/本地的调用,并返回结果(该过程对用户而言透明)。
-
Protocol组件:是对数据格式统一的一种规定。可以把我们对接口的配置根据不同的协议转换成不同的Invoker对象。
-
Exporter组件:用于暴露到注册中心的对象,他的内部属性中持有Invoker对象。即Invoker的一个二次封装。
-
Registry组件:将Exporter注册到注册中心上。
因此先来看看服务暴露的过程:
- 服务端启动时,初始化服务实例,通过Proxy组件调用具体协议(Protocol),将服务端要暴露的接口封装成Invoker(
AbstractProxyInvoker
对象),然后转换成Exporter。 - 框架打开服务端口等并记录服务实例到内存中。
- 通过Registry将服务元数据注册到注册中心中。
再来看看服务调用的过程:
- 前面说过,调用时我们是调用了Proxy的代理方法,而Proxy持有一个Invoker对象。
- 然后出发invoke调用,调用过程中,则需要使用Cluster(负责容错)。
- Cluster在调用之前会通过
Directory
获取所有可以调用的远程服务Invoker列表。 - 若用户配置了路由规则,那么根据路由规则将Invoker列表进行过滤。
- 通过
LoadBalance
方法做负载均衡,最终选出一个可以调用的Invoker,而该Invoker在调用之前又会经过一个消费者端过滤器链(处理上下文信息、限流、计数等)。 - 使用Client进行数据传输,例如Netty Client等。其中会先进行私有协议的构造(调用
Codec
接口),再对数据包进行序列化处理,然后传输到服务提供者端。 - 服务提供者收到数据包后,同样使用Codec接口进行相应的数据处理,处理后再做反序列化处理。
- 随后该Request请求被分配到线程池中进行处理,Server根据请求查找对应的
Exporter
(内部封装了Invoker),并同样经历服务者端过滤器链,最后调用方法得到最终的结果后,原路将结果返回。
二. Dubbo的基本使用
1.首先看下项目的大致结构:
2.父类dubbo-demo的pom文件:
<modules>
<module>demo-interface</module>
<module>demo-provider-xml</module>
<module>demo-consumer-xml</module>
<module>demo-provider-api</module>
<module>demo-consumer-api</module>
<module>demo-consumer-annotate</module>
<module>demo-provider-annotate</module>
</modules>
<properties>
<source.level>1.8</source.level>
<target.level>1.8</target.level>
<spring.version>4.3.16.RELEASE</spring.version>
<dubbo.version>2.6.3</dubbo.version>
<dubbo.rpc.version>2.6.3</dubbo.rpc.version>
<curator.version>2.12.0</curator.version>
<validation-api.version>1.1.0.Final</validation-api.version>
<hibernate-validator.version>4.2.0.Final</hibernate-validator.version>
<resteasy.version>3.0.19.Final</resteasy.version>
<curator-client.version>2.12.0</curator-client.version>
<swagger.version>1.5.19</swagger.version>
<spring-boot.version>1.5.13.RELEASE</spring-boot.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId>
<version>${spring.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>${dubbo.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo-rpc-rest</artifactId>
<version>${dubbo.rpc.version}</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>${curator.version}</version>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>${validation-api.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>${hibernate-validator.version}</version>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jackson-provider</artifactId>
<version>${resteasy.version}</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-client</artifactId>
<version>${curator-client.version}</version>
</dependency>
<!-- swagger -->
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>${swagger.version}</version>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-jaxrs</artifactId>
<version>${swagger.version}</version>
</dependency>
<dependency>
<!-- Import dependency management from Spring Boot -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<exclusions>
<exclusion>
<groupId>io.netty</groupId>
<artifactId>netty</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
</dependency>
</dependencies>
3.提供的接口如下:
因为消费者生产者都需要到调用到这个UserService
接口,因此将它单独做成一个maven项目,让两方的pom文件都引入即可。(也可以打成jar的形式)
public interface UserService {
String getName(String name);
}
2.1 基于XML实现远程调用
2.1.1 搭建生产者-XML
1.搭建生产者demo-provider-xml
,项目结构如下:
2.pom文件如下,引入接口项目就行,所需的依赖在其父项目dubbo-demo
中已经添加了:
<dependencies>
<dependency>
<groupId>org.example</groupId>
<artifactId>demo-interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
3.接口的实现类:
public class UserServiceImpl implements UserService {
@Override
public String getName(String name) {
String now = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
System.out.println(now + ":" + name);
return name;
}
}
4.启动类:
public class XmlProvider {
public static void main(String[] args) throws Exception {
// #1 指定服务暴露配置文件
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"provider.xml"});
// #2 启动spring容器并暴露服务
context.start();
System.in.read();
}
}
5.配置文件provider.xml
(注意改你的zookeeper地址):
<?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"
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">
<!-- 服务提供方应用名称, 方便用于依赖跟踪 -->
<dubbo:application name="demo-provider-xml"/>
<!-- 使用zookeeper作为注册中心,注意改成你自己的IP地址 -->
<dubbo:registry address="zookeeper://192.168.237.130:2181"/>
<!-- 只用Dubbo协议并且指定监听端口 20880 -->
<dubbo:protocol name="dubbo" port="20880"/>
<!-- 通过xml方式配置为bean, 让spring托管和实例化 -->
<bean id="userService" class="com.pro.impl.UserServiceImpl"/>
<!-- 声明服务暴露的接口,并暴露服务 -->
<dubbo:service interface="com.pro.service.UserService" ref="userService"/>
</beans>
2.1.2 搭建消费者-XML
1.搭建消费者demo-consumer-xml
,项目结构如下:
2.pom文件如上:
<dependencies>
<dependency>
<groupId>org.example</groupId>
<artifactId>demo-interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
3.配置文件consumer.xml
(注意改你的zookeeper地址):
<?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"
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">
<!-- 服务消费方应用名称, 方便用于依赖跟踪 -->
<dubbo:application name="demo-consumer-xml"/>
<!-- 使用zookeeper作为注册中心,注意改成你自己的IP地址 -->
<dubbo:registry address="zookeeper://192.168.237.130:2181"/>
<!-- 指定要消费的服务 -->
<dubbo:reference id="userService" check="false" interface="com.pro.service.UserService"/>
</beans>
4.启动类:
public class XmlConsumer {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"consumer.xml"});
context.start();
UserService userService = (UserService) context.getBean("userService"); // get remote service proxy
String status = userService.getName("张三");
System.out.println("远程调用结果:" + status);
}
}
最后,先启动好生产者provider-xml
,再启动消费者consumer-xml
结果如下(服务端):
消费端:
2.2 基于注解实现远程调用
pom文件与上文都一致(2.3节不再重复说明)
2.2.1 搭建生产者-annotate
1.搭建demo-provider-annotate
,项目结构:
2.实现类:
import com.pro.service.UserService;
// 注意这里不是Spring下的Service注解,是dubbo下面的Service注解。
import com.alibaba.dubbo.config.annotation.Service;
import java.text.SimpleDateFormat;
import java.util.Date;
@Service
public class UserServiceImpl implements UserService {
@Override
public String getName(String name) {
String now = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
System.out.println(now + ":" + name);
return name;
}
}
3.启动类AnnotateProvider
(注意改你的zookeeper地址):
import com.alibaba.dubbo.config.ApplicationConfig;
import com.alibaba.dubbo.config.ProtocolConfig;
import com.alibaba.dubbo.config.ProviderConfig;
import com.alibaba.dubbo.config.RegistryConfig;
import com.alibaba.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
public class AnnotateProvider {
public static void main(String[] args) throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ProviderConfiguration.class);
context.start();
System.in.read();
}
@Configuration
// #1 指定扫描服务的位置
@EnableDubbo(scanBasePackages = "com.pro")
static class ProviderConfiguration {
@Bean
public ProviderConfig providerConfig() {
return new ProviderConfig();
}
@Bean
public ApplicationConfig applicationConfig() {
ApplicationConfig applicationConfig = new ApplicationConfig();
applicationConfig.setName("demo-annotation-provider");
return applicationConfig;
}
@Bean
public RegistryConfig registryConfig() {
RegistryConfig registryConfig = new RegistryConfig();
// #2 使用zookeeper作为注册中心,同时给出注册中心ip和端口
registryConfig.setProtocol("zookeeper");
registryConfig.setAddress("192.168.237.130");
registryConfig.setPort(2181);
return registryConfig;
}
@Bean
public ProtocolConfig protocolConfig() {
ProtocolConfig protocolConfig = new ProtocolConfig();
// #3 默认服务使用dubbo协议,在20880监听服务
protocolConfig.setName("dubbo");
protocolConfig.setPort(20880);
return protocolConfig;
}
}
}
2.2.2 搭建消费者-annotate
1.搭建demo-consumer-annotate
,项目结构如下:
2.UserConsumer
类
import com.alibaba.dubbo.config.annotation.Reference;
import com.pro.service.UserService;
import org.springframework.stereotype.Component;
@Component
public class UserConsumer {
@Reference
private UserService userService;
public String getName(String name) {
return userService.getName(name);
}
}
3.启动类AnnotateConsumer
(注意改你的zookeeper地址):
import com.alibaba.dubbo.config.ApplicationConfig;
import com.alibaba.dubbo.config.ConsumerConfig;
import com.alibaba.dubbo.config.RegistryConfig;
import com.alibaba.dubbo.config.spring.context.annotation.EnableDubbo;
import com.pro.refer.UserConsumer;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
public class AnnotateConsumer {
public static void main(String[] args) {
// #1 基于注解配置初始化spring上下文
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConsumerConfiguration.class);
context.start();
// #2 发起服务调用
UserConsumer userConsumer = context.getBean(UserConsumer.class);
String name = userConsumer.getName("张三");
System.out.println("result: " + name);
}
@Configuration
// #3 指定要扫描的消费注解,会触发注入
@EnableDubbo(scanBasePackages = "com.pro")
@ComponentScan(value = {"com.pro"})
static class ConsumerConfiguration {
@Bean
public ApplicationConfig applicationConfig() {
ApplicationConfig applicationConfig = new ApplicationConfig();
applicationConfig.setName("demo-annotation-consumer");
return applicationConfig;
}
@Bean
public ConsumerConfig consumerConfig() {
return new ConsumerConfig();
}
@Bean
public RegistryConfig registryConfig() {
RegistryConfig registryConfig = new RegistryConfig();
// #4 使用zookeeper作为注册中心,同时给出注册中心ip和端口
registryConfig.setProtocol("zookeeper");
registryConfig.setAddress("192.168.237.130");
registryConfig.setPort(2181);
return registryConfig;
}
}
}
2.3 基于API实现远程调用
2.3.1 搭建生产者-api
1.项目结构:
2.实现类:
public class UserServiceImpl implements UserService {
@Override
public String getName(String name) {
String now = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
System.out.println(now + ":" + name);
return name;
}
}
3.启动类APIProvider
:
public class APIProvider {
public static void main(String[] args) throws IOException {
ServiceConfig<UserService> service = new ServiceConfig<>();
service.setApplication(new ApplicationConfig("java-demo-provider"));
service.setRegistry(new RegistryConfig("zookeeper://192.168.237.130:2181"));
service.setInterface(UserService.class);
service.setRef(new UserServiceImpl());
service.export();
System.out.println("java-echo-provider is running.");
System.in.read();
}
}
2.3.2 搭建消费者-api
1.项目结构:
2.启动类APIConsumer
:
public class APIConsumer {
public static void main(String[] args) {
ReferenceConfig<UserService> reference = new ReferenceConfig<>();
// #1 设置消费方应用名称
reference.setApplication(new ApplicationConfig("java-demo-consumer"));
// #2 设置注册中心地址和协议
reference.setRegistry(new RegistryConfig("zookeeper://192.168.237.130:2181"));
// #3 指定要消费的服务接口
reference.setInterface(UserService.class);
// #4 创建远程连接并做动态代理转换
UserService greetingsService = reference.get();
String message = greetingsService.getName("张三");
System.out.println(message);
}
}
三. Dubbo整合SpringBoot
3.1 搭建provider
1.项目结构:
2.pom文件(引入第二节中的demo-interface
项目):
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<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>
</dependency>
<dependency>
<groupId>org.example</groupId>
<artifactId>demo-interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--引入dubbo环境-->
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>0.2.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
3.实现类:
import com.pro.service.UserService;
import com.alibaba.dubbo.config.annotation.Service;
import java.text.SimpleDateFormat;
import java.util.Date;
@Service
public class UserServiceImpl implements UserService {
@Override
public String getName(String name) {
String now = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
System.out.println(now + ":" + name);
return name;
}
}
4.启动类:
@SpringBootApplication
@EnableDubbo
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class, args);
}
}
5.配置文件:
spring.application.name=boot-provider
#注册中心地址
dubbo.registry.address=zookeeper://192.168.237.130:2181
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880
#指定注册到zk上超时时间,ms
dubbo.registry.timeout=10000
#指定实现服务(提供服务)的包
dubbo.scan.base-packages=com.pro
dubbo.application.name=dubbo-provider
3.2 搭建consumer
1.项目结构:
2.pom文件同上,配置文件如下:
spring.application.name=boot-consumer
#注册中心地址
dubbo.registry.address=zookeeper://192.168.237.130:2181
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880
#指定注册到zk上超时时间,ms
dubbo.registry.timeout=10000
#指定实现服务(提供服务)的包
dubbo.scan.base-packages=com.pro
dubbo.application.name=dubbo-consumer
server.port=8000
3.Service层;
import com.alibaba.dubbo.config.annotation.Reference;
import com.pro.service.UserService;
import org.springframework.stereotype.Component;
@Component
public class MyConsumer {
@Reference
UserService userService;
public String getName(String name){
return userService.getName(name);
}
}
4.Controller层:
import com.pro.comsumer.service.MyConsumer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MyController {
@Autowired
MyConsumer consumer;
@GetMapping("/{name}")
public String test(@PathVariable("name") String name) {
return "远程调用成功!结果:"+consumer.getName(name);
}
}
5.启动类:
@SpringBootApplication
@EnableDubbo
public class ComsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ComsumerApplication.class, args);
}
}
完成好后,先启动Provider,在启动Consumer,接着访问路径http://localhost:8000/张三
:
文章到这里就结束了,其实,这篇文章并没有涉及太多底层原理的部分,只说明了几个基本的知识点以及Dubbo的用法。我只是希望借此来开个头,对于源码或是重点的分析则放到后续几篇文章来说。