dubbo
一 软件架构的演进过程
集群:每个人干的活是一样的. 共同分担这件事情
分布式:多个人共同完成一件"大"事情
分布式一般都伴随着集群
随着互联网的发展,网站应用的规模也在不断的扩大,进而导致系统架构也在不断的进行变化。
从互联网早起到现在,系统架构大体经历了下面几个过程: 单体应用架构—>垂直应用架构—>分布式架构—>SOA架构—>微服务架构
1 单体应用架构
单体应用结构,就是将一个系统的所有模块做成一个web项目,然后部署到一台tomcat服务器上
* 优点:
- 项目架构简单,开发、测试、部署成本低
- 项目部署在一个节点上, 后期维护方便
* 缺点:
- 项目模块之间紧密耦合,单点容错率低
- 无法针对不同模块进行针对性优化和水平扩展
2 垂直应用架构
垂直应用架构,就是将原来的一个系统拆成成多个模块,然后每个模块部署在一台tomcat服务器上
* 优点:
- 可以针对不同模块进行优化和水平扩展
- 一个系统的问题不会影响到其他系统,提高单点容错率
* 缺点:
- 系统之间相互独立,无法进行相互调用,会有重复的开发任务(造成了代码冗余)
3 分布式架构
分布式架构就是指将
服务层(service)
单独启动,对外提供服务,在controller中可以通过远程调用
访问服务层中的方法
* 优点:
- 抽取公共的功能为服务层,提高代码复用性
* 缺点:
- 调用关系错综复杂,难以维护
4 SOA架构
SOA结构,在分布式架构的基础上,增加一个
调度中心
对系统进行实时管理。
面试直达: 聊一聊
集群
和分布式
的区别
* 集群:
多台服务器重复完成同一个任务,即同一个任务部署在多台服务器上
* 分布式:
多台服务器协同完成同一个任务,即同一个任务拆分为多个子任务,多个子任务部署在多台服务器上协同完成同一个任务
二 Dubbo概述
1 Dubbo简介
Apache Dubbo是一款高性能的Java RPC框架。其前身是阿里巴巴公司开源的一个高性能、轻量级的开源Java RPC(远程过程调用)框架,可以和Spring框架无缝集成。
发展历程
- Dubbo是阿里巴巴内部使用的分布式业务框架,2012年由阿里巴巴开源
- 在很短时间内,Dubbo就被许多互联网公司所采用,并产生了许多衍生版本,如网易,京东,新浪,当当等等
- 由于阿里策略变化,2014年10月Dubbo停止维护。随后部分互联网公司公开了自行维护的Dubbo版本,比较著名的如当当DubboX
- 经过三年的沉寂,在2017年9月,阿里宣布重启Dubbo项目,并决策在未来对开源进行长期持续的投入
- 随后Dubbo开始了密集的更新,并将停摆三年以来大量分支上的特性及缺陷修正快速整合
- 2018.2月,阿里将Dubbo捐献给Apache基金会,Dubbo成为Apache孵化器项目
RPC介绍
- RPC全称为remote procedure call,即远程过程调用
- 需要注意的是RPC并不是一个具体的技术,而是指整个网络远程调用过程
- Java中的RPC框架比较多,广泛使用的有RMI、Hessian、Dubbo等
简单来说:A机器通过网络调用B机器的某个方法,这个过程就称为RPC
核心能力
- 面向接口的远程方法调用
- 智能容错和负载均衡
- 服务自动注册和发现
2 Dubbo架构 【面试题】
https://dubbo.apache.org/zh/docs/v2.7/user/preface/architecture/[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jfqgcyFZ-1623825356406)(dubbo.assets/image-20201024170913490.png)]
节点角色说明:
节点 | 角色名称 |
---|---|
Provider | 暴露服务的服务提供方 |
Consumer | 调用远程服务的服务消费方 |
Registry | 服务注册与发现的注册中心 |
Monitor | 统计服务的调用次数和调用时间的监控中心 |
Container | 服务运行容器 |
调用关系说明:
- 服务容器负责启动,加载,运行服务提供者。
- 服务提供者在启动时,向注册中心注册自己提供的服务。
- 服务消费者在启动时,向注册中心订阅自己所需的服务。
- 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推
送变更数据给消费者。 - 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,
如果调用失败,再选另一台调用。 - 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计
数据到监控中心。
三 Dubbo快速入门【重点】
1 服务注册中心zk
Zookeeper介绍
Dubbo官方推荐使用 zookeeper 注册中心。注册中心负责服务地址的注册与查找,相当于目录服务,服务提供者和消费者只在启动时与注册中心交互,注册中心不转发请求,压力较小。
Zookeeper 是 Apacahe Hadoop 的子项目,是一个树型的目录服务,支持变更推送,适合作为Dubbo 服务的注册中心,工业强度较高,可用于生产环境。
Zookeeper安装与启动
- 将资料中的zookeeper-3.4.6.zip解压即安装(无中文和特殊符号路径)
- 进入安装路径的bin目录,双击
zkServer.cmd
即可启动zookeeper服务(依赖jdk环境)
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.5</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实现
@Service // 使用dubbo的注解: 1.将对象交给ioc容器 2.将对象服务暴露给注册中心
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: 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 服务消费者
建模块,导依赖
<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.5</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);
}
编写controller
@RestController
public class UserController {
@Reference // dubbo注解:连接注册中心获取远程地址,创建代理对象
private UserService userService;
@GetMapping("/sayHello")
public String sayHello(String name) {
return userService.sayHello(name);
}
}
application.yml
# dubbo.application.name 服务名称,一般跟模块名称一致即可
# dubbo.registry.address 注册中心的连接地址
# dubbo.scan.base-packages 包扫描
dubbo:
application:
name: dubbo-demo-consumer
registry:
address: zookeeper://127.0.0.1:2181
scan:
base-packages: com.itheima.web
启动类
@SpringBootApplication
public class ConsumerApp {
public static void main(String[] args) {
SpringApplication.run(ConsumerApp.class, args);
}
}
4 RPC执行流程
提供者启动成功之后,告诉注册中心我的地址:
dubbo://192.168.63.104:20880/com.it…UserService?method=sayHello…
5 接口模块抽取
目前代码存在问题:
- service接口代码重复
- 两个工程中的service代码必须完全一致,后期修改麻烦
抽取公共接口模块
dubbo-interface
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
</dependency>
</dependencies>
提供者和消费者依赖此模块
maven模块依赖关系
注意:
- web层是消费者
- service和dao属于提供者
- service的接口抽取成单独的模块
- 消费者和提供者都需要依赖service的接口模块
- 接口模块需要依赖domain模块
6 知识小结
在使用dubbo之后:
- 先运行注册中心
- 再运行服务提供者
- 最后运行服务消费者
7 常见异常
1.注册中心没有启动
2.注册中心启动后,先启动了消费者
错误信息:在注册中心上没有找到可用的xxxService提供者
解决思路:
- 看提供者启动成功没有
- 再看提供者的配置文件写错没有(4行)
- 最后再看Service注解有没有用错
3.实体类没有实现序列化接口
四 Dubbo管理控制台
- 主要包含:服务管理 、 路由规则、动态配置、服务降级、访问控制、权重调整、负载均衡等管理功能
- 如我们在开发时,需要知道Zookeeper注册中心都注册了哪些服务,有哪些消费者来消费这些服务。我们可以通过部署一个管理中心来实现。
- 其实管理中心就是一个
web应用
,原来是war(2.6版本以前)包需要部署到tomcat即可。现在是jar包可以直接通过java命令运行。
1 下载和安装
1. 从git上下载项目 https://github.com/apache/dubbo-admin/tree/master
2. 修改项目下的application.properties文件
spring.root.password=root #roog用户的密码就是root
spring.guest.password=guest#guest用户的密码就是guest
dubbo.registry.address=zookeeper://127.0.0.1:2181 #注册中心地址
3. 切换到项目所在的路径 使用mvn 打包
mvn clean package -Dmaven.test.skip=true
4. java命令运行环境
java -jar 对应的jar包
1.下载.
2.修改zk的地址:若访问的本地的zk的话,不需要修改
2 使用
- 打开浏览器,输入http://127.0.0.1:7001/ ,登录用户名和密码均为root
- 通过服务治理下
服务 应用 提供者 消费者
可以观察具体应用的信息
注意:管理控制需要连接zookeeper注册中心
五 Dubbo使用细节【面试】
1 序列化
dubbo底层是需要通过网络传输数据的,因此被传输的对象必须实现序列化接口
User
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Integer id;
private String name;
private Integer age;
}
UserService
在
dubbo-demo-interface
模块下
User findById(Integer id);
UserServiceImpl
在
dubbo-demo-provider
模块下
@Override
public User findById(Integer id) {
return new User(id, "jack", 18);
}
UserController
在
dubbo-demo-consumer
模块下
@GetMapping("/findById")
public User findById(Integer id) {
return userService.findById(id);
}
问题描述
- 问题: java.lang.IllegalStateException: Serialized class com.itheima.dubbo.domain.User must implement java.io.Serializable
- 原因: 我们的实体没有实现序列化接口
2 启动时检查
启动时检查,配置在服务消费者一方,用于服务消费者在启动的时候主动检查注册中心中有无相应的服务提供者
- 如果配置为false,代表不检查
- 如果配置为true(默认),代表检查,一旦检查到服务提供者未准备好,就会直接抛异常
dubbo:
consumer:
check: false
3 服务超时和重试
服务消费者在调用服务提供者的时候可能会发生阻塞、等待的情形,这个时候,如果服务消费者会一直等下去,就会造成线程堆积,服务宕机(雪崩)。
为了避免这个问题的发生,dubbo允许设置一个服务的超时时间(默认为1s),如果超过这个时间,服务无法作出反应,默认会重试2次(若有其他的提供者会重连其他的提供者),若还没有反应,直接终止线程,抛出异常。
这个时间可以设置在服务调用者一端,也可以设置在服务提供者一端(如果同时设置了,消费者比提供者优先级高)
幂等性:多次操作的结果都是一致的 例如:查询
非幂等性:多次操作的结果不一致的 例如:添加,删除
- 例如: update account set money = 100 where id = 1; 这个是幂等性
- 例如: update account set money = money + 100 where id = 1; 这个是非幂等性
全局配置
若只在一方设置的话,建议在提供者方设置,并且消费者的默认值也会使用在提供者设置的这个值.
dubbo:
provider:
timeout: 3000
retries: 0
#-------------------------------------------------------------------
dubbo:
consumer:
timeout: 5000
retries: 0
局部配置
dubbo还支持在类上定义超时时间,其优先级高于全局配置
//1. 在服务提供端声明
@Service(timeout = 3000, retries = 0)
public class UserServiceImpl implements UserService{}
//2. 在消费端声明
public class Controller{
@Reference(timeout = 5000,retries = 0)
private UserService userService;
}
4 服务降级
https://dubbo.apache.org/zh/docs/v2.7/user/examples/service-downgrade/
当一个请求发生超时,一直等待着服务响应,那么在高并发情况下,很多请求都是因为这样一直等着响应,
直到服务资源耗尽产生宕机,而宕机之后会导致分布式其他服务调用该宕机的服务也会出现资源耗尽宕机,
这样下去将导致整个分布式服务都瘫痪。
异常降级:比如访问一个请求发生了错误,但是我们希望有一个兜底的策略,这个时候可以返回一个默认的结果
方式1:
出现问题的时候返回null
@RestController
public class Controller {
@Reference(mock="fail:return null")
UserService userService;
}
方式2:
在服务的消费者中,定义服务降级处理类 也要实现服务接口
public class UserServiceFailback implements UserService {
@Override
public User findById(Integer id) {
User user = new User();
user.setId(-1);
user.setName("获取名称出错");
return user;
}
//...
}
@RestController
@RequestMapping("user")
public class UserController {
// 指定服务降级处理类
@Reference(mock = "com.itheima.service.failback.UserServiceFailback")
UserService userService;
//....
}
5 负载均衡
在高并发情况下,一个服务往往需要以集群的形式对外工作。
那么服务器消费者的一个请求到底应该由哪一个服务提供者去处理就成了个问题,这个时候就需要配置对应的负载均衡策略了。
再搞出一台提供者
复制提供者模块,改下模块名称和端口
提供者指定负载策略(提倡)
@RestController
public class UserController {
@Reference(loadbalance = "consistenthash")
private UserService userService;
}
dubbo支持四种负载均衡策略:
https://dubbo.apache.org/zh/docs/v2.7/user/examples/loadbalance/
- random:按权重随机选择,这是默认值。
- roundrobin:按权重轮询选择。
- leastactive:最少活跃调用数,相同活跃数的随机选择。
- consistenthash:一致性Hash,相同参数的请求总是发到同一提供者。
6 多版本
很多时候,当项目出现新功能时,会让一部分用户先使用新功能,用户反馈没问题时,再将所有用户迁移到新功能
dubbo使用version属性来设置和调用同一个接口的不同版本
消费者指定某个版本
@RestController
public class UserController {
@Reference(version = "v2.0")
private UserService userService;
}