深入探讨dubbo组件的实践
目录
1、dubbo概述
1.1、介绍
1.2、RPC
1.3、区别
1.4、优点
2、dubbo分层
2.1、层级分类
1. Service 服务层(业务层)
2. Config 配置层
3. Proxy 服务代理层
4. Registry 注册中心层
5. Cluster 集群容错层
6. Monitor 监控层
7. Protocol 远程调用层
8. Exchange 信息交换层
9. Transport 网络传输层
10. Serialize 数据序列化层
2.2、各层协作关系
2.3、启动流程
2.4、线程模型
2.5、协议比较
2.6、总结
3、原理
4、dubbo 高级特性
前言
随着系统的发展,服务越来越多,服务间依赖关系变得错踪复杂,基于访问压力实时管理集群容量,提高集群利用率。当某个服务挂掉之后,可以调用备用服务。甚至当同一个服务部署在不同的机器时该如何调用那一台机器上的服务呢?
如下图所示:
基于以上的问题,dubbo组件越来越满足分布式系统的项目需要。
1、dubbo
概述
1.1、介绍
- Dubbo是阿里巴巴公司开源的一个高性能、轻量级的 Java RPC 框架。
- 致力于提供高性能和透明化的 RPC 远程服务调用方案,以及 SOA 服务治理方案。、
- 官网:http://dubbo.apache.org
本质上是个远程服务调用的分布式框架。
1.2、RPC
RPC(Remote Procedure Call Protocol):远程服务调用
两台服务器A、B,分别部署不同的应用a,b。当A服务器想要调用B服务器上应用b提供的函数或方法的时候,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义传达调用的数据。
RPC是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据。在OSI网络通信模型中,RPC跨越了传输层和应用层。
RPC采用客户机/服务器模式。请求程序就是一个客户机,而服务提供程序就是一个服务器。首先,客户机调用进程发送一个有进程参数的调用信息到服务进程,然后等待应答信息。在服务器端,进程保持睡眠状态直到调用信息到达为止。
当一个调用信息到达,服务器获得进程参数,计算结果,发送答复信息,然后等待下一个调用信息,最后,客户端调用进程接收答复信息,获得进程结果,然后调用执行继续进行。
如下图所示:
1.3、区别
Dubbo是SOA时代的产物,它的关注点主要在于服务的调用,流量分发、流量监控和熔断。而Spring Cloud诞生于微服务架构时代,考虑的是微服务治理的方方面面。
如下图所示:
对比可知: Dubbo定位服务治理、Spirng Cloud是一个生态。
1.4、优点
1.透明化的远程方法调用:
就像调用本地方法一样调用远程方法,只需简单配置,没有任何API入侵。
2.软负载均衡及容错机制:
可在内网替代F5等硬件负载均衡器,降低成本。
3.自动注册与发现:
不再需要写死服务提供方地址,注册中心基于接口名查询服务提供者的IP地址,并且能平滑添加或删除服务提供者。
4.全Spring配置方式,透明化接入应用:
对应用没有任何API侵入,只需用Spring加载Dubbo配置即可,Dubbo基于Spring的Schema扩展进行加载。
5.启动时检查
Dubbo 缺省会在启动时检查依赖的服务是否可用,不可用时会抛出异常,阻止 Spring 初始化完成,以便上线时,能及早发现问题,默认 check=“true”。
2、dubbo分层
Dubbo 采用了清晰的分层架构设计,各层之间松耦合,使得系统具有高度的可扩展性和灵活性。
Dubbo 框架主要分为以下 10 层(从下至上),如下图所示:
2.1、层级分类
1. Service 服务层(业务层)
功能:
-
实际业务逻辑实现层
-
包含业务接口和实现类
特点:
-
开发者编写的业务代码所在层
-
通过配置暴露服务和引用服务
2. Config 配置层
功能:
-
对外配置接口
-
管理 Dubbo 的各种配置(服务提供者配置、消费者配置等)
核心类:
-
ServiceConfig
- 服务提供者配置 -
ReferenceConfig
- 服务消费者配置 -
ProtocolConfig
- 协议配置 -
RegistryConfig
- 注册中心配置
3. Proxy 服务代理层
功能:
-
生成服务接口的代理对象
-
对消费者透明化远程调用
特点:
-
消费者调用的是本地代理对象
-
代理对象负责将调用转发到远程服务
-
支持 JDK 动态代理和 Javassist 字节码生成
4. Registry 注册中心层
功能:
-
服务注册与发现
-
服务地址的注册与订阅
支持实现:
-
Zookeeper
-
Nacos
-
Redis
-
Multicast
-
Simple(直连)
5. Cluster 集群容错层
功能:
-
将多个服务提供者组合为一个虚拟服务
-
提供负载均衡和容错机制
核心特性:
-
负载均衡策略:随机、轮询、最少活跃调用等
-
容错策略:失败自动切换、失败快速失败、失败安全等
-
路由规则:条件路由、标签路由等
6. Monitor 监控层
功能:
-
统计服务调用次数和调用时间
-
监控服务健康状况
实现方式:
-
通过 Filter 拦截调用
-
数据可上报到各种监控系统
7. Protocol 远程调用层
功能:
-
封装 RPC 调用细节
-
定义调用协议和序列化方式
支持协议:
-
dubbo(默认)
-
hessian
-
http
-
rmi
-
webservice
-
thrift
-
grpc(Dubbo 3+)
默认使用的端口号:
8. Exchange 信息交换层
功能:
-
封装请求-响应模式
-
同步转异步
核心概念:
-
Request/Response 模型
-
Exchanger
-
ExchangeChannel
9. Transport 网络传输层
功能:
-
抽象网络传输的统一接口
-
定义数据传输的模型
核心抽象:
-
Endpoint
- 通信端点 -
Channel
- 通信通道 -
ChannelHandler
- 通道处理器 -
Client
/Server
- 客户端/服务端
实现方式:
-
基于 Netty(默认)
-
Mina
-
Grizzly
10. Serialize 数据序列化层
功能:
-
将对象转换为字节流进行网络传输
-
将字节流反序列化为对象
支持协议:
-
Hessian2(默认)
-
Java 原生序列化
-
JSON
-
Kryo
-
FST
-
Protobuf(Dubbo 3+)
2.2、各层协作关系
-
服务层通过 Config 层进行配置
-
Proxy 层生成代理对象
-
调用时经过 Cluster 层进行路由和负载均衡
-
Protocol 层封装调用信息
-
Exchange 层处理请求/响应交互
-
Transport 层进行网络传输
-
Serialize 层处理数据序列化
-
通过 Registry 层发现服务地址
-
Monitor 层监控整个调用过程
这种分层设计使得 Dubbo 各层可以独立扩展和替换,例如可以替换序列化方式而不影响其他层,或者更换注册中心而不需要修改业务代码。
下面我将通过一个完整的 Spring Boot 整合 Dubbo 的示例项目,详细介绍 Dubbo 的每一层在实际项目中的应用。
项目结构
dubbo-springboot-demo
├── api (服务接口模块)
├── provider (服务提供者)
└── consumer (服务消费者)
1. Service 服务层(业务层)
位置:api 模块中定义接口,provider 模块中实现
// api模块 - GreetingService.java
public interface GreetingService {String sayHello(String name);
}// provider模块 - GreetingServiceImpl.java
@Service // Dubbo的@Service注解,不是Spring的
public class GreetingServiceImpl implements GreetingService {@Overridepublic String sayHello(String name) {return "Hello, " + name + " from Dubbo provider!";}
}
Spring Boot 整合配置:
# provider的application.yml
dubbo:application:name: dubbo-springboot-providerprotocol:name: dubboport: 20880registry:address: zookeeper://127.0.0.1:2181scan:base-packages: com.example.provider.service # 扫描Dubbo服务
2. Config 配置层
Spring Boot 自动配置:
Dubbo 提供了 dubbo-spring-boot-starter
,自动完成配置层的初始化
// 自动配置类 DubboAutoConfiguration
@Configuration
@EnableConfigurationProperties(DubboConfigurationProperties.class)
@ConditionalOnProperty(prefix = "dubbo", name = "enabled", matchIfMissing = true)
public class DubboAutoConfiguration {@Beanpublic ApplicationConfig applicationConfig() {// 从application.yml读取dubbo.application配置}@Beanpublic ProtocolConfig protocolConfig() {// 从application.yml读取dubbo.protocol配置}// 其他配置Bean...
}
3. Proxy 服务代理层
消费者端自动创建代理:
// consumer模块 - ConsumerController.java
@RestController
public class ConsumerController {@Reference // Dubbo的@Reference注解注入代理private GreetingService greetingService;@GetMapping("/hello")public String hello(String name) {return greetingService.sayHello(name);}
}
代理创建过程:
-
ReferenceAnnotationBeanPostProcessor
处理@Reference
注解 -
创建
ReferenceConfig
实例 -
通过
ProxyFactory
创建代理对象
4. Registry 注册中心层
Zookeeper 注册中心配置:
# 公共配置application.yml
dubbo:registry:address: zookeeper://127.0.0.1:2181timeout: 3000
注册中心交互:
-
服务提供者启动时注册服务
-
服务消费者启动时订阅服务
-
注册中心通知消费者服务变更
5. Cluster 集群容错层
配置负载均衡和容错策略:
// 消费者端配置
@Reference(loadbalance = "roundrobin", // 轮询负载均衡cluster = "failover", // 失败自动切换retries = 2 // 重试2次
)
private GreetingService greetingService;
Dubbo 内置策略:
-
负载均衡:random, roundrobin, leastactive
-
集群容错:failover, failfast, failsafe
6. Protocol 远程调用层
多协议支持配置:
dubbo:protocols:dubbo:name: dubboport: 20880rest:name: restport: 8081server: tomcat
协议选择:
@Reference(protocol = "dubbo") // 指定使用dubbo协议
private GreetingService greetingService;
7. Exchange & Transport 网络通信层
Netty 配置:
dubbo:provider:dispatcher: allthreadpool: fixedthreads: 200iothreads: 4
通信过程:
-
消费者通过代理发起调用
-
经过 Exchange 层封装请求/响应模型
-
Transport 层通过 Netty 进行网络传输
8. Serialize 序列化层
配置序列化方式:
dubbo:protocol:serialization: hessian2 # 默认hessian2
支持的其他序列化:
-
kryo
-
fst
-
json
-
nativejava
9. Monitor 监控层
监控中心配置:
dubbo:monitor:protocol: registry
监控数据收集:
-
调用次数
-
调用时间
-
成功/失败次数
2.3、启动流程
-
服务提供者启动:
-
扫描
@Service
注解的服务 -
通过 Protocol 层暴露服务
-
向 Registry 层注册服务
-
-
服务消费者启动:
-
创建
@Reference
注解的代理 -
通过 Registry 层订阅服务
-
建立与提供者的网络连接
-
-
服务调用过程:
-
消费者调用代理方法
-
Cluster 层选择提供者
-
通过 Exchange 和 Transport 层发送请求
-
提供者处理请求并返回响应
-
Monitor 层记录调用数据
-
2.4、线程模型
Dubbo默认的底层网络通讯使用的是Netty,服务提供方NettyServer使用两级线程池,其中EventLoopGroup(boss)主要用来接受客户端的链接请求,并把接受的请求分发给EventLoopGroup(worker) 来处理,boss和worker线程组我们称之为IO线程。
如下图所示:
如果事件处理的逻辑能迅速完成,并且不会发起新的IO请求,比如只是在内存中记个标识,则直接在IO线程上处理更快,因为减少了线程池调度。
但如果事件处理逻辑较慢,或者需要发起新的IO请求,比如需要查询数据库,则必须派发到线程池,否则IO线程阻塞,将导致不能接收其它请求。
需要通过不同的派发策略和不同的线程池配置的组合来应对不同的场景:
<dubbo:protocol name="dubbo" dispatcher="all" threadpool="fixed" threads="100" />
根据请求的消息类被IO线程处理还是被业务线程池处理。
Dubbo提供了下面几种线程模型:
all:所有消息都派发到线程池,包括请求、响应、连接事件、断开事件、心跳等。
direct:所有消息都不派发到线程池,全部在IO线程上直接执行。
message:只有请求响应消息派发到线程池,其它连接断开事件、心跳等消息,直接在IO线程上执行。
execution:只请求消息派发到线程池,不含响应,响应和其它连接断开事件,心跳等消息,直接在IO线程上执行。
connection:在IO线程上,将连接断开事件放入队列,有序逐个执行,其它消息派发到线程池。
Dubbo提供的线程池策略,扩展接口ThreadPool的SPI实现有如下几种:
fixed:固定大小线程池,启动时建立线程,不关闭,一直持有(缺省)。
cached:缓存线程池,空闲一分钟自动删除,需要时重建。
limited:可伸缩线程池,但池中的线程数只会增长不会收缩。只增长不收缩的目的是为了避免收缩时突然来了大流量引起的性能问题。
2.5、协议比较
dubbo(默认)
单一长连接和NIO异步通讯,适合大并发小数据量的服务调用,以及消费者远大于提供者。
Dubbo协议适用常见:传入传出参数数据包较小(建议小于100K),消费者比提供者个数多,单一消费者无法压满提供者(并发量很高),尽量不要用Dubbo协议传输大文件或超大字符串。
如下图所示:
为了要支持高并发场景,一般是服务提供者就几台机器,但是服务消费者有上百台,可能每天调用量达到上亿次!此时用长连接是最合适的,就是跟每个服务消费者维持一个长连接就可以,可能总共就 100 个连接。然后后面直接基于长连接 NIO 异步通信,可以支撑高并发请求。
长连接,就是建立连接过后可以持续发送请求,无须再建立连接。
Dubbo允许配置多协议,在不同服务上支持不不同协议或者同一服务上同时支持多种协议。
2.6、总结
通过上面的介绍,可以概括为三层。
通过 Spring Boot 整合 Dubbo,我们可以清晰地看到 Dubbo 各层是如何协同工作的,同时 Spring Boot 的自动配置大大简化了 Dubbo 的配置和使用。
3、原理
1、节点
如下图所示:
节点角色说明:
- Provider:暴露服务的服务提供方
- Container:服务运行容器
- Consumer:调用远程服务的服务消费方
- Registry:服务注册与发现的注册中心
- Monitor:统计服务的调用次数和调用时间的监控中心
2.工作过程(Dubbo服务启动,调用,暴露消费的过程):
如下图所示:
1、服务容器负责启动,加载,运行服务提供者。
2、服务提供者在启动时,向注册中心注册自己提供的服务。
3、服务消费者在启动时,向注册中心订阅自己所需的服务。
4、注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者(注册中心,服务提供者,服务消费者三者之间均为长连接,监控中心除外。注册中心通过长连接感知服务提供者的存在,服务提供者宕机,注册中心将立即推送事件通知消费者。
5、注册中心和监控中心全部宕机,不影响已运行的提供者和消费者,消费者在本地缓存了提供者列表。)。
6、服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
7、服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
4、dubbo 高级特性
根据dubbo的分层架构可知,每层都有对应的特性。
1.序列化
dubbo 内部已经将序列化和反序列化的过程内部封装了
我们只需要在定义pojo类时实现Serializable接口即可
一般会定义一个公共的pojo模块,让生产者和消费者都依赖该模块。
2.地址缓存
假如注册中心挂了,服务是否可以正常访问?
可以,因为dubbo服务消费者在第一次调用时,会将服务提供方地址缓存到本地,以后在调用则不会访问注册中心。
当服务提供者地址发生变化时,注册中心会通知服务消费者。
3.超时与重试
- 服务消费者在调用服务提供者的时候发生了阻塞、等待的情形,这个时候,服务消费者会一直等待下去。
- 在某个峰值时刻,大量的请求都在同时请求服务消费者,会造成线程的大量堆积,势必会造成雪崩。
解决办法:
(1)设置超时和重试机制
dubbo 利用超时机制来解决这个问题,设置一个超时时间,在这个时间段内,无法完成服务访问,则自动断开连接。
使用timeout属性配置超时时间,默认值1000,单位毫秒。
设置了超时时间,在这个时间段内,无法完成服务访问,则自动断开连接。
如果出现网络抖动,则这一次请求就会失败。
Dubbo 提供重试机制来避免类似问题的发生。
通过 retries 属性来设置重试次数。默认为 2 次。
4.多版本
- 灰度发布:当出现新功能时,会让一部分用户先使用新功能,用户反馈没问题时,再将所有用户迁移到新功能。
- dubbo 中使用version 属性来设置和调用同一个接口的不同版本
可以控制服务使用范围
其实就是在服务生产者的service注解和服务消费者的Reference注解上加version属性,并确保值相同,才能访问的到。
5.负载均衡
负载均衡策略(4种):
- Random :按权重随机,默认值。按权重设置随机概率。
- RoundRobin :按权重轮询。
- LeastActive:最少活跃调用数,相同活跃数的随机。
- ConsistentHash:一致性 Hash,相同参数的请求总是发到同一提供者。
5.集群容错
集群容错模式:
Failover Cluster:失败重试。默认值。当出现失败,重试其它服务器 ,默认重试2次,使用 retries 配置。一般用于读操作
Failfast Cluster :快速失败,只发起一次调用,失败立即报错。通常用于写操作。
Failsafe Cluster :失败安全,出现异常时,直接忽略。返回一个空结果。
Failback Cluster :失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。
Forking Cluster :并行调用多个服务器,只要一个成功即返回。
Broadcast Cluster :广播调用所有提供者,逐个调用,任意一台报错则报错。
1.2.6.1 RPC需要解决的问题
通讯问题:
主要是通过在客户端和服务器之间建立TCP连接,远程过程调用的所有交换的数据都在这个连接里传输。连接可以是按需连接,调用结束后就断掉,也可以是长连接,多个远程过程调用共享同一个连接。
寻址问题:
A服务器上的应用怎么告诉底层的RPC框架,如何连接到B服务器(如主机或IP地址)以及特定的端口,方法的名称名称是什么,这样才能完成调用。比如基于Web服务协议栈的RPC,就要提供一个endpoint URI,或者是从UDDI服务上查找。如果是RMI调用的话,还需要一个RMI Registry来注册服务的地址。
序列化 与 反序列化:
当A服务器上的应用发起远程过程调用时,方法的参数需要通过底层的网络协议如TCP传递到B服务器,由于网络协议是基于二进制的,内存中的参数的值要序列化成二进制的形式,也就是序列化(Serialize)或编组(marshal),通过寻址和传输将序列化的二进制发送给B服务器。
同理,B服务器接收参数要将参数反序列化。B服务器应用调用自己的方法处理后返回的结果也要序列化给A服务器,A服务器接收也要经过反序列化的过程。
参考文章:
1、Dubbo详解,用心看这一篇文章就够了【重点】-CSDN博客
2、Dubbo(从入门到掌握)看完这一篇就够了-CSDN博客