dubbo

因为 RPC 和 HTTP 就不是一个层级的东西,所以严格意义上这两个没有可比性,也不应该来作比较,而题目问的就是把这两个作为比较了。

HTTP 只是传输协议,协议只是规范了一定的交流格式,而且 RPC 是早于 HTTP 的,所以真要问也是问有 RPC 为什么还要 HTTP。

RPC 对比的是本地过程调用,是用来作为分布式系统之间的通信,它可以用 HTTP 来传输,也可以基于 TCP 自定义协议传输。

注册中心:zk,redis,Nacos.

协议;gRPC、Thrift、Hessian2、dubbo.

dubbo3推出了, Triple(性能比较好)  Triple 协议是 Dubbo3 推出的主力协议。它采用分层设计,其数据交换格式基于Protobuf (Protocol Buffers) 协议开发,具备优秀的序列化/反序列化效率,当然还支持多种序列化方式,也支持众多开发语言。在传输层协议,Triple 选择了 HTTP/2,相较于 HTTP/1.1,其传输效率有了很大提升。此外HTTP/2作为一个成熟的开放标准,具备丰富的安全、流控等能力,同时拥有良好的互操作性

gRPC缺点(强绑定 protobuf 的序列化方式,需要较高的学习成本和改造成本)

官网的一张图。

首先注册中心和监控中心是可选的,你可以不要监控,也不要注册中心,直接在配置文件里面写然后提供方和消费方直连

然后注册中心、提供方和消费方之间都是长连接,和监控方不是长连接,并且消费方是直接调用提供方,不经过注册中心

就算注册中心和监控中心宕机了也不会影响到已经正常运行的提供者和消费者,因为消费者有本地缓存提供者的信息。

什么是Invoker.

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

Dubbo 的 Cluster 有什么用?

前面我们已经说了有服务目录,并且目录还经过了路由规则的过滤,此时我们手上还是有一堆 invokers,那对于消费者来说就需要进行抉择,那到底选哪个 invoker 进行调用呢?

假设选择的那个 invoker 调用出错了怎么办?前面我们已经提到了,这时候就是 cluster 登场的时候了,它会把这一堆 invoker 封装成 clusterInovker,给到消费者调用的就只有一个 invoker 了,

然后在这个 clusterInovker 内部还能做各种操作,比如选择一个 invoker ,调用出错了可以换一个等等。

这些细节都被封装了,消费者感受不到这个复杂度,所以 cluster 就是一个中间层,为消费者屏蔽了服务提供者的情况,简化了消费者的使用。比如(失败策略)。

并且也更加方便的替换各种集群容错措施。

Dubbo 默认的 cluster 实现有很多,主要有以下几种:

每个 Cluster 内部其实返回的都是 XXXClusterInvoker,我就举一下 FailoverCluster 这个例子。

Invoker 就是 Dubbo 对远程调用的抽象。

dubbo_rpc_invoke.jpg

按照 Dubbo 官方的话来说,Invoker 分为

  • 服务提供 Invoker

  • 服务消费 Invoker

假如我们需要调用一个远程方法,我们需要动态代理来屏蔽远程调用的细节吧!我们屏蔽掉的这些细节就依赖对应的 Invoker 实现, Invoker 实现了真正的远程服务调用。

 ApplicationListener<ContextRefreshedEvent>),可以去做一些自己想做的事。其实这也就是spring ioc容器给提供的一个扩展的地方。我们可以这样使用这个扩展机制。

  1. org.springframework.context.ApplicationEvent

  2. org.springframework.context.ApplicationListener

一个最简单的方式就是,让我们的bean实现ApplicationListener接口,这样当发布事件时,spring的ioc容器就会以容器的实例对象作为事件源类,并从中找到事件的监听者,此时ApplicationListener接口实例中的onApplicationEvent(E event)方法就会被调用,我们的逻辑代码就会写在此处。这样我们的目的就达到了。但这也带来一个思考,有人可能会想,这样的代码我们也可以通过实现spring的InitializingBean接口来实现啊,也会被spring容器去自动调用,但是大家应该想到,如果我们现在想做的事,是必须要等到所有的bean都被处理完成之后再进行,此时InitializingBean接口的实现就不合适了,所以需要深刻理解事件机制的应用场合。

这里又涉及到 Spring 相关内容了,可以看到它实现了 ApplicationListener<ContextRefreshedEvent>,这样就会在 Spring IOC 容器刷新完成后调用 onApplicationEvent 方法,而这个方法里面做的就是服务暴露,这就是服务暴露的启动点。

看过源码,那说下服务暴露的流程?

服务的暴露起始于 Spring IOC 容器刷新完毕之后,会根据配置参数组装成 URL, 然后根据 URL 的参数来进行本地或者远程调用

会通过 proxyFactory.getInvoker,利用 javassist 来进行动态代理,封装真的实现类,然后再通过 URL 参数选择对应的协议来进行 protocol.export,默认是 Dubbo 协议。

在第一次暴露的时候会调用 createServer 来创建 Server,默认是 NettyServer。

然后将 export 得到的 exporter 存入一个 Map 中,供之后的远程调用查找,然后会向注册中心注册提供者的信息

基本上就是这么个流程,说了这些差不多了,太细的谁都记住不。

看过源码,那说下服务引入的流程?

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

饿汉式就是加载完毕就会引入,懒汉式是只有当这个服务被调用的时候,默认是懒汉式。

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

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

说这么多差不多了,关键的点都提到了。

切忌不要太过细,不要把你知道的都说了,这样会抓不住重点,比如上面的流程你要插入,引入的三种方式:本地引入、直连远程引入、通过注册中心引入。

然后再分别说本地引入怎样的,芭芭拉的就会很乱,所以面试的时候是需要删减的,要直击重点。

其实真实说的应该比我上面说的还要精简点才行,我是怕大家不太清楚说的稍微详细了一些。

看过源码,那说下服务调用的流程?

调用某个接口的方法会调用之前生成的代理类,然后会从 cluster 中经过路由的过滤、负载均衡机制选择一个 invoker 发起远程调用,此时会记录此请求和请求的 ID 等待服务端的响应。

服务端接受请求之后会通过参数找到之前暴露存储的 map,得到相应的 exporter ,然后最终调用真正的实现类,再组装好结果返回,这个响应会带上之前请求的 ID。

消费者收到这个响应之后会通过 ID 去找之前记录的请求,然后找到请求之后将响应塞到对应的 Future 中,唤醒等待的线程,最后消费者得到响应,一个流程完毕。

关键的就是 cluster、路由、负载均衡,然后 Dubbo 默认是异步的,所以请求和响应是如何对应上的。

之后可能还会追问 Dubbo 异步转同步如何实现的之类的,在丙之前文章里面都说了,忘记的同学可以回去看看。

Dubbo 的微内核架构了解吗?

Dubbo 采用 微内核(Microkernel) + 插件(Plugin) 模式,简单来说就是微内核架构。微内核只负责组装插件。核心系统提供系统所需核心能力,插件模块可以扩展系统的功能。因此, 基于微内核架构的系统,非常易于扩展功能。

正是因为 Dubbo 基于微内核架构,才使得我们可以随心所欲替换 Dubbo 的功能点。比如你觉得 Dubbo 的序列化模块实现的不满足自己要求,没关系啊!你自己实现一个序列化模块就好了啊!

Dubbo 支持多种序列化方式:JDK 自带的序列化、hessian2、JSON、Kryo、FST、Protostuff,ProtoBuf 等等。

Dubbo 默认使用的序列化方式是 hession2。

谈谈你对这些序列化协议了解?

一般我们不会直接使用 JDK 自带的序列化方式。主要原因有两个:

  1. 不支持跨语言调用 : 如果调用的是其他语言开发的服务的时候就不支持了。

  2. 性能差 :相比于其他序列化框架性能更低,主要原因是序列化之后的字节数组体积较大,导致传输成本加大。

JSON 序列化由于性能问题,我们一般也不会考虑使用。

像 Protostuff,ProtoBuf、hessian2 这些都是跨语言的序列化方式,如果有跨语言需求的话可以考虑使用。

Kryo 和 FST 这两种序列化方式是 Dubbo 后来才引入的,性能非常好。不过,这两者都是专门针对 Java 语言的。

cluster 先来看下官网的这一张图,很是清晰,然后我再用语言来阐述一遍。

首先在服务引入的时候,将多个远程调用都塞入 Directory 中,然后通过 Cluster 来封装这个目录,封装的同时提供各种容错功能,比如 FailOver、FailFast 等等,最终暴露给消费者的就是一个 invoker。

然后消费者调用的时候会目录里面得到 invoker 列表,当然会经过路由的过滤,得到这些 invokers 之后再由 loadBalance 来进行负载均衡选择一个 invoker,最终发起调用。

这种过程其实是在 Cluster 的内部发起的,所以能在发起调用出错的情况下,用上容错的各种措施。

负载均衡

RandomLoadBalance( 默认采取的负载均衡实现)

这个算法是加权随机,思想其实很简单,我举个例子:假设现在有两台服务器分别是 A 和 B,我想让 70% 的请求落到 A 上,30% 的请求落到 B上,此时我只要搞个随机数生成范围在 [0,10),这个 10 是由 7+3 得来的。

然后如果得到的随机数在 [0,7) 则选择服务器 A,如果在 [7,10) 则选择服务器 B ,当然前提是这个随机数的分布性很好,概率才会正确。

LeastActiveLoadBalance

这个是最少活跃数负载均衡

ConsistentHashLoadBalance

这个是一致性 Hash 负载均衡算法,一致性 Hash 想必大家都很熟悉了,常见的一致性 Hash 算法是 Karger 提出的,就是将 hash值空间设为 [0, 2^32 - 1],并且是个循环的圆环状。

将服务器的 IP 等信息生成一个 hash 值,将这个值投射到圆环上作为一个节点,然后当 key 来查找的时候顺时针查找第一个大于等于这个 key 的 hash 值的节点。

一般而言还会引入虚拟节点,使得数据更加的分散,避免数据倾斜压垮某个节点,来看下官网的一个图。

RoundRobinLoadBalance

这个是加权轮询负载均衡,轮询我们都知道,这个加权也就是加了权重的轮询,比如说现在有两台服务器 A、B,轮询的调用顺序就是 A、B、A、B....,如果加了权重,A比B 的权重是3:1,那现在的调用顺序就是 A、A、A、B、A、A、A、B....

加权的原因是个别服务器性能比较好,所以想轮询的多一些

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值