目录
前言
随着系统的发展,服务越来越多,服务间依赖关系变得错踪复杂,基于访问压力实时管理集群容量,提高集群利用率。当某个服务挂掉之后,可以调用备用服务。甚至当同一个服务部署在不同的机器时该如何调用那一台机器上的服务呢?
如下图所示:
基于以上的问题,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 {
@Override
public String sayHello(String name) {
return "Hello, " + name + " from Dubbo provider!";
}
}
Spring Boot 整合配置:
# provider的application.yml
dubbo:
application:
name: dubbo-springboot-provider
protocol:
name: dubbo
port: 20880
registry:
address: zookeeper://127.0.0.1:2181
scan:
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 {
@Bean
public ApplicationConfig applicationConfig() {
// 从application.yml读取dubbo.application配置
}
@Bean
public 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:2181
timeout: 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: dubbo
port: 20880
rest:
name: rest
port: 8081
server: tomcat
协议选择:
@Reference(protocol = "dubbo") // 指定使用dubbo协议
private GreetingService greetingService;
7. Exchange & Transport 网络通信层
Netty 配置:
dubbo:
provider:
dispatcher: all
threadpool: fixed
threads: 200
iothreads: 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、集群特性
集群特性分为容错机制和负载均衡机制。
1、集群容错机制:
分为以下几种,如下图所示:
各节点的关系:
Invoker是Provider的一个可调用Service的抽象, Invoker封装了Provider地址及Service接口信息。
Directory代表多个Invoker,可以把它看成List<Invoker>,但与List不同的是,它的值可能是动态变化的,比如注册中心推送变更。
Cluster将Directory中的多个Invoker伪装成一个Invoker,对上层透明,伪装过程包含了容错逻辑,调用失败后,重试另一个。
Router负责从多个Invoker中按路由规则选出子集,比如读写分离,应用隔离等。
LoadBalance负责从多个Invoker中选出具体的一个用于本次调用,选的过程包含了负载均衡算法,调用失败后,需要重选。
Dubbo的默认集群容错方案是Failover Cluster
。
1、Failover Cluster
失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。可通过retries="2"来设置重试次数(不含第一次)。示例:
<!--2种写法-->
<dubbo:service retries="2" cluster="failover"/>
<dubbo:reference retries="2" cluster="failover"/>
cluster="failover"可以不用写,因为默认就是failover。
2、Failfast Cluster
快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。配置示例:
<!--2种写法-->
<dubbo:service cluster="failfast" />
<dubbo:reference cluster="failfast" />
cluster="failfast"和 把 cluster=“failover”、retries="0"是一样的效果,retries="0"就是不重试。
3、Failsafe Cluster
失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。配置示例:
<!--2种写法-->
<dubbo:service cluster="failsafe" />
<dubbo:reference cluster="failsafe" />
4、Failback Cluster
失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。配置示例:
<!--2种写法-->
<dubbo:service cluster="failback" />
<dubbo:reference cluster="failback" />
5、Forking Cluster
并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks=“2” 来设置最大并行数。配置示例:
<!--2种写法-->
<dubbo:service cluster="forking" forks="2"/>
<dubbo:reference cluster="forking" forks="2"/>
6、Broadcast Cluster
广播调用所有提供者,逐个调用,任意一台报错则报错 。通常用于通知所有提供者更新缓存或日志等本地资源信息。可以在不同的级别配置。
不同的配置级别:
<!--服务端服务级别-->
<dubbo:service interface="..." loadbalance="roundrobin" />
<!--客户端服务级别-->
<dubbo:reference interface="..." loadbalance="roundrobin" />
<!--服务端方法级别-->
<dubbo:service interface="..."> <dubbo:method name="..." loadbalance="roundrobin" />
<!--客户端方法级别-->
<dubbo:reference interface="..."> <dubbo:method name="..." loadbalance="roundrobin" />
2、负载均衡
Dubbo负载均衡在客户端。
Dubbo提供了多种均衡策略,默认采用Random LoadBalance策略,可以自行扩展负载均衡策略。
1、Random LoadBalance(默认,基于权重的随机负载均衡机制)
随机,按权重设置随机概率。
调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重(权重可以在dubbo控制台配置)。
如下图所示:
2、RoundRobin LoadBalance(均匀的轮询负载均衡机制)
轮循,按公约后的权重设置轮训比率(越慢越多)。
存在慢的提供者累积请求的问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。
如下图所示:
3、LeastActive LoadBalance
最少活跃调用数(越快越多处理),相同活跃数的随机,活跃数指调用前后计数差。
使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。
每个服务维护一个活跃数计数器。当A机器开始处理请求,该计数器加1,此时A还未处理完成。若处理完毕则计数器减1。而B机器接受到请求后很快处理完毕。那么A、B的活跃数分别是1、0。当又产生了一个新的请求,则选择B机器去执行(B活跃数最小),这样使慢的机器A收到少的请求。
4、ConsistentHash LoadBalance
一致性Hash,相同参数的请求总是发到同一提供者。
当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。
缺省只对第一个参数Hash,如果要修改,配置示例:
<dubbo:parameter key="hash.arguments" value="0,1" />
2.7、总结
通过上面的介绍,可以概括为三层。
通过 Spring Boot 整合 Dubbo,我们可以清晰地看到 Dubbo 各层是如何协同工作的,同时 Spring Boot 的自动配置大大简化了 Dubbo 的配置和使用。
3、原理
3.1、节点
如下图所示:
节点角色说明:
- Provider:暴露服务的服务提供方
- Container:服务运行容器
- Consumer:调用远程服务的服务消费方
- Registry:服务注册与发现的注册中心
- Monitor:统计服务的调用次数和调用时间的监控中心
3.2、工作过程
Dubbo服务启动,调用,暴露消费的过程,如下图所示:
1、服务容器负责启动,加载,运行服务提供者。
2、服务提供者在启动时,向注册中心注册自己提供的服务。
3、服务消费者在启动时,向注册中心订阅自己所需的服务。
4、注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者(注册中心,服务提供者,服务消费者三者之间均为长连接,监控中心除外。注册中心通过长连接感知服务提供者的存在,服务提供者宕机,注册中心将立即推送事件通知消费者。
5、注册中心和监控中心全部宕机,不影响已运行的提供者和消费者,消费者在本地缓存了提供者列表。)。
6、服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
7、服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
3.3、核心配置
如下图所示:
3.4、配置的优先级
- 优先级从高到低:
如下图所示:
4、dubbo 高级特性
根据dubbo的分层架构可知,每层都有对应的特性。
4.1、服务分组
当一个接口有多种实现时,可以用group区分。
不同的组:
<dubbo:service group="feedback" interface="com.xxx.IndexService" />
<dubbo:service group="member" interface="com.xxx.IndexService" />
4.2、多版本
当一个接口实现,出现不兼容升级时,可以用版本号过渡,版本号不同的服务相互间不引用。
可以按照以下的步骤进行版本迁移:
- 在低压力时间段,先升级一半提供者为新版本;
- 再将所有消费者升级为新版本;
- 然后将剩下的一半提供者升级为新版本。
<!--老版本服务提供者配置示例-->
<dubbo:service interface="com.foo.BarService" version="1.0.0" />
<!--新版本服务提供者示例-->
<dubbo:service interface="com.foo.BarService" version="2.0.0" />
<!--老版本服务消费者示例-->
<dubbo:reference id="barService" interface="com.foo.BarService" version="1.0.0" />
<!--新版本服务消费者示例-->
<dubbo:reference id="barService" interface="com.foo.BarService" version="2.0.0" />
4.3、事件通知
在调用之前、调用之后、出现异常时,会触发oninvoke、onreturn、onthrow三个事件,可以配置当事件发生时,通知哪个类的哪个方法 。
服务消费者Callback配置示例:
<bean id ="demoCallback"
class = "com.alibaba.dubbo.callback.implicit.NofifyImpl" />
<dubbo:reference id="demoService"
interface="com.alibaba.dubbo.callback.implicit.IDemoService"
version="1.0.0"
group="cn" >
<dubbo:method name="get"
async="true"
onreturn = "demoCallback.onreturn"
onthrow="demoCallback.onthrow" />
</dubbo:reference>
async表示结果是否马上返回,onreturn表示是否需要回调。
存在以下几种组合情况 :
异步回调模式: async=true onreturn=“xxx”
同步回调模式: async=false onreturn=“xxx”
异步无回调 : async=true
同步无回调 : async=false
4.4、本地伪装
本地伪装通常用于服务降级,比如某验权服务,当服务提供方全部挂掉后,客户端不抛出异常,而是通过Mock数据返回授权失败。
在Spring配置文件中按以下方式配置:
<!--2种方式-->
<dubbo:service interface="com.foo.BarService" mock="true" />
<dubbo:service interface="com.foo.BarService" mock="com.foo.BarServiceMock" />
如果服务的消费方经常需要try-catch捕获异常,如:
Offer offer = null;
try {
offer = offerService.findOffer(offerId);
} catch (RpcException e) {
logger.error(e);
}
可以考虑改为Mock实现,并在Mock实现中return null。如果只是想简单的忽略异常,在2.0.11以上版本可用:
<dubbo:service interface="com.foo.BarService" mock="return null" />
4.5、令牌验证
通过令牌验证在注册中心控制权限,以决定要不要下发令牌给消费者,可以防止消费者绕过注册中心访问提供者.另外通过注册中心可灵活改变授权方式,而不需修改或升级提供者。图示:
可以全局设置开启令牌验证,也可以在服务提供者或者消费者的时候开启:
<!--随机token令牌,使用UUID生成-->
<dubbo:provider interface="com.foo.BarService" token="true" />
<!--固定token令牌,相当于密码-->
<dubbo:provider interface="com.foo.BarService" token="123456" />
4.6、优雅停机
Dubbo是通过JDK的ShutdownHook来完成优雅停机的,所以如果用户使用kill -9 PID等强制关闭指令,是不会执行优雅停机的,只有通过kill PID时,才会执行。
服务提供方
停止时,先标记为不接收新请求,新请求过来时直接报错,让客户端重试其它机器。然后,检测线程池中的线程是否正在运行,如果有,等待所有线程执行完成,除非超时,则强制关闭。
服务消费方
停止时,不再发起新的调用请求,所有新的调用在客户端即报错。
然后,检测有没有请求的响应还没有返回,等待响应返回,除非超时,则强制关闭。
设置方式
设置优雅停机超时时间,缺省超时时间是10秒,如果超时则强制关闭。示例:
4.7、注册中心
Dubbo默认采用Zookeeper注册中心
。
更多知识可参考:深入学习Zookeeper的知识体系-CSDN博客
4.8、超时与重试
超时用于控制调用的等待时间,而重试用于在请求失败时重新尝试调用服务。
代码示例如下:
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.stereotype.Service;
@Service
public class ConsumerService {
@Reference(retries = 3, timeout = 2000) // 设置重试次数为 3,超时时间为 2000 毫秒
private HelloService helloService;
public void callService() {
String response = helloService.sayHello("World");
System.out.println("Response: " + response);
}
}
总结:
Dubbo 是一款高性能的 Java RPC(远程过程调用)框架,由阿里巴巴开源。它用于构建分布式应用程序,支持服务的透明化调用、服务治理(如注册、发现、负载均衡和容错),并实现了高效的异步非阻塞调用。
通过合理的配置参数,可以提高服务的性能及高可用。
参考文章:
1、Dubbo详解,用心看这一篇文章就够了【重点】-CSDN博客
2、Dubbo(从入门到掌握)看完这一篇就够了-CSDN博客