Dubbo系列-扬帆起航

前言

接下来一段时间敖丙将带你们开启紧张刺激的 Dubbo 之旅!是的要开始写 Dubbo 系列的文章了,以前我已经写过一篇架构演进的文章,也说明了微服务的普及化以及重要性,服务化场景下随之而来的就是服务之间的通讯问题,那服务间的通讯脑海中想到的就是 RPC,说到 RPC 就离不开我们的 Dubbo。
Dubbo系列-扬帆起航
这篇文章敖丙先带着你们来总览全局,通常而言熟悉一个框架你要先知道这玩意是作什么的,能解决什么痛点,核心的模块是什么,大体运转流程是怎样的。面试

你要一来就扎入细节之中没法自拔,一波 DFS 直接被劝退的可能性高达99.99%,因此本暖男敖丙将带你们先过一遍 Dubbo 的简介、整体分层、核心组件以及大体调用流程。编程

不只如此我还会带着你们过一遍若是要让你设计一个 RPC 框架你看看都须要什么功能?这波操做以后你会发现嘿嘿 Dubbo 怎么设计的和我想的同样呢?真是英雄所见略同啊!缓存

并且我还会写一个简单版 RPC 框架实现,让你们明白 RPC 究竟是如何工做的。网络

若是看了这篇文章你要仍是不知道 Dubbo 是啥,我能够要劝退了。
Dubbo系列-扬帆起航
咱们先来谈一谈什么叫 RPC ,我发现有不少同窗不太了解这个概念,还有人把 RPC 和 HTTP 来进行对比。因此我们先来讲说什么是 RPC。架构

什么是 RPC

RPC,Remote Procedure Call 即远程过程调用,远程过程调用其实对标的是本地过程调用,本地过程调用你熟悉吧?负载均衡

想一想那青葱岁月,你在大学赶着期末大做业,正在攻克图书管理系统,你奋笔疾书疯狂地敲击键盘,实现了图书借阅、图书归还等等模块,你实现的一个个方法之间的调用就叫本地过程调用。框架

你要是和我说你实现图书馆里系统已经用了服务化,搞了远程调用了,我只能和你说你有点东西。
Dubbo系列-扬帆起航运维

简单的说本机上内部的方法调用均可以称为本地过程调用,而远程过程调用实际上就指的是你本地调用了远程机子上的某个方法,这就是远程过程调用。
Dubbo系列-扬帆起航
因此说 RPC 对标的是本地过程调用,至于 RPC 要如何调用远程的方法能够走 HTTP、也能够是基于 TCP 自定义协议。异步

因此说你讨论 RPC 和 HTTP 就不是一个层级的东西。socket

而 RPC 框架就是要实现像那小助手同样的东西,目的就是让咱们使用远程调用像本地调用同样简单方便,而且解决一些远程调用会发生的一些问题,使用户用的无感知、舒心、放心、顺心,它好我也好,快乐没烦恼。

如何设计一个 RPC 框架

在明确了什么是 RPC,以及 RPC 框架的目的以后,我们想一想若是让你作一款 RPC 框架你该如何设计?
Dubbo系列-扬帆起航

服务消费者

咱们先从消费者方(也就是调用方)来看须要些什么,首先消费者面向接口编程,因此须要得知有哪些接口能够调用,能够经过公用 jar 包的方式来维护接口。

如今知道有哪些接口能够调用了,可是只有接口啊,具体的实现怎么来?这事必须框架给处理了!因此还须要来个代理类,让消费者
只管调,啥事都别管了,我代理帮你搞定。

对了,还须要告诉代理,你调用的是哪一个方法,而且参数的值是什么。

虽然说代理帮你搞定可是代理也须要知道它到底要调哪一个机子上的远程方法,因此须要有个注册中心,这样调用方从注册中心能够知晓能够调用哪些服务提供方,通常而言提供方不止一个,毕竟只有一个挂了那不就没了。

因此提供方通常都是集群部署,那调用方须要经过负载均衡来选择一个调用,能够经过某些策略例如同机房优先调用啊啥的。
Dubbo系列-扬帆起航
固然还须要有容错机制,毕竟这是远程调用,网络是不可靠的,因此可能须要重试什么的。

还要和服务提供方约定一个协议,例如咱们就用 HTTP 来通讯就好啦,也就是你们要讲同样的话,否则可能听不懂了。

固然序列化必不可少,毕竟咱们本地的结构是“立体”的,须要序列化以后才能传输,所以还须要约定序列化格式。

而且这过程当中间可能还须要掺入一些 Filter,来做一波统一的处理,例如调用计数啊等等。

这些都是框架须要作的,让消费者像在调用本地方法同样,无感知。

服务提供者

服务提供者确定要实现对应的接口这是毋庸置疑的。
而后须要把本身的接口暴露出去,向注册中心注册本身,暴露本身所能提供的服务。

而后有消费者请求过来须要处理,提供者须要用和消费者协商好的协议来处理这个请求,而后作反序列化。

序列化完的请求应该扔到线程池里面作处理,某个线程接受到这个请求以后找到对应的实现调用,而后再将结果原路返回。

注册中心

上面其实咱们都提到了注册中心,这东西就至关于一个平台,你们在上面暴露本身的服务,也在上面得知本身能调用哪些服务。

固然还能作配置中心,将配置集中化处理,动态变动通知订阅者。

监控运维

面对众多的服务,精细化的监控和方便的运维必不可少。

这点不少开发者在开发的时候察觉不到,到你真正上线开始运行维护的时候,若是没有良好的监控措施,快速的运维手段,到时候就是睁眼瞎!手足无措,等着挨批把!

那种痛苦不要问我为何知道,我就是知道!
Dubbo系列-扬帆起航

小结一下

让咱们小结一下,大体上一个 RPC 框架须要作的就是约定要通讯协议,序列化的格式、一些容错机制、负载均衡策略、监控运维和一个注册中心!

简单实现一个 RPC 框架

没错就是简单的实现,上面咱们在思考如何设计一个 RPC 框架的时候想了不少,那算是生产环境使用级别的功能需求了,咱们这是 Demo,目的是突出 RPC框架重点功能 - 实现远程调用。

因此啥七七八八的都没,而且我用伪代码来展现,其实也就是删除了一些保护性和约束性的代码,由于看起来太多了不太直观,须要一堆 try-catch 啥的,所以我删减了一些,直击重点。

Let's Do It!

首先咱们定义一个接口和一个简单实现。

public interface AobingService {  
    String hello(String name);  
} 

public class AobingServiceImpl implements AobingService {  
    public String hello(String name) {  
        return "Yo man Hello,I am" + name;  
    }  
}

而后咱们再来实现服务提供者暴露服务的功能。

public class AobingRpcFramework { 
     public static void export(Object service, int port) throws Exception { 
          ServerSocket server = new ServerSocket(port);
          while(true) {
              Socket socket = server.accept();
              new Thread(new Runnable() {
                  //反序列化
                  ObjectInputStream input = new ObjectInputStream(socket.getInputStream()); 
                  String methodName = input.read(); //读取方法名
                  Class<?>[] parameterTypes = (Class<?>[]) input.readObject(); //参数类型
                  Object[] arguments = (Object[]) input.readObject(); //参数
                  Method method = service.getClass().getMethod(methodName, parameterTypes);  //找到方法
                  Object result = method.invoke(service, arguments); //调用方法
                  // 返回结果
                  ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream());
                  output.writeObject(result);
              }).start();
          }
     }
    public static <T> T refer (Class<T> interfaceClass, String host, int port) throws Exception {
       return  (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class<?>[] {interfaceClass}, 
            new InvocationHandler() {  
                public Object invoke(Object proxy, Method method, Object[] arguments) throws Throwable {  
                    Socket socket = new Socket(host, port);  //指定 provider 的 ip 和端口
                    ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream()); 
                    output.write(method.getName());  //传方法名
                    output.writeObject(method.getParameterTypes());  //传参数类型
                    output.writeObject(arguments);  //传参数值
                    ObjectInputStream input = new ObjectInputStream(socket.getInputStream());  
                    Object result = input.readObject();  //读取结果
                    return result;  
               }
        });  
    }  
}

好了,这个 RPC 框架就这样好了,是否是很简单?就是调用者传递了方法名、参数类型和参数值,提供者接收到这样参数以后调用对于的方法返回结果就行了!这就是远程过程调用。
咱们来看看如何使用

//服务提供者只须要暴露出接口
     AobingService service = new AobingServiceImpl ();  
     AobingRpcFramework.export(service, 2333);  

     //服务调用者只须要设置依赖
     AobingService service = AobingRpcFramework.refer(AobingService.class, "127.0.0.1", 2333);  
     service.hello();

看起来好像好不错哟,不过这非常简陋,用做 demo 有助理解仍是极好的!

接下来就来看看 Dubbo 吧!上正菜!

Dubbo 简介

Dubbo 是阿里巴巴 2011年开源的一个基于 Java 的 RPC 框架,中间沉寂了一段时间,不过其余一些企业还在用 Dubbo 并本身作了扩展,好比当当网的 Dubbox,还有网易考拉的 Dubbok。

可是在 2017 年阿里巴巴又重启了对 Dubbo 维护。在 2017 年荣获了开源中国 2017 最受欢迎的中国开源软件 Top 3。

在 2018 年和 Dubbox 进行了合并,而且进入 Apache 孵化器,在 2019 年毕业正式成为 Apache 顶级项目。

目前 Dubbo 社区主力维护的是 2.6.x 和 2.7.x 两大版本,2.6.x 版本主要是 bug 修复和少许功能加强为准,是稳定版本。

而 2.7.x 是主要开发版本,更新和新增新的 feature 和优化,而且 2.7.5 版本的发布被 Dubbo 认为是里程碑式的版本发布,以后咱们再作分析。

它实现了面向接口的代理 RPC 调用,而且能够配合 ZooKeeper 等组件实现服务注册和发现功能,而且拥有负载均衡、容错机制等。

Dubbo 整体架构

咱们先来看下官网的一张图。
Dubbo系列-扬帆起航
本丙再暖心的给上图内每一个节点的角色说明一下。
节点 角色说明
Consumer 须要调用远程服务的服务消费方
Registry 注册中心
Provider 服务提供方
Container 服务运行的容器
Monitor 监控中心

我再来大体说一下总体的流程,首先服务提供者 Provider 启动而后向注册中心注册本身所能提供的服务。

服务消费者 Consumer 启动向注册中心订阅本身所需的服务。而后注册中心将提供者元信息通知给 Consumer, 以后 Consumer 由于已经从注册中心获取提供者的地址,所以能够经过负载均衡选择一个 Provider 直接调用 。

以后服务提供方元数据变动的话注册中心会把变动推送给服务消费者。

服务提供者和消费者都会在内存中记录着调用的次数和时间,而后定时的发送统计数据到监控中心。

一些注意点

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

而后注册中心、提供方和消费方之间都是长链接,和监控方不是长链接,而且消费方是直接调用提供方,不通过注册中心。

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

Dubbo 分层架构

总的而言 Dubbo 分为三层,若是每一层再细分下去,一共有十层。别怕也就十层,本丙带你们过一遍,你们先有个大体的印象,以后的文章丙会带着你们再深刻。
Dubbo系列-扬帆起航
大的三层分别为 Business(业务层)、RPC 层、Remoting,而且还分为 API 层和 SPI 层。

分为大三层其实就是和咱们知道的网络分层同样的意思,只有井井有条,职责边界清晰才能更好的扩展。

而分 API 层和 SPI 层这是 Dubbo 成功的一点,采用微内核设计+SPI扩展,使得有特殊需求的接入方能够自定义扩展,作定制的二次开发。

接下来我们再来看看每一层都是干吗的。

  • Service,业务层,就是我们开发的业务逻辑层。
  • Config,配置层,主要围绕 ServiceConfig 和 ReferenceConfig,初始化配置信息。
  • Proxy,代理层,服务提供者仍是消费者都会生成一个代理类,使得服务接口透明化,代理层作远程调用和返回结果。
  • Register,注册层,封装了服务注册和发现。
  • Cluster,路由和集群容错层,负责选取具体调用的节点,处理特殊的调用要求和负责远程调用失败的容错措施。
  • Monitor,监控层,负责监控统计调用时间和次数。
  • Portocol,远程调用层,主要是封装 RPC 调用,主要负责管理 Invoker,Invoker表明一个抽象封装了的执行体,以后再作详解。
  • Exchange,信息交换层,用来封装请求响应模型,同步转异步。
  • Transport,网络传输层,抽象了网络传输的统一接口,这样用户想用 Netty 就用 Netty,想用 Mina 就用 Mina。
  • Serialize,序列化层,将数据序列化成二进制流,固然也作反序列化。

SPI

我再稍微提一下 SPI(Service Provider Interface),是 JDK 内置的一个服务发现机制,它使得接口和具体实现彻底解耦。咱们只声明接口,具体的实现类在配置中选择。

具体的就是你定义了一个接口,而后在META-INF/services目录下放置一个与接口同名的文本文件,文件的内容为接口的实现类,多个实现类用换行符分隔。

这样就经过配置来决定具体用哪一个实现!

而 Dubbo SPI 还作了一些改进,篇幅有限留在以后再谈。

Dubbo 调用过程

上面我已经介绍了每一个层究竟是干吗的,咱们如今再来串起来走一遍调用的过程,加深你对 Dubbo 的理解,让知识点串起来,由点及面来一波连连看。
Dubbo系列-扬帆起航
咱们先从服务提供者开始,看看它是如何工做的。
Dubbo系列-扬帆起航

服务暴露过程

首先 Provider 启动,经过 Proxy 组件根据具体的协议 Protocol 将须要暴露出去的接口封装成 Invoker,Invoker 是 Dubbo 一个很核心的组件,表明一个可执行体。
而后再经过 Exporter 包装一下,这是为了在注册中心暴露本身套的一层,而后将 Exporter 经过 Registry 注册到注册中心。这就是总体服务暴露过程。

消费过程

接着咱们来看消费者调用流程(把服务者暴露的过程也在图里展现出来了,这个图其实算一个挺完整的流程图了)。
Dubbo系列-扬帆起航

首先消费者启动会向注册中心拉取服务提供者的元信息,而后调用流程也是从 Proxy 开始,毕竟都须要代理才能无感知。

Proxy 持有一个 Invoker 对象,调用 invoke 以后须要经过 Cluster 先从 Directory 获取全部可调用的远程服务的 Invoker 列表,若是配置了某些路由规则,好比某个接口只能调用某个节点的那就再过滤一遍 Invoker 列表。

剩下的 Invoker 再经过 LoadBalance 作负载均衡选取一个。而后再通过 Filter 作一些统计什么的,再经过 Client 作数据传输,好比用 Netty 来传输。

传输须要通过 Codec 接口作协议构造,再序列化。最终发往对应的服务提供者。

服务提供者接收到以后也会进行 Codec 协议处理,而后反序列化后将请求扔到线程池处理。某个线程会根据请求找到对应的 Exporter ,而找到 Exporter 其实就是找到了 Invoker,可是还会有一层层 Filter,通过一层层过滤链以后最终调用实现类而后原路返回结果。

完成整个调用过程!

总结

此次敖丙带着你们先了解了下什么是 RPC,而后规划了一波 RPC 框架须要哪些组件,而后再用代码实现了一个简单的 RPC 框架。

而后带着你们了解了下 Dubbo 的发展历史、整体架构、分层设计架构以及每一个组件是干吗的,再带着大伙走了一遍总体调用过程。
我真的是太暖了啊!

dubbo近期我会安排几个章节继续展开,最后会出一个面试版本的dubbo,咱们拭目以待吧。

我是敖丙,你知道的越多,你不知道的越多,咱们下期见!