文章目录
我们来回顾一下不断演进的服务架构
单体架构 -> 分布式架构 -> 微服务
单体架构:将业务的所有功能集中在一个项目中开发,打成一个包部署
优点:
- 架构简单
- 部署成本低
缺点:
- 耦合度高(维护困难、升级困难)
分布式架构:根据业务功能对系统做拆分,每个业务功能模块作为独立项目开发,称为一个服务
优点:
- 降低服务耦合
- 有利于服务升级和拓展
缺点:
- 服务调用关系错综复杂
分布式架构虽然降低了服务耦合,但是服务拆分时也有很多问题需要思考:
- 服务拆分的粒度如何界定?
- 服务之间如何调用?
- 服务的调用关系如何管理?
人们需要制定一套行之有效的标准来约束分布式架构,进而有了我们现在的微服务,微服务的上述特性其实是在给分布式架构制定一个标准,进一步降低服务之间的耦合度,提供服务的独立性和灵活性。做到高内聚,低耦合。
因此,可以认为微服务是一种经过良好架构设计的分布式架构方案 。
微服务的架构特征:
- 单一职责:微服务拆分粒度更小,每一个服务都对应唯一的业务能力,做到单一职责
- 自治:团队独立、技术独立、数据独立,独立部署和交付
- 面向服务:服务提供统一标准的接口,与语言和技术无关
- 隔离性强:服务调用做好隔离、容错、降级,避免出现级联问题
现在国内用的最多的微服务就是SpringCloud
,它的核心组件包括:
1. 什么是Dubbo
我们在学习SpringCloud的时候,为了调用其他微服务里面的接口,经常会使用Feign
来调用,其实这就是一种远程过程调用(RPC),是基于Http协议的远程服务调用
Dubbo
其实也是干类似的事情的,即Dubbo
是一款高性能的RPC框架,用来解决微服务中各个模块调用的问题,那我们既然有了Feign
,为什么还要有Dubbo
呢?那是因为对于微服务模块来说HTTP
协议还是太重了,HTTP
协议在设计之处为了许多的安全性,设置了许多健壮性的设计,对于微服务模块来说,其实完全可以采用更加轻量级的通讯协议来完成远程服务调用,Dubbo
应运而生
Dubbo主要从以下两个方面来加快远程调用的速度,这两个方面同时也是我们进行网络IO最耗时的方面:
-
序列化
我们在学习Java网络编程的时候知道,一个对象要想在网络中传输,就必须要实现
Serializable
接口进行序列化,一般序列化的方式有:xml、json、二进制流等,其中效率最高的就是二进制流(因为网络传输的本质就是通过二进制传输的),Dubbo
采用的就是效率最高的二进制方式进行序列化 -
网络通信
Dubbo采用Socket通信,自定义一套高效的通讯协议,提升了通信效率,并且可以建立长连接,不用反复连接,极大的提升了传输的效率
现在市面上还有很多的RPC框架,如:gRPC、Thrift、HSF等等
Dubbo
除了提供远程服务调用的功能之外,还有服务注册发现的功能,我们来看一下官方的Dubbo
架构图:
可以看到在Dubbo
中主要有五个角色:
-
Container: 服务运行容器,负责加载、运行服务提供者。必须。
-
Provider: 暴露服务的服务提供方,会向注册中心注册自己提供的服务。必须。
-
Consumer: 调用远程服务的服务消费方,会向注册中心订阅自己所需的服务。必须。
-
Registry: 服务注册与发现的注册中心。注册中心会返回服务提供者地址列表给消费者。非必须。
-
Monitor: 统计服务的调用次数和调用时间的监控中心。服务消费者和提供者会定时发送统计数据到监控中心。 非必须。
读者可能会感觉这些服务好像Nacos
也提供叭,或者说是SpringCloud
也提供,是的,其实Dubbo
和SpringCloud
在某种程度上是竞争关系
那这里就有疑问了:dubbo和springCloud各有什么优缺点呢?这个问题放到文章最后面讨论,我们接着往下看
Dubbo
为我们提供的主要功能有:
- 面向接口代理的高性能 RPC 调用
- 智能容错和负载均衡。
- 服务自动注册和发现
- 高度可扩展能力
- 运行期流量调度
- 可视化的服务治理与运维
我们总结一下Dubbo
的作用: Dubbo是一站式的微服务解决方案
2. Dubbo快速上手
因为Dubbo是使用Zookeeper进行服务注册发现,所以我们需要安装一个zk,后续可以使用Nacos
作为注册中心
我们可以在Linux环境下搭建一个Zookeeper,这里笔者采用的方式是Docker的方式
2.1、docker安装zookeeper
下载zookeeper 最新版镜像
docker search zookeeper
docker pull zookeeper
docker images //查看下载的本地镜像
docker inspect zookeeper //查看zookeeper详细信息
新建一个文件夹
mkdir zookeeper
挂载本地文件夹并启动服务
docker run -d -e TZ="Asia/Shanghai" -p 2181:2181 -v /root/docker/zookeeper:/data --name zookeeper --restart always zookeeper
参数解释
-e TZ="Asia/Shanghai" # 指定上海时区
-d # 表示在一直在后台运行容器
-p 2181:2181 # 对端口进行映射,将本地2181端口映射到容器内部的2181端口
--name # 设置创建的容器名称
-v # 将本地目录(文件)挂载到容器指定目录;
--restart always #始终重新启动zookeeper
查看容器
docker ps
进入容器(zookeeper)
第一种方式
docker run -it --rm --link zookeeper:zookeeper zookeeper zkCli.sh -server zookeeper //这样的话,直接登录到容器时,进入到 zkCli中
第二种方式(推荐)
docker exec -it zookeeper bash //只登录容器,不登录 zkCli
./bin/zkCli.sh //执行脚本新建一个Client,即进入容器
2.2、各模块的配置文件
我们这里来新建一个项目用来演示一下Dubbo
的使用过程:
父模块的依赖如下,采用的都是现在最新的版本,如果版本发生冲突可以试着降低版本
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.hnit</groupId>
<artifactId>dubbo_demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<name>dubbo_demo</name>
<description>dubbo_demo</description>
<modules>
<module>dubbo-api</module>
<module>dubbo-service-provider</module>
<module>dubbo-web-consumer</module>
</modules>
<properties>
<java.version>1.8</java.version>
<dubbo-boot.version>3.0.8</dubbo-boot.version>
<zookeeper.version>4.0.0</zookeeper.version>
<web.version>2.6.4</web.version>
</properties>
<dependencyManagement>
<dependencies>
<!-- apache 官方 3.0 starter依赖 -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>${dubbo-boot.version}</version>
</dependency>
<!-- zookeeper客户端 只需引入此依赖curator-framework curator-recipes 都有 -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-x-discovery</artifactId>
<version>${zookeeper.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${web.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
dubbo-api
依赖:
<parent>
<groupId>com.hnit</groupId>
<artifactId>dubbo_demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<groupId>com.hnit</groupId>
<artifactId>dubbo-api</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
dubbo-services-provider
依赖:
<parent>
<groupId>com.hnit</groupId>
<artifactId>dubbo_demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<groupId>com.hnit</groupId>
<artifactId>dubbo-service-provider</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>dubbo-service-provider</name>
<description>dubbo-service-provider</description>
<dependencies>
<!-- 不需要对外暴露接口,仅需要给其他模块进行RPC调用 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!--ZooKeeper-->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-x-discovery</artifactId>
</dependency>
<!--dubbo-->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
</dependency>
<!--依赖公共的接口模块-->
<dependency>
<groupId>com.hnit</groupId>
<artifactId>dubbo-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
dubbo-services-provider
配置文件:
# 这里的配置属性只是基础配置,如需更多功能配置,请自行扩展
spring:
application:
name: dubbo-service-provider
server:
port: 9101
dubbo:
application:
name: dubbo-service
#配置dubbo包扫描
scan:
base-packages: com.hnit.provider.service.impl
#配置注册中心的地址
registry:
address: zookeeper://localhost:2181
#元数据配置
metadata-report:
address: zookeeper://localhost:2181
#Dubbo协议配置
protocol:
name: dubbo
port: 30882
#超时重试时间
provider:
timeout: 3000
qos:
enable: true # 启用QoS功能
port: 55555 # 指定QoS服务端口号
若想配置第二个dubbo-services-provider
,只是要将dubbo通信的端口修改一下就好
dubbo-web-comsumer
依赖:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-x-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.hnit</groupId>
<artifactId>dubbo-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
配置文件:
spring:
application:
name: dubbo-web-consumer
server:
port: 9102
dubbo:
application:
name: dubbo-web
registry:
address: zookeeper://localhost:2181
consumer:
timeout: 3000
protocol:
name: dubbo
port: 30882
接着我们在API
中写几个需要进行RPC的接口,这个包一般用来定义接口,提供给其他包进行实现,读者也可以不写这个包
User类
/**
* 所有的pojo类都需要实现Serializable接口
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
private static final long serialVersionUID = 8728327146677888239L;
private Integer id;
private String username;
private String password;
}
接口UserService
public interface UserService {
User getByUserId(Integer userId);
}
2.3、对外暴露接口
接着我们写调用逻辑和对外暴露的接口,对外暴露的接口仅写在dubbo-consumer
模块中,在此之前我们需要先写服务中心的RPC调用逻辑,也就是dubbo-services-provider
模块中,写一个类来实现之前我们dubbo-api
中定义的接口
@Slf4j
@DubboService
public class UserServiceImpl implements UserService {
// 模拟数据
private static final List<User> USERS = Arrays.asList(
new User(1, "张三"),
new User(2, "李四")
);
// 用来记录被调用的次数
private final AtomicInteger sum = new AtomicInteger(0);
@Override
public User getByUserId(Integer userId) {
// 打印一下被调用情况,dubbo-provider2中这里填dubbo-provider2被调用
log.info("dubbo-provider被调用【{}】次", sum.incrementAndGet());
for (User user : USERS) {
if (user.getUserId().equals(userId)) {
return user;
}
}
return null;
}
}
启动类上我们需要加上@EnableDubbo
注解表示启动Dubbo RPC
@EnableDubbo
@SpringBootApplication
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class, args);
}
}
dubbo-web-consumer
模块
@RestController
@RequestMapping("/user")
public class UserController {
//注入Service
//@Autowired//本地注入
/*
1. 从zookeeper注册中心获取userService的访问url
2. 进行远程调用RPC
3. 将结果封装为一个代理对象。给变量赋值
*/
@Reference//远程注入
private UserService userService;
@RequestMapping("/getByUserId")
public User getByUserId(Integer userId) {
return userService.getByUserId(userId);
}
}
启动类同样加@EnableDubbo
注解
2.4、查看调用情况
接下来我们启动dubbo-web-consumer
、dubbo-services-provider
,注意我们这里要先启动 RPC提供的服务,再启动第一个对外暴露接口的服务
我们来测试一下,发现可以成功完成调用:
3、dubbo高级特性
3.1、dubbo-admin
-
dubbo-admin 管理平台,是图形化的服务管理页面
-
从注册中心中获取到所有的提供者 / 消费者进行配置管理
-
路由规则、动态配置、服务降级、访问控制、权重调整、负载均衡等管理功能
-
dubbo-admin 是一个前后端分离的项目。前端使用vue,后端使用springboot
-
安装 dubbo-admin 其实就是部署该项目
3.2、负载均衡
负载均衡策略(4种):
-
Random :按权重随机,默认值。按权重设置随机概率。
-
RoundRobin :按权重轮询。
-
LeastActive:最少活跃调用数,相同活跃数的随机。
-
ConsistentHash:一致性 Hash,相同参数的请求总是发到同一提供者。
这里我们再加一个服务提供者dubbo-services-provider2
我们来看看ZooKeeper
上的结点情况:
3.3、序列化
3.4、地址缓存
注册中心挂了,服务是否可以正常访问?
-
可以,因为dubbo服务消费者在第一次调用时,会将服务提供方地址缓存到本地,以后在调用则不会访问注册中心。
-
当服务提供者地址发生变化时,注册中心会通知服务消费者。
3.5、超时与重试
-
服务消费者在调用服务提供者的时候发生了阻塞、等待的情形,这个时候,服务消费者会一直等待下去。
-
在某个峰值时刻,大量的请求都在同时请求服务消费者,会造成线程的大量堆积,势必会造成雪崩。
-
服务消费者在调用服务提供者的时候发生了阻塞、等待的情形,这个时候,服务消费者会一直等待下去。
-
在某个峰值时刻,大量的请求都在同时请求服务消费者,会造成线程的大量堆积,势必会造成雪崩。
-
dubbo 利用超时机制来解决这个问题,设置一个超时时间,在这个时间段内,无法完成服务访问,则自动断开连接。
-
使用timeout属性配置超时时间,默认值1000,单位毫秒。
-
设置了超时时间,在这个时间段内,无法完成服务访问,则自动断开连接。
-
如果出现网络抖动,则这一次请求就会失败。
-
Dubbo 提供重试机制来避免类似问题的发生。
-
通过 retries 属性来设置重试次数。默认为 2 次。
3.6、多版本
-
灰度发布:当出现新功能时,会让一部分用户先使用新功能,用户反馈没问题时,再将所有用户迁移到新功能。
-
dubbo 中使用version 属性来设置和调用同一个接口的不同版本
3.7、集群容错
集群容错模式:
-
Failover Cluster:失败重试。默认值。当出现失败,重试其它服务器 ,默认重试2次,使用 retries 配置。一般用于读操作
-
Failfast Cluster :快速失败,只发起一次调用,失败立即报错。通常用于写操作。
-
Failsafe Cluster :失败安全,出现异常时,直接忽略。返回一个空结果。
-
Failback Cluster :失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。
-
Forking Cluster :并行调用多个服务器,只要一个成功即返回。
-
Broadcast Cluster :广播调用所有提供者,逐个调用,任意一台报错则报错。
4、使用nacos作为注册中心
在上面的快速入门中,我们知道了Dubbo
进行RPC
调用的过程,我们可以想注入一个普通Bean
一样,注入一个远程的Bean
,并且还可以配置负载均衡策略,到这里Dubbo
中的五个核心组件我们已经见识过四个了,其中Container
容器就是我们的Spring容器,现在只有最后一个组件:Monitor
没有见过了,接下来我们就开始学习它
Monitor
是监视器的意思,用来监视整个Dubbo
组件的状态,它提供了一个web界面让我们更清楚在查看我们的Dubbo
使用情况,由于Monitor
本身存在一些问题,一般我们会使用dubbo-admin
进行管理,但是 dubbo-2.6.1以后的版本不再有dubbo-admin了
现在一般我们会使用Nacos
作为注册中心和配置管理中心,接下来,我们使用Nacos
重复一遍上面的过程
这里我在docker上面安装一下,这里需要注意的是Nacos
进行RPC通信的端口是9848
和9849
,所以这两个端口也需要开放
docker run -d \
-e MODE=standalone \
--privileged=true \
--restart=always \
-e JVM_XMS=256m \
-e JVM_XMX=256m \
-e MODE=standalone \
-e PREFER_HOST_MODE=hostname \
--log-opt max-size=500m \
-p 8848:8848 \
-p 9848:9848 \
-p 9849:9849 \
--name nacos \
--restart=always \
nacos/nacos-server:1.4.1
在浏览器中输入:http://你的ip地址:8848/nacos,即可访问(账号密码都是Nacos),如果是云服务器记得开安全组
4.1、nacos 添加命名空间
我们需要在Nacos上添加一个命名空间,用来专门作为Dubbo的空间
4.2、添加依赖
这里的方式是非
SpringCloud
的方式,后面再看SpringCloud
的方式
首先我们需要改造一下之前的pom
依赖,将SpringCloud
和Nacos
的依赖添加进去
首先在父工程的pom文件中的<dependencyManagement>
中引入Dubbo Registry Nacos
的依赖:
然后在对应的consumer
和provider
中也要引入这个依赖,版本和自己Nacos依赖一样即可
<!-- Dubbo Registry Nacos -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-nacos</artifactId>
<version>3.0.8</version>
</dependency>
在consumer
和provider
的application.yml中添加nacos地址:
# 这里的配置属性只是基础配置,如需更多功能配置,请自行扩展
dubbo:
application:
name: dubbo-provider # 这里取名字的时候要区分开
registry:
address: nacos://localhost:8848
parameters:
namespace: 60f00a4f-2f26-4b5d-8f88-7fe63b85455f
protocol:
name: dubbo
port: 20880