我们来回顾一下不断演进的服务架构
单体架构 -> 分布式架构 -> 微服务
单体架构:将业务的所有功能集中在一个项目中开发,打成一个包部署
优点:
- 架构简单
- 部署成本低
缺点:
- 耦合度高(维护困难、升级困难)
分布式架构:根据业务功能对系统做拆分,每个业务功能模块作为独立项目开发,称为一个服务
优点:
- 降低服务耦合
- 有利于服务升级和拓展
缺点:
- 服务调用关系错综复杂
分布式架构虽然降低了服务耦合,但是服务拆分时也有很多问题需要思考:
- 服务拆分的粒度如何界定?
- 服务之间如何调用?
- 服务的调用关系如何管理?
人们需要制定一套行之有效的标准来约束分布式架构,进而有了我们现在的微服务,微服务的上述特性其实是在给分布式架构制定一个标准,进一步降低服务之间的耦合度,提供服务的独立性和灵活性。做到高内聚,低耦合。
因此,可以认为微服务是一种经过良好架构设计的分布式架构方案 。
微服务的架构特征:
- 单一职责:微服务拆分粒度更小,每一个服务都对应唯一的业务能力,做到单一职责
- 自治:团队独立、技术独立、数据独立,独立部署和交付
- 面向服务:服务提供统一标准的接口,与语言和技术无关
- 隔离性强:服务调用做好隔离、容错、降级,避免出现级联问题
现在国内用的最多的微服务就是SpringCloud
,它的核心组件包括:
我们在学习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
为我们提供的主要功能有:
- 面向接口代理的高性能 RPC 调用
- 智能容错和负载均衡。
- 服务自动注册和发现
- 高度可扩展能力
- 运行期流量调度
- 可视化的服务治理与运维
我们总结一下Dubbo
的作用: Dubbo是一站式的微服务解决方案
因为Dubbo是使用Zookeeper进行服务注册发现,所以我们需要安装一个zk,后续可以使用Nacos
作为注册中心
我们可以在Linux环境下搭建一个Zookeeper,这里笔者采用的方式是Docker的方式
如果读者对Zookeeper的使用还比较陌生,可以看笔者的这篇文章:docker安装zookeeper&zookeeper基本使用(非常详细)
docker run -d \
-e TZ="Asia/Shanghai" \
-p 2181:2181 \
--name zookeeper \
--log-opt max-size=500m \
--restart always zookeeper
我们这里来新建一个项目用来演示一下Dubbo
的使用过程:
父模块的依赖如下,采用的都是现在最新的版本,如果版本发生冲突可以试着降低版本
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.hnit</groupId>
<artifactId>dubbo-demo</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>dubbo-provider</module>
<module>dubbo-comsumer</module>
<module>dubbo-provider2</module>
<module>dubbo-api</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.2</version>
</parent>
<properties>
<java.version>1.8</java.version>
<dubbo-boot.version>3.0.8</dubbo-boot.version>
<zkclient.version>5.1.0</zkclient.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>${zkclient.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${web.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
dubbo-api
依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>dubbo-demo</artifactId>
<groupId>cn.hnit</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>dubbo-api</artifactId>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>
接着在这两个子模块添加依赖
dubbo-provider
<dependencies>
<!-- 不需要对外暴露接口,仅需要给其他模块进行RPC调用 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-x-discovery</artifactId>
</dependency>
<dependency>
<groupId>cn.hnit</groupId>
<artifactId>dubbo-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
配置文件:
# 这里的配置属性只是基础配置,如需更多功能配置,请自行扩展
dubbo:
application:
name: dubbo-provider
registry:
address: zookeeper://volunteer.fengxianhub.top:20016
protocol:
name: dubbo
port: 20880
第二个dubbo-provider
和上面一样,只是要将dubbo通信的端口修改一下,例如改为20881
dubbo-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>cn.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>
配置文件:
server:
port: 8080 #Tomcat端口
# 这里的配置属性只是基础配置,如需更多功能配置,请自行扩展
dubbo:
application:
name: dubbo-provider
registry:
address: zookeeper://volunteer.fengxianhub.top:20016
protocol:
name: dubbo
port: 20882
接着我们在API
中写几个需要进行RPC的接口,这个包一般用来定义接口,提供给其他包进行实现,读者也可以不写这个包
User类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {
private static final long serialVersionUID = 8728327146677888239L;
private Integer userId;
private String userName;
}
接口UserService
public interface UserService {
User getByUserId(Integer userId);
}
接着我们写调用逻辑和对外暴露的接口,对外暴露的接口仅写在dubbo-consumer
模块中,在此之前我们需要先写服务中心的RPC调用逻辑,也就是dubbo-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-consumer
、dubbo-provider
、dubbo-provider2
三个服务,注意我们这里要先启动后面两个RPC提供的服务,再启动第一个对外暴露接口的服务
我们来测试一下,发现可以完成调用:
我们再来做一万次压测,看看两个
provider
服务的负载情况
这里压测笔者使用AB
压力测试,启用100个线程共发送一万个请求
来看看两个provider
的负载,provider1
:
provider2
:
可以看到请求被平均发到两个服务上了,起到了负载均衡的作用
我们再来看看ZooKeeper
上的结点情况:
在上面的快速入门中,我们知道了Dubbo
进行RPC
调用的过程,我们可以想注入一个普通Bean
一样,注入一个远程的Bean
,并且还可以配置负载均衡策略,到这里Dubbo
中的五个核心组件我们已经见识过四个了,其中Container
容器就是我们的Spring容器,现在只有最后一个组件:Monitor
没有见过了,接下来我们就开始学习它
Monitor
是监视器的意思,用来监视整个Dubbo
组件的状态,它提供了一个web界面让我们更清楚在查看我们的Dubbo
使用情况,由于Monitor
本身存在一些问题,一般我们会使用dubbo-admin
进行管理,但是 dubbo-2.6.1以后的版本不再有dubbo-admin了
现在一般我们会使用Nacos
作为注册中心和配置管理中心,接下来,我们使用Nacos
重复一遍上面的过程
Nacos的使用可以看笔者的另一篇文章:eureka&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),如果是云服务器记得开安全组
我们需要在Nacos上添加一个命名空间,用来专门作为Dubbo的空间
这里的方式是非
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://volunteer.fengxianhub.top:20026
protocol:
name: dubbo
port: 20880