Doubble系列-初体验

Doubble系列-初体验

文章发布于 2020-09-23 17:08:57,最后更新于 2020-09-23 17:27:09

Doubble系列-初体验

一、背景知识

随着微服务的普及化和他的重要性日益渐增,服务化场景下随之而来的就是服务通信问题。

1.1 微服务通信基本概念

常见的通信方式:

  1. 远程数据共享(比如共享远程文件、共享数据库实现不同系统通信)

  2. 消息队列

  3. RPC(远程过程调用)

    1.2RPC概念和分类

从通信协议的层面大致分为:

基于HTTP协议的(例如基于文本的SOAP(XML)、Rest(JSON),基于二进制Hessian(Binary))

基于TCP协议的(通常会借助Mina、Netty等高性能网络框架)

从不同的开发语言和平台层面,分为:

单种语言或平台特定支持的通信技术(例如Java平台的RMI、.NET平台Remoting)

支持跨平台通信的技术(例如HTTP Rest、Thrift等)

从调用过程来看,分为:

同步通信调用(同步RPC)

异步通信调用(MQ、异步RPC)

1.3 RPC与HTTP的不同特点

传输协议

(1)RPC:基于TCP,也可基于HTTP

(2)HTTP:基于HTTP

传输效率

(1)自定义TCP协议,报文小。 如果基于HTTP2协议,也可减小报文,提高传输效率

(2)基于HTTP1.1协议,报文有很多无用内容,传输效率低

性能消耗,主要在于序列化和反序列化的耗时

(1)RPC,可以基于thrift实现高效二进制传输

(2)HTTP,大部分是json实现,字节大小和序列化耗时逗比thrift更消耗性能

负载均衡

(1)RPC自带负载均衡策略

(2)HTTP,需要配置Nginx,HAProxy来实现

服务治理

(1)RPC,自动通知

(2)HTTP,事先通知,修改Nginx/HAProxy配置

介绍完一下基本概念,下面就进入主要话题Dubbo,他是基于RPC的。

二、弄懂RPC

RPC,Remote Procedure Call也就是远程过程调用,远程过程调用其实对标的是本地过程调用。简单的说本机上内部的方法调用都可以称为本地过程调用,而远程过程调用实际上就指的是你本地调用了远程机子上的某个方法,这就是远程过程调用。

所以说 RPC 对标的是本地过程调用,至于 RPC 要如何调用远程的方法可以走 HTTP、也可以是基于 TCP 自定义协议。

 RPC 和 HTTP 就不是一个层级的东西。

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

2.1 如何设计RPC框架

明确了目标后就可以设计解决方案。根据场景,很容易分析出RPC中所涉及的角色有以下几个。

  1. 服务消费者:

    消费者面向接口编程,所以需要得知有哪些接口可以调用,可以通过公用 jar 包的方式来维护接口。

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

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

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

    所以提供方一般都是集群部署,那调用方需要通过负载均衡来选择一个调用,可以通过某些策略例如同机房优先调用啊啥的。

    当然还需要有容错机制,毕竟这是远程调用,网络是不可靠的,所以可能需要重试什么的。

    还要和服务提供方约定一个协议,例如我们就用 HTTP 来通信就好啦,也就是大家要讲一样的话,不然可能听不懂了。

    当然序列化必不可少,毕竟我们本地的结构是“立体”的,需要序列化之后才能传输,因此还需要约定序列化格式

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

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

  2. 服务提供者:

    服务提供者肯定要实现对应的接口这是毋庸置疑的。

    然后需要把自己的接口暴露出去,向注册中心注册自己,暴露自己所能提供的服务。

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

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

  3. 注册中心:

    上面其实我们都提到了注册中心,这东西就相当于一个平台,大家在上面暴露自己的服务,也在上面得知自己能调用哪些服务。

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

  4. 运维监控:

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

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

大致上一个 RPC 框架需要做的就是约定要通信协议,序列化的格式、一些容错机制、负载均衡策略、监控运维和一个注册中心!

2.2 实现RPC

首先定义一个接口和简单的实现

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

调用者传递了方法名、参数类型和参数值,提供者接收到这样参数之后调用对于的方法返回结果就好了!这就是远程过程调用。

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

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

这个就是远程调用的一个简单demo

三、初识Dubbo

3.1 dubbo简介

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

Dubbo是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。简单的说,dubbo就是个服务框架,如果没有分布式的需求,其实是不需要用的,只有在分布式的时候,才有dubbo这样的分布式服务框架的需求,并且本质上是个服务调用的东东,说白了就是个远程服务调用的分布式框架(告别Web Service模式中的WSdl,以服务者与消费者的方式在dubbo上注册)
其核心部分包含:

  1. 远程通讯: 提供对多种基于长连接的NIO框架抽象封装,包括多种线程模型,序列化,以及“请求-响应”模式的信息交换方式。
  2. 集群容错: 提供基于接口方法的透明远程过程调用,包括多协议支持,以及软负载均衡,失败容错,地址路由,动态配置等集群支持。
  3. 自动发现: 基于注册中心目录服务,使服务消费方能动态的查找服务提供方,使地址透明,使服务提供方可以平滑增加或减少机器。

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

3.2 Dubbo总体架构

节点角色说明
Consumer需要调用远程服务的服务消费方
Registry注册中心
Provider服务提供方
Container服务运行的容器
Monitor监控中心

大致流程:

  1. 服务提供者在启动时,向注册中心注册自己提供的服务。
  2. 服务消费者在启动时,向注册中心订阅自己所需的服务。
  3. 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
  4. 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
  5. 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。

注意:

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

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

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

3.3 Dubbo分层架构

总的而言 Dubbo 分为三层,如果每一层再细分下去,一共有十层。

微信图片_20200923171013.jpg

三层分别为 Business(业务层)、RPC 层、Remoting,并且还分为 API 层和 SPI 层。

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

接下来咱们再来看看每一层都是干嘛的。
微信图片_20200923171018.jpg

  • Service,业务层,就是咱们开发的业务逻辑层。

  • Config,配置层,主要围绕 ServiceConfig 和 ReferenceConfig,初始化配置信息。可以直接new配置类,也可以通过spring解析配置生成配置类。

  • Proxy,代理层,服务提供者还是消费者都会生成一个代理类,使得服务接口透明化,代理层做远程调用和返回结果。

  • Register,注册层,封装了服务注册和发现。

  • Cluster,路由和集群容错层,负责选取具体调用的节点,处理特殊的调用要求和负责远程调用失败的容错措施。

  • Monitor,监控层,负责监控统计调用时间和次数。

  • Portocol,远程调用层,主要是封装 RPC 调用,主要负责管理 Invoker,Invoker代表一个抽象封装了的执行体,之后再做详解。

  • Exchange,信息交换层,用来封装请求响应模型,同步转异步。Request和Response为中心,扩展接口为Exchanger、ExchangeChannel、ExchangeClient和ExchangeServer。

  • Transport,网络传输层,抽象了网络传输的统一接口,这样用户想用 Netty 就用 Netty,想用 Mina 就用 Mina。

  • Serialize,序列化层,将数据序列化成二进制流,当然也做反序列化。可复用的一些工具,扩展接口为Serialization、 ObjectInput、ObjectOutput和ThreadPool。

SPI

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

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

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

3.4 Dubbo调用过程

服务暴露过程:
微信图片_20200923171022.jpg

首先 Provider 启动,通过 Proxy 组件根据具体的协议 Protocol 将需要暴露出去的接口封装成 Invoker,Invoker 是 Dubbo 一个很核心的组件,代表一个可执行体。

然后再通过 Exporter 包装一下,这是为了在注册中心暴露自己套的一层,然后将 Exporter 通过 Registry 注册到注册中心。这就是整体服务暴露过程。

消费过程:

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

Proxy 持有一个 Invoker 对象,调用 invoke 之后需要通过 Cluster 先从 Directory 获取所有可调用的远程服务的 Invoker 列表,如果配置了某些路由规则,比如某个接口只能调用某个节点的那就再过滤一遍 Invoker 列表。

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

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

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

完成整个调用过程!

四、总结

先了解了下什么是 RPC,然后规划了一波 RPC 框架需要哪些组件,然后再用代码实现了一个简单的 RPC 框架。
然后带着大家了解了下 Dubbo 的发展历史、总体架构、分层设计架构以及每个组件是干嘛的,再带着大伙走了一遍整体调用过程。
dubbo近期后面会接着学习,当然到最后也会出一个面试版的作为学习的总结。

最后老话,欢迎有问题,有建议,有好资料的哥们和我分享,当然我也很乐意分享我的资料。只要你主动我们就有故事!!!

我是星宇,一个满头黑发,渴望秃头的开发,我们下期见!

Copyright: 采用 知识共享署名4.0 国际许可协议进行许可

Links: https://mxyblogs.club/archives/doubble系列-初体验

Buy me a cup of coffee ☕.