框架之Dubbo基础
一、系统架构的演进过程
随着互联网的发展,网站应用的规模也在不断的扩大,进而导致系统架构也在不断的进行变化。
从互联网早期到现在,系统架构大体经历了以下几个过程:
单体应用架构 —> 垂直应用架构 —> 分布式架构 —> SOA架构 —> 微服务架构
1. 1 单体应用架构
优点:
- 项目架构简单,小型项目的话, 开发成本低
- 项目部署在一个节点上, 维护方便
缺点:
- 项目模块之间紧密耦合,单点容错率低
- 无法针对不同模块进行针对性优化和水平扩展
1.2 垂直应用架构
优点:
- 可以针对不同模块进行优化和水平扩展
- 一个系统的问题不会影响到其他系统,提高单点容错率
缺点:
- 系统之间相互独立,无法进行相互调用,会有重复的开发任务
1.3 分布式架构
优点:
- 抽取公共的功能为服务层,提高代码复用性
缺点:
- 调用关系错综复杂,难以维护
1.4 SOA架构
优点:
- 更易维护
- 更高的可用性
- 更好的灵活性,根据需求变化,可重新编排服务或应用程序
缺点:
-
服务划分很困难
-
服务的编排是否得当
-
如果选择的接口标准有问题,会带来系统的额外开销和不稳定性
-
对IT硬件资产还谈不上复用
-
主流实现方式接口很多,很难统一
-
主流实现方式只局限于不带界面的服务的共享
1.5 微服务架构
微服务架构:把一个大型的单个应用程序和服务拆分为数个甚至数十个的支持微服务,它可扩展单个组件而不是整个的应用程序堆栈,从而满足服务等级协议
优点:
- 复杂度可控
- 独立按需扩展,技术选型灵活
- 容错,可用性高
缺点:
- 分布式系统产生的复杂性
- 服务间通信成本
- 数据一致性问题
- 系统集成测试复杂
二、Dubbo
Apache Dubbo是一款高性能的Java RPC框架,由阿里开发,2018年捐献给Apache基金会。
2.1 RPC介绍
RPC(remote procedure call),即远程过程调用,通俗的解释为让程序员像调用本地方法一样来调用远程方法。
需要注意的是RPC并不是一个具体的技术,而是指整个网络远程调用过程。
Java中的RPC框架比较多,广泛使用的有RMI
、Hessian
、Dubbo
等。
简单来说:A机器通过网络调用B机器的某个方法,这个过程就称为RPC。
核心能力:
- 面向接口的远程方法调用
- 智能容错和负载均衡
- 服务自动注册和发现
2.2 Dubbo架构
节点角色说明:
节点 | 角色名称 |
---|---|
Provider | 暴露服务的服务提供方 |
Consumer | 调用远程服务的服务消费方 |
Registry | 服务注册与发现的注册中心 |
Monitor | 监控中心 |
Container | 服务运行容器 |
调用关系说明:
0. 服务容器负责启动,加载,运行服务提供者。
1. 服务提供者在启动时,向注册中心注册自己提供的服务。
2. 服务消费者在启动时,向注册中心订阅自己所需的服务。
3. 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推
送变更数据给消费者。
4. 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,
如果调用失败,再选另一台调用。
5. 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计
数据到监控中心。
三、Dubbo快速使用
3.1 安装并启动Zookeeper
Dubbo官方推荐使用 zookeeper 注册中心。
注册中心负责服务地址的注册与查找,相当于目录服务,服务提供者和消费者只在启动时与注册中心交互,注册中心不转发请求,压力较小。
3.2 创建服务提供者模块
- 导入依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
</parent>
<dependencies>
<!--基础环境-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!--dubbo的起步依赖-->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.8</version>
</dependency>
<!-- zookeeper的api管理依赖 -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.2.0</version>
</dependency>
<!-- zookeeper依赖 -->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.12</version>
</dependency>
</dependencies>
- 编写service接口
public interface UserService {
String sayHello(String name);
}
- 编写service实现
@DubboService // 1.将对象交给ioc容器 2.将对象注册到zookeeper中
public class UserServiceImpl implements UserService {
@Override
public String sayHello(String name) {
return "hello " + name;
}
}
- application.yml配置
# dubbo.application.name 服务名称,一般跟模块名称一致即可
# dubbo.registry.address 注册中心的连接地址
# dubbo.protocol.name 当前服务的访问协议,支持dubbo、rmi、hessian、http、webservice、rest、redis等
# dubbo.protocol.port 当前服务的访问端口
# dubbo.scan.base-packages 包扫描
dubbo:
application:
name: day03-dubbo-demo-provider
registry:
address: zookeeper://127.0.0.1:2181
protocol:
name: dubbo
port: 20880
scan:
base-packages: com.itheima.service
- 启动类
@SpringBootApplication
public class ProviderApp {
public static void main(String[] args) {
SpringApplication.run(ProviderApp.class, args);
}
}
3.3 创建服务消费者模块
- 导入依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
</parent>
<dependencies>
<!--web环境-->
<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>2.7.8</version>
</dependency>
<!-- zookeeper的api管理依赖 -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.2.0</version>
</dependency>
<!-- zookeeper依赖 -->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.12</version>
</dependency>
</dependencies>
- 复制service接口
- 编写controller
@RestController
public class UserController {
@DubboReference // 连接zookeeper获取rpc远程地址完成调用
private UserService userService;
@GetMapping("/user/hello/{name}")
public String sayHello(@PathVariable String name){
return userService.sayHello(name);
}
}
- application.yml配置
# dubbo.application.name 服务名称,一般跟模块名称一致即可
# dubbo.registry.address 注册中心的连接地址
# dubbo.scan.base-packages 包扫描
dubbo:
application:
name: day03-dubbo-demo-consumer
registry:
address: zookeeper://127.0.0.1:2181
scan:
base-packages: com.baidu.controller
- 启动类
@SpringBootApplication
public class ConsumerApp {
public static void main(String[] args) {
SpringApplication.run(ConsumerApp.class, args);
}
}
3.4 RPC执行流程
3.5 接口模块抽取
目前存在的问题:
- service接口代码重复
- 两个工程中的service代码必须完全一致,后期修改麻烦
-
抽取公共接口模块
-
提供者和消费者依赖此模块
-
maven模块依赖关系
四、Dubbo管理控制台
在开发时,当程序员需要知道Zookeeper注册中心都注册了哪些服务,有哪些消费者来消费这些服务,可以通过部署一个管理中心来实现。
其实管理中心就是一个web应用,原来是war(2.6版本以前)包需要部署到tomcat即可。现在是jar包可以直接通过java命令运行,主要包含:服务管理 、 路由规则、动态配置、服务降级、访问控制、权重调整、负载均衡等管理功能。
4.1 下载和安装
1. 从git上下载项目 https://github.com/apache/dubbo-admin/tree/master
2. 修改项目下的application.properties文件
dubbo.registry.address=zookeeper://zk所在机器ip:zk端口
dubbo.admin.root.password=root
dubbo.admin.guest.password=guest
3. 切换到项目所在的路径 使用mvn 打包
mvn clean package -Dmaven.test.skip=true
4.2 使用
- java命令运行管理控制台的jar包
- 打开浏览器,输入http://127.0.0.1:7001/ ,登录用户名和密码均为root
- 通过服务治理下
服务 提供者 消费者
可以观察具体应用的信息
五、Dubbo使用细节
5.1 序列化
dubbo底层是需要通过网络传输数据的,因此被传输的对象必须实现序列化接口
问题: java.lang.IllegalStateException: Serialized class com.itheima.dubbo.domain.User must implement java.io.Serializable
原因: 我们的实体没有实现序列化接口
5.2 启动时检查(开发阶段)
启动时检查,配置在服务消费者一方,用于服务消费者在启动的时候主动检查注册中心或者服务提供者是否准备好提供服务
- 如果配置为false,代表不检查
- 如果配置为true,代表检查,一旦检查到服务提供者未准备好,就会直接抛异常
dubbo:
consumer:
check: false
registry:
check: false
5.3 服务超时
服务消费者在调用服务提供者的时候可能会发生阻塞、等待的情形,这个时候,如果服务消费者一直等下去,就会造成线程堆积,服务宕机。
全局配置(开发阶段):
# dubbo允许提供者端设置一个服务的超时时间(默认为1s),如果超过这个时间,服务无法作出反应,直接终止线程。
dubbo:
provider:
timeout: 3000
retries: 0
# 这个时间可以设置在服务调用者一端,也可以设置在服务提供者一端(如果同时设置了,消费者提供者优先级高)
dubbo:
consumer:
timeout: 5000
retries: 0
局部配置(生产阶段):
- dubbo还支持在类上定义超时时间,其优先级高于全局配置
//1. 在服务提供端声明
@DubboService(timeout = 3000, retries = 0)
public class UserServiceImpl implements UserService{}
//2. 在服务调用端声明
public class Controller{
@DubboReference(timeout = 5000,retries = 0)
private UserService userService;
}
服务重试:
dubbo设置了超时时间,如果在这个时间段内,无法完成服务访问,则自动断开连接。
为了弥补这一特性的缺点,保证系统健壮,dubbo提供了重试机制。
public class Controller{
@DubboReference(timeout = 2000,retries = 4)
private UserService userService;
}
服务降级:
当一个请求发生超时,一直等待着服务响应,那么在高并发情况下,很多请求都是因为这样一直等着响应,
直到服务资源耗尽产生宕机,而宕机之后会导致分布式其他服务调用该宕机的服务也会出现资源耗尽宕机,
这样下去将导致整个分布式服务都瘫痪。
这时候需要对服务开启容错和降级
在消费者端编写一个接口实现类,提供该方法的默认返回值
public class UserServiceMock implements UserService {
@Override
public String sayHello(String name) {
return "mock " + name;
}
@Override
public User findById(Integer id) {
return new User(id, "mock", 18);
}
}
在消费者端在controller中设置@Reference中的mock属性
@RestController
public class UserController {
// 连接zookeeper获取rpc远程地址完成调用
@DubboReference(timeout = 2000,retries = 0,mock = "com.baidu.service.UserServiceMock")
private UserService userService;
@GetMapping("/user/hello/{name}")
public String sayHello(@PathVariable String name) {
return userService.sayHello(name);
}
@GetMapping("/user/findById/{id}")
public User findById(@PathVariable Integer id) {
return userService.findById(id);
}
}
5.4 负载均衡
在高并发情况下,一个服务往往需要以集群的形式对外工作。
那么服务器消费者的一个请求到底应该由哪一个服务提供者去处理就成了个问题,这个时候就需要配置对应的负载均衡策略了。
dubbo支持四种负载均衡策略:
- random(默认):按权重随机选择,这是默认值
- roundrobin:按权重轮询选择
- leastactive:最少活跃调用数,相同活跃数的随机选择
- consistenthash:一致性Hash,相同参数的请求总是发到同一提供者
5.5 多版本
很多时候,当项目出现新功能时,会让一部分用户先使用新功能,用户反馈没问题时,再将所有用户迁移到新功能
dubbo使用version属性来设置和调用同一个接口的不同版本