一、介绍
? Dubbo是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。Dubbo提供的主要职责:
- 透明化的远程方法调用,就像调用本地方法一样调用远程方法,只需简单配置,没有任何API侵入
- 软负载均衡及容错机制, 提供基于接口方法的透明远程过程调用,包括多协议支持,以及软负载均衡,失败容错,地址路由,动态配置等集群支持
- 服务自动注册与发现,不再需要写死服务提供方地址,注册中心基于接口名查询服务提供者的IP地址,并且能够平滑添加或删除服务提供者。
二、 架构
节点角色说明
节点 | 角色说明 |
---|
Provider | 暴露服务的服务提供方 | Consumer | 调用远程服务的服务消费方 | Registry | 服务注册与发现的注册中心 | Monitor | 统计服务的调用次数和调用时间的监控中心 | Container | 服务运行容器 |
三、远程方法调用
1.注册中心
注册中心 | 配置方式 | 原理 | 试用范围 |
---|
Multicast | multicast://224.5.6.7:1234组播地址段: 224.0.0.0 - 239.255.255.255 | 1.提供方启动时广播自己的地址 2.消费方启动时广播订阅请求 3. 提供方收到订阅请求时,单播自己的地址给订阅者,如果设置了 unicast=false ,则广播给订阅者 4.消费方收到提供方地址时,连接该地址进行 RPC 调用。 | 组播受网络结构限制,只适合小规模应用或开发阶段使用 | zookeeper | zookeeper://191.168.10.101:2181 | 1.服务提供者启动时: 向 /dubbo/com.xx.xx/providers 目录下写入自己的 URL 地址 2.服务消费者启动时: 订阅 目录下的提供者 URL 地址。 /dubbo/com.xx.xxx/consumers` 目录下写入自己的 URL 地址 | 推荐使用。可以用zookeeper集群保证可靠性 | nacos | nacos://127.0.0.1:8848 | 生产者,消费者启动后将信息放到nacos上 | 推荐使用。比起zookeeper,可以用namepaces隔离多个服务 | Redis | redis://127.0.0.1:6379 | 发布订阅模式进行通信;hash结构存储生产者、消费者信息 | 性能高,但是对于服务器环境要求较高 |
2.传输协议
? Dubbo支持多种传输协议,如默认的dubbo协议,http协议,grpc协议等。
Dubbo 缺省协议采用单一长连接和 NIO 异步通讯,Hession序列化方式。适合于小数据量大并发的服务调用,以及服务消费者机器数远大于服务提供者机器数的情况。
3.线程模型
? Dubbo 默认的底层网络通讯使用的是 Netty ,服务提供方 NettyServer 使用两级线程池。IO线程池,负责接受客户端的连接请求并将接受的请求分发到执行的服务具体逻辑的线程池。另一个是执行的服务具体逻辑的线程池
如果服务提供方的逻辑能迅速完成,并且不会发起新的 IO 请求,那么直接在 IO 线程上处理会更快,因为这减少了线程池调度与上下文切换开销。但如果处理逻辑较慢,或者需要发起新的 IO 请求,比如需要查询数据库中的数据
Dispatcher
all 所有消息都派发到线程池,包括请求,响应,连接事件,断开事件,心跳等。(默认)direct 所有消息都不派发到线程池,全部在 IO 线程上直接执行。message 只有请求响应消息派发到线程池,其它连接断开事件,心跳等消息,直接在 IO 线程上执行。execution 只有请求消息派发到线程池,不含响应,响应和其它连接断开事件,心跳等消息,直接在 IO 线程上执行。connection 在 IO 线程上,将连接断开事件放入队列,有序逐个执行,其它消息派发到线程池。
,则 IO 线程必须派发请求到新的线程池进行处理,否则 IO 线程会被阻塞,将导致不能接收其它请求。
ThreadPool
fixed 固定大小线程池,启动时建立线程,不关闭,一直持有。(缺省)cached 缓存线程池,空闲一分钟自动删除,需要时重建。limited 可伸缩线程池,但池中的线程数只会增长不会收缩。只增长不收缩的目的是为了避免收缩时突然来了大流量引起的性能问题。eager 优先创建Worker 线程池。在任务数量大于corePoolSize 但是小于maximumPoolSize 时,优先创建Worker 来处理任务。当任务数量大于maximumPoolSize 时,将任务放入阻塞队列中。阻塞队列充满时抛出RejectedExecutionException 。(相比于cached :cached 在任务数量超过maximumPoolSize 时直接抛出异常而不是将任务放入阻塞队列)
设置方式
dubbo.protocol.dispatcher=message
#指定线程池
dubbo.protocol.threadpool=fixed
#指定线程数目
dubbo.protocol.threads=300
4.生产者执行方式和消费者调用方式
Provider 端支持异步执行和同步执行,Consumer 端支持同步调用和异步调用。可以任意正交组合两端配置。
- Consumer同步 - Provider同步
- Consumer异步 - Provider同步
- Consumer同步 - Provider异步
- Consumer异步 - Provider异步
生产者执行方式:
public interface AsyncService {
String sayHelloSync(String name);
String sayHelloByRpcContext(String name);
CompletableFuture<String> sayHelloAsync(String name);
}
public class AsyncServiceImpl implements AsyncService {
@Override
public String sayHelloByRpcContext(String name) {
AsyncContext asyncContext = RpcContext.startAsync();
new Thread(() -> {
asyncContext.write("hello " + name);
}).start();
return null;
}
@Override
public String sayHelloSync(String name) {
return "hello" + name;
}
@Override
public CompletableFuture<String> sayHelloAsync(String name) {
return CompletableFuture.supplyAsync(() ->
"hello " + name
);
}
}
消费者异步调用:
public static void main(String[] args) throws Exception {
System.out.println("call sayHelloSync"+asyncService.sayHelloSync("testSync"));
CompletableFuture<String> callAsync1 = RpcContext.getContext()
.asyncCall(() -> asyncService.sayHelloByRpcContext("testAsyncByRpcContext"));
callAsync1.whenComplete((retValue, exception) -> {
if (exception == null) {
System.out.println("return value: " + retValue);
} else {
exception.printStackTrace();
}
});
CompletableFuture<String> callAsync2 = asyncService.sayHelloAsync("testAsync");
callAsync2.whenComplete((retValue, exception) -> {
if (exception == null) {
System.out.println("return value: " + retValue);
} else {
exception.printStackTrace();
}
});
new CountDownLatch(1).await();
}
四、集群容错和负载均衡
1 容错模式
- Failfast Cluster 快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。
- Failsafe Cluster 失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。
- Failback Cluster 失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。
- Forking Cluster 并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过
forks="2" 来设置最大并行数。 - Broadcast Cluster 广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息。
2 负载均衡
-
Random LoadBalance
- 随机,按权重设置随机概率。
- 在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。
-
RoundRobin LoadBalance
- 轮询,按公约后的权重设置轮询比率。
- 存在慢的提供者累积请求的问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。
-
LeastActive LoadBalance
- 最少活跃调用数,活跃数指调用前后计数差(请求发送数 - 响应返回数)。活跃数越低,越优先调用。相同活跃数的随机。
- 使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。
-
ConsistentHash LoadBalance
- 一致性 Hash,相同参数的请求总是发到同一提供者。
五、总结
Dubbo 与Kafka 对比
-
相同点
-
都可以以分布式方式处理任务 -
都可以负载均衡 -
不同点
-
Dubbo处理任务是同步的,可以收到返回结果值。Kafka是异步的,不能返回结果值。 -
Kafka可以存储消息,可以从某个消息重新开始执行。如果任务执行速度较慢,消息可以缓存,不会阻塞。
Feign与Dubbo对比
-
相同点
- 都依赖注册中心、负载均衡
-
不同点
| RPC | REST |
---|
耦合性 | 强耦合 | 松耦合 | 消息协议 | 二进制 thrift/protobuf | 文本 xml、jason | 通信协议 | TCP | HTTP | 接口契约IDL | thrift/protobuf | swagger | 开发调试 | 消息不可读 | 可读,可调试 | 对外开放 | 一般作为内部各个系统的通信框架 | 对接外部系统 | 性能 | 高 | 短 |
参考文档:
Dubbo官方文档: https://dubbo.apache.org/zh/docs/
开发调试 | 消息不可读 | 可读,可调试 | | 对外开放 | 一般作为内部各个系统的通信框架 | 对接外部系统 | | 性能 | 高 | 短 |
参考文档:
Dubbo官方文档: https://dubbo.apache.org/zh/docs/
Dubbo代码示例地址: https://github.com/apache/dubbo-samples.git
|