1、什么是Dubbo
Dubbo 是一个分布式、高性能、透明化的 RPC 服务框架,
提供服务自动注册、自动发现等高效服务治理方案,
可以和Spring 框架无缝集成。
2、为什么要使用Dubbo
Dubbo 的诞生和 SOA 分布式架构的流行有着莫大的关系。SOA 面向服务的架构(Service Oriented Architecture),也就是把工程按照业务逻辑拆分成服务层、表现层两个工程。服务层中包含业务逻辑,只需要对外提供服务即可。表现层只需要处理和页面的交互,业务逻辑都是调用服务层的服务来实现。
SOA架构中有两个主要角色:
- 服务提供者(Provider)
- 服务使用者(Consumer)
- 我觉得主要可以从 Dubbo 提供的下面四点特性来说为什么要用 Dubbo:
- 负载均衡——同一个服务部署在不同的机器时该调用那一台机器上的服务
- 服务调用链路生成——随着系统的发展,服务越来越多,服务间依赖关系变得错踪复杂,甚至分不清哪个应用要在哪个应用之前启动,架构师都不能完整的描述应用的架构关系。Dubbo 可以为我们解决服务之间互相是如何调用的。
- 服务访问压力以及时长统计、资源调度和治理——基于访问压力实时管理集群容量,提高集群利用率。
- 服务降级——某个服务挂掉之后调用备用服务 另外,Dubbo 除了能够应用在分布式系统中,也可以应用在现在比较火的微服务系统中。不过,由于 Spring Cloud 在微服务中应用更加广泛,所以,我觉得一般我们提 Dubbo 的话,大部分是分布式系统的情况
3、什么是分布式
分布式或者说 SOA 分布式重要的就是面向服务,说简单点分布式就是我们把整个系统拆分成不同的服务然后将这些服务放在不同的服务器上减轻单体服务的压力提高并发量和性能。比如电商系统可以简单地拆分成订单系统、商品系统、登录系统等等,拆分之后的每个服务可以部署在不同的机器上,如果某一个服务的访问量比较大的话也可以将这个服务同时部署在多台机器上。
4、为什么要用分布式
从开发角度来讲单体应用的代码都集中在一起,而分布式系统的代码根据业务被拆分。所以,每个团队可以负责一个服务的开发,这样提升了开发效率。另外,代码根据业务拆分之后更加便于维护和扩展。
另外,我觉得将系统拆分成分布式之后不光便于系统扩展和维护,更能提高整个系统的性能。使用更多的计算机完成同样的功能,计算机越多,CPU、内存、存储资源也越多,处理并发访问和数据量就越大。
5、Dubbo的架构与工作流程
- Provider: 暴露服务的服务提供方
- Consumer: 调用远程服务的服务消费方
- Registry: 服务注册与发现的注册中心
- Monitor: 统计服务的调用次数和调用时间的监控中心
- Container: 服务运行容器
调用关系说明:
- 服务容器负责启动,加载,运行服务提供者。
- 服务提供者在启动时,向注册中心注册自己提供的服务。
- 服务消费者在启动时,向注册中心订阅自己所需的服务。
- 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
- 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
- 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
重要知识点总结:
- 注册中心负责服务地址的注册与查找,相当于目录服务,服务提供者和消费者只在启动时与注册中心交互,注册中心不转发请求,压力较小
- 监控中心负责统计各服务调用次数,调用时间等,统计先在内存汇总后每分钟一次发送到监控中心服务器,并以报表展示
- 注册中心,服务提供者,服务消费者三者之间均为长连接,监控中心除外
- 注册中心通过长连接感知服务提供者的存在,服务提供者宕机,注册中心将立即推送事件通知消费者
- 注册中心和监控中心全部宕机,不影响已运行的提供者和消费者,消费者在本地缓存了提供者列表
- 注册中心和监控中心都是可选的,服务消费者可以直连服务提供者
- 服务提供者无状态,任意一台宕掉后,不影响使用
- 服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复
6、Dubbo的负载均衡策略
在集群负载均衡时,Dubbo 提供了多种均衡策略,默认为 <font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">random</font>
随机调用。可以自行扩展负载均衡策略。
Random LoadBalance(默认,基于权重的随机负载均衡机制)
- 随机,按权重设置随机概率。
- 在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。
RoundRobin LoadBalance(不推荐,基于权重的轮询负载均衡机制)
- 轮循,按公约后的权重设置轮循比率。
- 存在慢的提供者累积请求的问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。
LeastActive LoadBalance
- 最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。
- 使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。
ConsistentHash LoadBalance
- 一致性 Hash,相同参数的请求总是发到同一提供者。(如果你需要的不是随机负载均衡,是要一类请求都到一个节点,那就走这个一致性hash策略。)
- 当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。
注解配置方式:
消费方基于基于注解的服务级别配置方式:
<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">@Reference(loadbalance = "roundrobin")</font>
<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">HelloService helloService;</font>
xml 配置方式 服务端服务级别
<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);"><dubbo:service interface="..." loadbalance="roundrobin" /></font>
7、Dubbo的集群容错策略
Failover Cluster(默认)
失败自动切换,当出现失败,重试其它服务器。(缺省)通常用于读操作,
但重试会带来更长延迟。可通过 retries="2"来设置重试次数(不含第一次)。
Failfast Cluster
快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,
比如新增记录。
Failsafe Cluster
失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。
Failback Cluster
失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。
Forking Cluster
并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读
操作,但需要浪费更多服务资源。可通过 forks="2"来设置最大并行数。
Broadcast Cluster
广播逐个调用所有提供者,任意一个报错则报错
8、Dubbo使用demo
首先创建个Springboot工程,共有三个子模块,
provider模块:服务提供方,consumer:服务调度方(消费),common:公共模块:暴露接口
导入依赖包,provider与consumer的如下:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- dubbo依赖 -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>3.2.4</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-rpc-dubbo</artifactId>
<version>3.2.4</version>
</dependency>
<!-- triple协议 -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-rpc-triple</artifactId>
<version>3.2.4</version>
</dependency>
<dependency>
<!-- 注册中心 -->
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-zookeeper</artifactId>
<version>3.2.4</version>
</dependency>
<dependency>
<groupId>com.llg</groupId>
<artifactId>common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
配置中心:provider
server:
port: 8081
spring:
application:
name: dubbo-provider
dubbo:
protocol:
name: tri
port: 20881
registry:
address: zookeeper://127.0.0.1:2181
配置中心:consumer
server:
port: 8082
spring:
application:
name: dubbo-consumer
dubbo:
protocol:
name: tri
port: 20881
registry:
address: zookeeper://127.0.0.1:2181
provider和consumer的启动器需要加上@EnableDubbo注解
将用户Service接口定义到common模块,方便其他服务调用,Service的实现类在provider模块
/**
* 用户接口
*
* @author llg
* @create 2023-12-30 12:43
*/
public interface UserService {
// UNARY
String getUserName();
}
userService的实现类,Dubbo的业务类用@DubboService取代@Service注解,用于Dubbo服务发现
/**
* 功能描述
*
* @author llg
* @create 2023-12-30 12:26
*/
@DubboService
public class UserServiceImpl implements UserService {
@Override
public String getUserName(){
return "llg";
}
}
consumer的OrderService调用UserService服务,通过@DubboReference注解注入远程服务。直接调用
/**
* 功能描述
*
* @author llg
* @create 2023-12-30 12:26
*/
@DubboService
public class OrderService {
@DubboReference
UserService userService;
public String getOrder(){
userService.getUserName();
}
}
9、Dubbo3.0的新特性Stream流
Stream 是 Dubbo3 新提供的一种调用类型,在以下场景时建议使用流的方式:
- 接口需要发送大量数据,这些数据无法被放在一个 RPC 的请求或响应中,需要分批发送,但应用层如果按照传统的多次 RPC 方式无法解决顺序和性能的问题,如果需要保证有序,则只能串行发送
- 流式场景,数据需要按照发送顺序处理, 数据本身是没有确定边界的
- 推送类场景,多个消息在同一个调用的上下文中被发送和处理
Stream 分为以下三种:
- SERVER_STREAM(服务端流)
- CLIENT_STREAM(客户端流)
- BIDIRECTIONAL_STREAM(双向流)
由于 <font style="color:rgb(119, 119, 119);background-color:rgb(243, 244, 244);">java</font>
语言的限制,BIDIRECTIONAL_STREAM 和 CLIENT_STREAM 的实现是一样的。
在 Dubbo3 中,流式接口以 <font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">SteamObserver</font>
声明和使用,用户可以通过使用和实现这个接口来发送和处理流的数据、异常和结束。
服务端流:
UserService:
// Server_Stream
@Override
public void sayHelloServerStream(String name, StreamObserver<String> response) {
// 处理name
response.onNext("Hello " + name + "!");
response.onNext("Hello " + name + "!");
// 处理结束
response.onCompleted();
}
OrderService
@DubboReference
UserService userService;
public String getOrder(){
userService.sayHelloServerStream("llg", new StreamObserver<String>() {
@Override
public void onNext(String data) {
System.out.println("接收结果 = " + data);
}
@Override
public void onError(Throwable throwable) {
System.out.println("接收异常");
}
@Override
public void onCompleted() {
System.out.println("结束");
}
});
}
双端流 or 客户端流
UserService:
@Override
public StreamObserver<String> sayHelloStream(StreamObserver<String> response) {
return new StreamObserver<String>() {
@Override
public void onNext(String data) {
System.out.println("服务端接收到的数据:"+data);
// 处理数据
response.onNext("服务端返回的数据:"+data);
}
@Override
public void onError(Throwable throwable) {
System.out.println("接收异常");
}
@Override
public void onCompleted() {
System.out.println("发送完成");
}
};
}
OrderService
public String getOrder(){
StreamObserver<String> streamObserver =
userService.sayHelloStream(new StreamObserver<String>() {
@Override
public void onNext(String data) {
System.out.println("data = " + data);
}
@Override
public void onError(Throwable throwable) {
System.out.println("接收异常");
}
@Override
public void onCompleted() {
System.out.println("结束");
}
});
streamObserver.onNext("1");
streamObserver.onNext("2");
streamObserver.onNext("3");
streamObserver.onCompleted();
return "success";
}