duboo原理分析

准备知识

  1. spirng基础知识、原理
  2. rpc基础知识
  3. netty基础知识

spring基础知识

spring涉及的东西太多,这里不做讲解,基本原理可以参考 https://blog.csdn.net/KevinDai007/article/details/53055409

rpc基础知识

rpc(Remote Procedure Call)——远程过程调用,既在机器A上调用机器B的服务,基本流程图如下
duboo原理分析_第1张图片
一次完整的rpc调用步骤有:

  1. 服务调用方(client)以本地调用的方式调用服务
  2. client stub接收到调用后负责将方法、参数等组装成能够进行网络传输的消息
  3. client stub找到服务端地址,并把消息发送给服务端
  4. server stub收到消息后进行解码
  5. server stub根据解码的结果调用本地服务
  6. B机器上的本地服务执行并把结果返回给Server stub
  7. server stub将返回的结果组装成消息发送至调用方
  8. client stub收到信息进行解码
  9. 服务调用方得到最终结果

一个rpc框架做的主要事情就是把2~8步封装起来,对用户透明。最流行的rpc框架就是dubbo

netty基础知识

说netty的话先要说两个基础知识bio、nio

bio

bio的基本模型如下
duboo原理分析_第2张图片
由于socket的accept()、read()、write()操作是同步阻塞的,因此用多线程解决当io阻塞时cpu的利用率问题,既每当有客户端连接服务器时都从线程池中取一个线程来进行处理。
这个模型的问题是严重依赖线程,假如有thread pool的最大线程数是100,那么当请求较多时就会有100个线程在cpu之间切换,而线程切换是一件成本很高的事,因而发展出了新的技术nio

附一段基本的bio的伪代码

public static void main(String[] args){
 ExecutorService executor = Excutors.newFixedThreadPollExecutor(100);//线程池

 ServerSocket serverSocket = new ServerSocket();
 serverSocket.bind(8088);
 while(!Thread.currentThread.isInturrupted()){//主线程死循环等待新连接到来
    Socket socket = serverSocket.accept();
    executor.submit(new ConnectIOnHandler(socket));//为新的连接创建新的线程
}

class ConnectIOnHandler extends Thread{
    private Socket socket;
    public ConnectIOnHandler(Socket socket){
       this.socket = socket;
    }
    public void run(){
      while(!Thread.currentThread.isInturrupted()&&!socket.isClosed()){死循环处理读写事件
          String someThing = socket.read()....//读取数据
          if(someThing!=null){
             //处理数据 doSomething()
             socket.write()....//写数据
          }

      }
    }
}

nio

nio的基本模型如下
duboo原理分析_第3张图片

nio的基本流程是当客户端连接时产生一个channel,selector监听channel状态到合适的状态时调用合适的handler处理业务逻辑
selector一般叫选择器,也叫多路复用器。在nio中select操作是阻塞的,因此可以用while(true)而不用担心空转。nio由原来bio的阻塞读写(或者占用线程)变成了单线程轮询事件,监听事件状态并进行读写。除了事件的轮询是阻塞的(没有可干的事情必须要阻塞,否则cpu会空转),剩余的I/O操作都是纯CPU操作,没有必要开启多线程(如果业务处理中有单独的IO则需要维护其他线程)
附一段nio的伪代码

interface ChannelHandler{
    void channelReadable(Channel channel);
    void channelWritable(Channel channel);
}
class Channel{
    Socket socket;
    Event event;//读,写或者连接
}

//IO线程主循环:
class IoThread extends Thread{
   public void run(){
   Channel channel;
   while(channel=Selector.select()){//选择就绪的事件和对应的连接
      if(channel.event==accept){
         registerNewChannelHandler(channel);//如果是新连接,则注册一个新的读写处理器
      }
      if(channel.event==write){
         getChannelHandler(channel).channelWritable(channel);//如果可以写,则执行写事件
      }
      if(channel.event==read){
          getChannelHandler(channel).channelReadable(channel);//如果可以读,则执行读事件
      }
    }
   }
   Map<Channel,ChannelHandler> handlerMap;//所有channel的对应事件处理器
}

再看下netty基本模型
duboo原理分析_第4张图片
主要流程有以下几步

  1. netty启动时监听一个端口,同时创建NioServerSocketChannel
  2. 将NioServerSocketChannel注册到boss eventLoop的selector
  3. boss eventLoop的select监听accept事件
  4. 当与客户端建立链接时创建一个NioSocketChannel
  5. 把新建的NioSocketChannel注册到selector
  6. selector监听read、write事件
  7. 当事件准备就绪时做相应的处理

这里简单描述一下流程,如果想有更深的了解可以下次做个分享

dubbo原理

dubbo框架设计

dubbo框架总体架构如下
duboo原理分析_第5张图片

service层:USER api层,主要用于用户定义自己的接口
config 配置层:主要是封装配置文件解析的配置信息
proxy 服务代理层:用于使用代理的方式生成客户端、服务端接口的代理对象
registry 注册中心层:用于封装服务的注册和发现
cluster 路由层:用于对多个服务提供者进行负载
monitor 监控层:用于监控dubbo调用情况
protocol 远程调用层:用于封装RPC调用,核心是invoker、protocol、exporter
exchange 信息交换层:创建客户端、服务端并建立管道
transport 网络传输层:传输数据,默认使用netty
serialize 数据序列化层:序列化

dubbo源码分析

1、dubbo源码从哪里开始看?
大家都知道写dubbo配置的时候可以用dubbo标签,如dubbo:application,了解spring的同学应该知道能够使用自定义标签那么必然存在一个spring.handlers的文件,里面就是自定义解析器的入口,看一下这个类
duboo原理分析_第6张图片
DubboBeanDefinitionParser继承了BeanDefinitionParser用于把指定的配置信息解析成相应的beanDefinition并交给spirng管理。这里干的事情很简单不做赘述

这里可以看到大部分标签对应的都是配置,其中ServiceBean、ReferenceBean、AnnotationBean三个比较特殊;service、reference、annotation三个标签是用来暴露服务、引用服务、使用dubbo注解的;那我们分别来看下

dubbo暴露服务

先看ServiceBean
首先能看到ServiceBean实现了InitializingBean、ApplicationContextAware、ApplicationListener,简单来说既spring启动的时候执行ServiceBean中的方法,执行顺序为setApplicationContext()、afterPropertiesSet()、onApplicationEvent(),其中大部分功能都是一些初始化工作,这里分析下onApplicationEvent()中的export()方法,一直往下看,其他地方都不重要直到doExportUrlsFor1Protocol()
duboo原理分析_第7张图片

private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

private static final ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();

这里需要重点注意一下,这里使用了SPI技术,这里我理解的也不是清楚,简单来说就是可以通过不同的配置使用不同的单例实现类(这里写的不一定对),dubbo中大量使用了这个技术,有机会的话下次分享一下。使用SPI技术之后不好debug

这里protocol的实现是RegistryProtocol,进去看下
duboo原理分析_第8张图片
下面进DubboProtocol的export()方法
duboo原理分析_第9张图片

duboo原理分析_第10张图片
一路往下看可以看到实际上是开启了一个nettyServer
duboo原理分析_第11张图片

duboo原理分析_第12张图片

注册完成之后接口在zk上的结构如下:
duboo原理分析_第13张图片

到这服务暴露的流程基本已经走完了,梳理下来大致做了以下几件事

  1. 解析配置文件信息并交给spirng管理
  2. 把要暴露的接口封装成invoker
  3. 创建Netty服务器
  4. 把要暴露的接口注册到注册中心
    时序图如下:

duboo原理分析_第14张图片

这里吐个槽目前发现官网文档上的两个错误
1、服务暴露的时序图错误
2、默认开启的server描述错误(官网说是使用mina,实际上使用的是netty)

dubbo暴露引用

直接看ReferenceBean,ReferenceBean实现了FactoryBean是一个工厂bean,工厂bean最主要的功能就是生产bean,所以我们直接看getObject()方法,一路往下找可以看到最终会调用init()方法,其中最重要的就是createProxy()这个方法
duboo原理分析_第15张图片

进入createProxy()可以看到最重要的地方在这里
duboo原理分析_第16张图片
这里的protocol又是使用了SPI技术使用了RegistryProtocol,进去看一下
duboo原理分析_第17张图片
再看下doRefer()
duboo原理分析_第18张图片
在进入DubboProtocl的refer()方法看下
duboo原理分析_第19张图片duboo原理分析_第20张图片
duboo原理分析_第21张图片

看到这里是不是稍微有点眼熟了?暴露服务的时候就是通过Exchangers来创建服务端的,这里Exchanger.connect()就是在本地开启一个netty的服务端。这样我们本地启动了一个client,在注册中心中又能够获取服务提供者的地址(ip、port),这样在调用服务的时候就能够找到对应的服务了。就不继续往下看了
时序图如下:

duboo原理分析_第22张图片

后记

到这里dubbo整个暴露服务、引用服务的流程已经大致介绍完了,这里介绍的并不细致,因为看源码的东西比较难分享的很详细,想要深究的同学看完本文章之后可以自己仔细看一遍源码,相信会有很多收获

你可能感兴趣的:(个人笔记)