您当前的位置: 首页 >  面试

庄小焱

暂无认证

  • 2浏览

    0关注

    805博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

Dubbo——大厂面试问题集合

庄小焱 发布时间:2021-04-01 15:41:53 ,浏览量:2

摘要

本文将总结Dubbo早大厂面试中的问题和解答。

一、Dubbo设计模式面试问题 1.1 知道什么是SPI嘛?

SPI 是 Service Provider Interface,主要用于框架中,框架定义好接口,不同的使用者有不同的需求,因此需要有不同的实现,而 SPI 就通过定义一个特定的位置,Java SPI 约定在 Classpath 下的 META-INF/services/ 目录里创建一个以服务接口命名的文件,然后文件里面记录的是此 jar 包提供的具体实现类的全限定名。所以就可以通过接口找到对应的文件,获取具体的实现类然后加载即可,做到了灵活的替换具体的实现类。

1.2 为什么Dubbo不用JDK的SPI而是要自己实现?

答:因为 Java SPI 在查找扩展实现类的时候遍历 SPI 的配置文件并且将实现类全部实例化,假设一个实现类初始化过程比较消耗资源且耗时,但是你的代码里面又用不上它,这就产生了资源的浪费。

因此 Dubbo 就自己实现了一个 SPI,给每个实现类配了个名字,通过名字去文件里面找到对应的实现类全限定名然后加载实例化,按需加载。

1.3 Dubbo为什么默认用 Javassist?

ASM 比 Javassist 更快,但是没有快一个数量级,而Javassist 只需用字符串拼接就可以生成字节码,而 ASM 需要手工生成,成本较高,比较麻烦。

二、Dubbo服务注册发现面试问题 2.1 看过源码,服务暴露的流程?
  • 服务的暴露起始于 Spring IOC 容器刷新完毕之后,会根据配置参数组装成 URL, 然后根据 URL 的参数来进行本地或者远程调用。
  • 会通过 proxyFactory.getInvoker,利用 javassist 来进行动态代理,封装真的实现类,然后再通过 URL 参数选择对应的协议来进行 protocol.export,默认是 Dubbo 协议。
  • 在第一次暴露的时候会调用 createServer 来创建 Server,默认是 NettyServer。
  • 然后将 export 得到的 exporter 存入一个 Map 中,供之后的远程调用查找,然后会向注册中心注册提供者的信息。基本上就是这么个流程,说了这些差不多了,太细的谁都记住不。

2.2 看过源码,服务引入的流程?

服务的引入时机有两种,第一种是饿汉式,第二种是懒汉式。

饿汉式就是加载完毕就会引入,懒汉式是只有当这个服务被注入到其他类中时启动引入流程,默认是懒汉式。

会先根据配置参数组装成 URL ,一般而言我们都会配置的注册中心,所以会构建 RegistryDirectory 向注册中心注册消费者的信息,并且订阅提供者、配置、路由等节点。

得知提供者的信息之后会进入 Dubbo 协议的引入,会创建 Invoker ,期间会包含 NettyClient,来进行远程通信,最后通过 Cluster 来包装 Invoker,默认是 FailoverCluster,最终返回代理类。

2.3 看过源码,那说下服务调用的流程?
  • 调用某个接口的方法会调用之前生成的代理类,然后会从 cluster 中经过路由的过滤、负载均衡机制选择一个 invoker 发起远程调用,此时会记录此请求和请求的 ID 等待服务端的响应。
  • 服务端接受请求之后会通过参数找到之前暴露存储的 map,得到相应的 exporter ,然后最终调用真正的实现类,再组装好结果返回,这个响应会带上之前请求的 ID。
  • 消费者收到这个响应之后会通过 ID 去找之前记录的请求,然后找到请求之后将响应塞到对应的 Future 中,唤醒等待的线程,最后消费者得到响应,一个流程完毕。
  • 关键的就是 cluster、路由、负载均衡,然后 Dubbo 默认是异步的,所以请求和响应是如何对应上的。
2.4 Dubbo 异步转同步如何实现的之类的

2.5 为什么要封装成 invoker?

至于为什么要封装成 invoker 其实就是想屏蔽调用的细节,统一暴露出一个可执行体,这样调用者简单的使用它,向它发起 invoke 调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现。

2.6 为什么要搞个本地暴露呢?

因为可能存在同一个 JVM 内部引用自身服务的情况,因此暴露的本地服务在内部调用的时候可以直接消费同一个 JVM 的服务避免了网络间的通信。

三、RPC调用面试问题 3.1 RPC框架需要解决的问题?
  • 如何确定客户端和服务端之间的通信协议?
  • 如何更高效地进行网络通信?
  • 服务端提供的服务如何暴露给客户端?
  • 客户端如何发现这些暴露的服务?
  • 如何更高效地对请求对象和响应结果进行序列化和反序列化操作?
3.2 RPC的实现基础?
  • 需要有非常高效的网络通信,比如一般选择Netty作为网络通信框架;
  • 需要有比较高效的序列化框架,比如谷歌的Protobuf序列化框架;
  • 可靠的寻址方式(主要是提供服务的发现),比如可以使用Zookeeper来注册服务等等;
  • 如果是带会话(状态)的RPC调用,还需要有会话和状态保持的功能;
3.3 RPC使用了哪些关键技术?

1、动态代理:生成Client Stub(客户端存根)和Server Stub(服务端存根)的时候需要用到Java动态代理技术,可以使用JDK提供的原生的动态代理机制,也可以使用开源的:CGLib代理,Javassist字节码生成技术。

2、序列化和反序列化:在网络中,所有的数据都将会被转化为字节进行传送,所以为了能够使参数对象在网络中进行传输,需要对这些参数进行序列化和反序列化操作。目前比较高效的开源序列化框架:如Kryo、FastJson和Protobuf等。

  • 序列化:把对象转换为字节序列的过程称为对象的序列化,也就是编码的过程。
  • 反序列化:把字节序列恢复为对象的过程称为对象的反序列化,也就是解码的过程。

3、NIO通信:出于并发性能的考虑,传统的阻塞式 IO 显然不太合适,因此我们需要异步的 IO,即 NIO。Java 提供了 NIO 的解决方案,Java 7 也提供了更优秀的 NIO.2 支持。可以选择Netty或者MINA来解决NIO数据传输的问题。

4、服务注册中心:可选:Redis、Zookeeper、Consul 、Etcd。一般使用ZooKeeper提供服务注册与发现功能,解决单点故障以及分布式部署的问题(注册中心)。

3.4 RPC和Http有什么区别
  • 性能:RPC和Http,主要差别在序列化和反序列化。RPC通过thrift二进制传输,http json序列化更消耗性能。
  • 传输协议:RPC基于tcp也可以基于http,http只能是http。
  • 负载均衡:RPC自带负载均衡的。http 需要自己搞。比如nginx等等
  • 传输效率:可以自定义tcp协议报文相对较小。http有很多无用的东西(很多头部信息,keepalivetime reffer)
  • 通知:RPC自动通知,http事先通知,自行修改nginx配置或者其他负载均衡的配置
3.5 为什么要消费者比提供者个数多?

因dubbo协议采用单一长连接,假设网络为千兆网卡(1024Mbit=128MByte),根据测试经验数据每条连接最多只能压满7MByte(不同的环境可能不一样,供参考),理论上1个服务提供者需要20个服务消费者才能压满网卡

3.6 为什么不能传大包?

因dubbo协议采用单一长连接,如果每次请求的数据包大小为500KByte,假设网络为千兆网卡(1024Mbit=128MByte),每条连接最大7MByte(不同的环境可能不一样,供参考),单个服务提供者的TPS(每秒处理事务数)最大为:128MByte / 500KByte = 262。单个消费者调用单个服务提供者的TPS(每秒处理事务数)最大为:7MByte / 500KByte = 14。如果能接受,可以考虑使用,否则网络将成为瓶颈。

3.7 为什么采用异步单一长连接?

因为服务的现状大都是服务提供者少,通常只有几台机器,而服务的消费者多,可能整个网站都在访问该服务,比如Morgan的提供者只有6台提供者,却有上百台消费者,每天有1.5亿次调用,如果采用常规的hessian服务,服务提供者很容易就被压跨,通过单一连接,保证单一消费者不会压死提供者,长连接,减少连接握手验证等,并使用异步IO,复用线程池,防止C10K问题。

接口增加方法,对客户端无影响,如果该方法不是客户端需要的,客户端不需要重新部署; 输入参数和结果集中增加属性,对客户端无影响,如果客户端并不需要新属性,不用重新 部署;

输入参数和结果集属性名变化,对客户端序列化无影响,但是如果客户端不重新部署,不管输入还是输出,属性名变化的属性值是获取不到的。

总结:服务器端 和 客户端 对 领域对象 并不需要完全一致,而是按照最大匹配原则。

如果不是集成Spring,单独配置如下:dubbo.service.protocol=dubbo

3.8 dubbo 默认使用什么序列化框架,你知道的还有哪些?

dubbo 有多种协议,不同的协议默认使用不同的序列化框架。比如:dubbo 协议 默认使用 Hessian2 序列化。(说明:Hessian2 是阿里在 Hessian 基础上进行的二次开发,起名为Hessian2 )。rmi协议 默认为 java 原生序列化,http 协议 默认为 为 json 。

此外补充,hessian 协议,默认是 hessian 序列化;webservice 协议,默认是 soap 文本序列化 。

3.9 什么是本地暴露和远程暴露,他们的区别

下面来看本地暴露于远程暴露的区别:本地暴露是暴露在本机JVM中,调用本地服务不需要网络通信.远程暴露是将ip,端口等信息暴露给远程客户端,调用远程服务时需要网络通信.

3.10 Dubbo支持的协议主要有:

dubbo:Dubbo 缺省协议是dubbo协议,采用单一长连接和 NIO 异步通讯,适合于小数据量大并发的服务调用,以及服务消费者机器数远大于服务提供者机器数的情况。Dubbo 缺省协议不适合传送大数据量的服务,比如传文件,传视频等,除非请求量很低。

RMI:RMI协议采用阻塞式(同步)短连接和 JDK 标准序列化方式。适用范围:传入传出参数数据包大小混合,消费者与提供者个数差不多,可传文件。

hessian:Hessian底层采用Http通讯(同步),采用Servlet暴露服务。适用于传入传出参数数据包较大,提供者比消费者个数多,提供者压力较大,可传文件。

3.11 Dubbo相关配置
配置应用信息:



配置注册中心相关信息:



配置服务协议:



配置所有暴露服务缺省值:



配置暴露服务:



配置所有引用服务缺省值:



配置引用服务:

备注:

a. 其中reference的check默认=true,启动时会检查引用的服务是否已存在,不存在时报错

b. 的配置是所有的缺省配置, 的配置会覆盖的配置。同理是所有的缺省配置。

3.12 既然有 HTTP 请求,为什么还要用 RPC 调用?

服务A调用服务B的过程是应用间的内部过程,牺牲可读性提升效率、易用性是可取的。基于这种思路,RPC产生了。通常,RPC要求在调用方中放置被调用的方法的接口。调用方只要调用了这些接口,就相当于调用了被调用方的实际方法,十分易用。于是,调用方可以像调用内部接口一样调用远程的方法,而不用封装参数名和参数值等操作。

HTTP协议,以其中的Restful规范为代表,其优势很大。它可读性好,且可以得到防火墙的支持、跨语言的支持。而且,在去年的报告中,Restful大有超过RPC的趋势。但是HTTP也有其缺点,这是与其优点相对应的。首先是有用信息占比少,毕竟HTTP工作在第七层,包含了大量的HTTP头等信息。其次是效率低,还是因为第七层的缘故。还有,其可读性似乎没有必要,因为我们可以引入网关增加可读性。此外,使用HTTP协议调用远程方法比较复杂,要封装各种参数名和参数值。

3.13 RPC 与 REST

两种风格的API区别,总结一下其实非常简单:

  • RPC面向过程,只发送 GET 和 POST 请求。GET用来查询信息,其他情况下一律用POST。
  • RESTful面向资源,使用 POST、DELETE、PUT、GET 请求,分别对应增、删、改、查操作。
3.14 zookeeper宕机与dubbo直连的情况

在实际生产中,假如zookeeper注册中心宕掉,一段时间内服务消费方还是能够调用提供方的服务的,实际上它使用的本地缓存进行通讯,这只是dubbo健壮性的一种体现。

dubbo的健壮性表现:

  1. 监控中心宕掉不影响使用,只是丢失部分采样数据
  2. 数据库宕掉后,注册中心仍能通过缓存提供服务列表查询,但不能注册新服务
  3. 注册中心对等集群,任意一台宕掉后,将自动切换到另一台
  4. 注册中心全部宕掉后,服务提供者和服务消费者仍能通过本地缓存通讯
  5. 服务提供者无状态,任意一台宕掉后,不影响使用
  6. 服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复
3.15 服务的IP和端口是如何确定的

3.16 如何优雅的进行服务的注册和下线

3.17 服务的提供者如何动态扩容

3.18 zookeeper提供树型的目录服务,支持变更推送,适合作为bubbo服务的注册中心?

四、Dubbo的序列化和反序列问题 4.1 为什么Protobuf序列化协议比JDk的性能要高?

java序列化:用ObjectInputStream 和ObjectOutputStream进行简单的序列化和反序列化。jdk进行序列化的时候要记录对象所在的类的信息,比如元数据,数据类型等等,这样反序列化才能进行映射。

protobuf 序列化:使用protobuf前需要写.proto文件,然后用proto命令生成对应的java文件。而protobuf会根据proto文件中字段的tag顺序。

public void writeTo(com.google.protobuf.CodedOutputStream output)
                        throws java.io.IOException {
      getSerializedSize();
      if (((bitField0_ & 0x00000001) == 0x00000001)) {
        output.writeInt32(1, id_);
      }
      if (((bitField0_ & 0x00000002) == 0x00000002)) {
        output.writeInt32(2, age_);
      }
      if (((bitField0_ & 0x00000004) == 0x00000004)) {
        output.writeBytes(3, getUsernameBytes());
      }
      if (((bitField0_ & 0x00000008) == 0x00000008)) {
        output.writeBytes(4, getAddressBytes());
      }
      getUnknownFields().writeTo(output);
    }

可以看到,protobuf会安装我们定义的顺序来一个一个写入到OutPutStream中。

XML/JSon/protobuf对比

4.2 Protobuf支持多语言的序列化和反序列化的原理?

各种语言使用Protobuf 时候,将在使用protobuf前需要写.proto文件,然后有各种语言生成对应的文件进行的序列化和反序列操作。

4.3 时间轮方法

如果存在海量定时任务,并且这些任务的开始时间跨度非常长,例如,有的是 1 分钟之后执行,有的是 1 小时之后执行,有的是 1 年之后执行,那你该如何对时间轮进行扩展,处理这些定时任务呢?

博文参考

设计原则 | Apache Dubbo

《Dubbo系列》-Dubbo常见面试题 - 掘金

关注
打赏
1657692713
查看更多评论
立即登录/注册

微信扫码登录

0.0417s