阿里P8大牛整理的这份Dubbo面试题集锦,GitHub上仅一天获赞上万

243 阅读11分钟

Hello,今天给各位童鞋们分享的是Dubbo,赶紧拿出小本子记下来吧

image.png

一 软件架构的演进过程

集群:每个人干的活是一样的. 共同分担这件事情

分布式:多个人共同完成一件"大"事情

分布式一般都伴随着集群

随着互联网的发展,网站应用的规模也在不断的扩大,进而导致系统架构也在不断的进行变化。

从互联网早起到现在,系统架构大体经历了下面几个过程: 单体应用架构—>垂直应用架构—>分布式架构—>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架构 【面试题】

节点角色说明:

调用关系说明:

服务容器负责启动,加载,运行服务提供者。

服务提供者在启动时,向注册中心注册自己提供的服务。

服务消费者在启动时,向注册中心订阅自己所需的服务。

注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。

服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。

服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计

数据到监控中心。

三 Dubbo快速入门【重点】

1 服务注册中心zk

Zookeeper介绍

Dubbo官方推荐使用 zookeeper 注册中心。注册中心负责服务地址的注册与查找,相当于目录服务,服务提供者和消费者只在启动时与注册中心交互,注册中心不转发请求,压力较小。

Zookeeper 是 Apacahe Hadoop 的子项目,是一个树型的目录服务,支持变更推送,适合作为Dubbo 服务的注册中心,工业强度较高,可用于生产环境。

Zookeeper安装与启动

将资料中的zookeeper-3.4.6.zip解压即安装(无中文和特殊符号路径)

进入安装路径的bin目录,双击zkServer.cmd即可启动zookeeper服务(依赖jdk环境)

2 服务提供者

建模块,导依赖

org.springframework.boot spring-boot-starter-parent 2.2.2.RELEASE org.springframework.boot spring-boot-starter org.apache.dubbo dubbo-spring-boot-starter 2.7.5 org.apache.curator curator-recipes 4.2.0 org.apache.zookeeper zookeeper 3.4.12

编写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 服务消费者

建模块,导依赖

org.springframework.boot spring-boot-starter-parent 2.2.2.RELEASE org.springframework.boot spring-boot-starter-web org.apache.dubbo dubbo-spring-boot-starter 2.7.5 org.apache.curator curator-recipes 4.2.0 org.apache.zookeeper zookeeper 3.4.12

复制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执行流程

提供者启动成功之后,告诉注册中心我的地址:

5 接口模块抽取

目前代码存在问题:

service接口代码重复

两个工程中的service代码必须完全一致,后期修改麻烦

抽取公共接口模块

org.projectlombok lombok 1.18.10

提供者和消费者依赖此模块

maven模块依赖关系

注意:

  1. web层是消费者
  2. service和dao属于提供者
  3. service的接口抽取成单独的模块
  4. 消费者和提供者都需要依赖service的接口模块
  5. 接口模块需要依赖domain模块

6 知识小结

在使用dubbo之后:

先运行注册中心

再运行服务提供者

最后运行服务消费者

7 常见异常

1.注册中心没有启动

2.注册中心启动后,先启动了消费者

错误信息:在注册中心上没有找到可用的xxxService提供者

解决思路:

  1. 看提供者启动成功没有
  2. 再看提供者的配置文件写错没有(4行)
  3. 最后再看Service注解有没有用错

3.实体类没有实现序列化接口

四 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); }

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 服务降级

当一个请求发生超时,一直等待着服务响应,那么在高并发情况下,很多请求都是因为这样一直等着响应,

直到服务资源耗尽产生宕机,而宕机之后会导致分布式其他服务调用该宕机的服务也会出现资源耗尽宕机,

这样下去将导致整个分布式服务都瘫痪。

异常降级:比如访问一个请求发生了错误,但是我们希望有一个兜底的策略,这个时候可以返回一个默认的结果

方式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支持四种负载均衡策略:

  • random:按权重随机选择,这是默认值。
  • roundrobin:按权重轮询选择。
  • leastactive:最少活跃调用数,相同活跃数的随机选择。
  • consistenthash:一致性Hash,相同参数的请求总是发到同一提供者。

6 多版本

很多时候,当项目出现新功能时,会让一部分用户先使用新功能,用户反馈没问题时,再将所有用户迁移到新功能

dubbo使用version属性来设置和调用同一个接口的不同版本

消费者指定某个版本

@RestController public class UserController {

@Reference(version = "v2.0")
private UserService userService;

}

好啦,今天的文章就到这里了,希望能够帮助到屏幕前迷茫的你们