dubbo源码分析-远程暴露

chapter III

什么是服务暴露

  服务暴露,通常也叫作服务发布,通过语义可以理解为将一个服务“暴露” 出去,本质上我觉得可以简单的认为只是组装一个服务域对象,保存起来,然后做些“暴露”相关的工作,方便客户端以某种契约(协议)进行rpc调用。

为什么要进行远程暴露

  关于为什么要进行远程暴露,我们可以从两个角度去分析:一、远程暴露和本地暴露有什么区别和联系;二、远程暴露涉及到哪些内容。把这两个问题解释清楚也就等于回答了为什么要进行远程暴露这个问题。
  首先,远程暴露和本地暴露的区别本质上本地暴露将转换的exporter装入InjvmProtocol的exporterMap中,而远程暴露是将exporter装入了协议所属Protocol的exporterMap中,比如使用dubbo协议进行服务暴露的话,那么exporter会被装入DubboProtocol,具体为什么我们会在引用服务章节去分析;然后我理解的本地暴露设计的初衷可能是为了规定服务提供方和服务调用方在同一应用内的调用方式,即如果没有本地暴露,那么不管通信双方是否在同一应用都需要走socket进行数据的交换,换句话说,如果进行了本地暴露,那么同一应用的双方可以直接在同一jvm中完成服务调用,减少了没必要的网络消耗;

远程暴露涉及哪些内容

  远程暴露涉及到的内容包括:

  • 将服务暴露到指定的协议维护的exporterMap集合中
  • 开启传输层服务(netty、mina等rpc框架),注意同一个端口只开启一次,在第一次服务暴露的时候开启,随后服务暴露会触发服务重启,而默认情况下,服务重启只是将当前服务url的参数追加到原本保存在AbstractPeer中的url。
  • 连接注册中心,注册服务,并且监听状态变更
  • 订阅服务(因为每个服务提供者也可能是服务的消费者)
    而本地暴露只是将exporter装载到InjvmProtocol的porterMap集合中。
    相信上诉通过对本地暴露和远程暴露的区别以及远程暴露包含的具体内容的讨论,可以解释dubbo为什么要远程暴露了(回答问题的角度是多种多样的,只要能把事情说清楚说透彻就可以)。

代码流程分析

远程暴露入口

  • RegistryProtocol#export()函数,如图:
    在这里插入图片描述

具体暴露逻辑

因为我们没有配置scope,所以默认全部暴露,暴露远程服务的时候会做以下动作:
(1).会遍历注册中心进行暴露(上文说过dubbo支持多注册中心服务暴露);
(2).将配置的监控monitor注册到URL上,(这里简单提一下 :dubbo默认会在Invoker中构造一个MonitorFilter,这个filter中持有一份MonitorFactory,这个MonitorFactory是也是基于spi扩展加载到成员变量中的,默认为DubboMonitorFactory,这个factory会生产DubboMonitor,最终在Invoker调用到MonitorFilter的invoke方法时,会根据过滤配置了monitor的请求URL,然后进行监控数据采集,后面有时间的话也会展开分析这块,并且可以尝试做一个自定义的Monitor),回到主线任务;
(3)接下来就是获取服务的可执行对象Invoker,得到Invoker过程和本地暴露一致,不在赘述;
(4)接下来会封装一个代理Invoker对象;
(5)然后进行远程暴露的Invoker转Exporter。

  • 远程暴露过程中,在Protocol为RegistryProtocol实例,ProtocolListenerWrapper,ProtocolFilterWrapper不做处理,直接走到RegistryProtocol#export()方法,如图:
    在这里插入图片描述
    直接进入RegistryProtocol#export():
    在这里插入图片描述

  • RegistryProtocol#export()逻辑分析:
    PS:本章只分析doLocalExport(),生成具体远程暴露协议的Exporter以及打开通信层。
    (1)首先执行doLocalExport(originInvoker)函数,如图:
    在这里插入图片描述
    这个函数执行流程
    I.先从缓存中获取exporter,如果没有的话,就进行创建。
    II.使用代理invoker包装invoker
    III.执行protocol#exprot(),根据下图可以判断此刻的protocol最内层将是dubboProtocol,外层会被wrapper类包裹,由于上篇已经讲过这里就不赘述,我们直接跟到DubboProtocol的export方法。
    在这里插入图片描述
    (2) DubboProtocol#export做了三件事如图:
    在这里插入图片描述

    I. 创建DubboExporter对象并放入dubbo协议的exporterMap中(即DubboExporter的exporterMap)。
    II. 判断是否为客户端存根
    III. 打开netty服务,如图
    在这里插入图片描述
    打开netty服务核心代码是DubboProtocol#createServer(url)如图(接下来是一片连续的代码截图):在这里插入图片描述
    图2.1 在这里插入图片描述
    图2.2在这里插入图片描述
    图2.3 在这里插入图片描述
    图2.4在这里插入图片描述
    图2.5 在这里插入图片描述
    图2.6

    大概的说一下上诉代码截图的逻辑:

  • 首先会设置heartbeat(图2.1),

  • 进行参数验证,判断指定的传输层server是否存在

  • 指定编解码,默认DubboCodec

  • 执行Exchangers#bind函数(图2.2)
    1. 校验url和handler参数
    2. Exchangers#getExchanger(URL),从url中获取参数exchanger,默认为header(图2.3),
    3. Exchangers#getExchanger(String),通过spi获取HeaderExchanger(图2.4)
    4. 此时可以看到得到的交换层具体实现为HeaderExchanger,执行HeaderExchanger# bind函数(图2.5)
    5. HeaderExchanger# bind内部执行逻辑可以用一个图来解释(图2.6&图2.7):

   return new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))));
                    |                          |                     |                    |                  |
                    V                          |                     |                    |                  V
            1.提供统一的服务操作接口              |                     |                    |                利用装饰器模式,这个才是最靠近业务的逻辑(直接调用相关的invoker)
            2.创建心跳定时任务                  V                     |                    |
                                    1.利于扩展点机制选择通信框架     |                    |
                                    2.格式化回调函数                 |                    |
                                                                 V                    V
                                                            消息的解码???         处理dubbo的通信模型:单向,双向,异步等通信模型

图2.7来源blog地址
再简单说下我个人理解HeaderExchangeHandler,这个是处理具体请求的Handler,DecodeHandler是负责编解码的Handler,然后通过装饰设计模式在DecodeHandler内部维护一个HeaderExchangeHandler,这样的作用其实很明显,就是在调用具体请求handler的时候,必须经过编解码handler(实际上我们从对端拿到数据是必须要经过解码的);然后Transporters.bind()的作用体现在两个方面:一、在真正的调用具体Transporter之前做一些处理,比如说,它内部对参数进行了校验;二、屏蔽掉具体的Transporter(传输层实现),起到一定的适配作用;最后在完成端口绑定之后返回一个HeaderExchangeServer,这个HeaderExchangeServer可以作为统一调用的入口。

  1. 最终真正执行绑定的是NettyTransporter,如图:
    在这里插入图片描述
    NettyTransporter通过构造NettyServer完成端口的绑定监听,也就是说在构建NettyServer对象完成时就已经实现了端口的绑定。NettyServer类图如下:
    在这里插入图片描述
    首先进入NettyServer构造函数如图:
    这里我们可以看到首先会调用ChannelHandlers#wrap函数,这一步的作用可以理解为是在构建ChannelHandler调用链,构建后的ChannelHandler如图:在这里插入图片描述然后将url和ChannelHandler链传给AbstractServer(根据 NettyServer类图可以判断),委托父类构造函数完成其余功能,如图:在这里插入图片描述
    AbstractServer只是做了一些抽象通用的初始化工作,比如设置编解码器,以及连接属性(timeout…);关键是doOpen(),doOpen()是个模板方法,也是绑定端口的关键,具体可以看NettyServer#doOpen,如图:在这里插入图片描述
    dubbo 2.6.5 封装的Netty版本是3.X,jboss netty和netty4的用法很类似,我们贴图对比一下:在这里插入图片描述
    流程大概是一致的,无非就是构造Boos和Worker线程池,指定ChannelFactory用来生产Channel;构造辅助启动类ServerBootstrap,设置ChannelPipeline,ChannelPipeline中添加添加ChannelHandler(编解码handler和业务处理handler),调用辅助启动类的bind方法进行端口绑定。 这里有个细节需要关注一下:
final NettyHandler nettyHandler = new NettyHandler(getUrl(), this);
channels = nettyHandler.getChannels();

  这个NettyHandler是dubbo自己封装的一个handler,继承自netty的SimpleChannelHandler,可以看出它的职能是处理netty中触发的各种事件,类似于netty4的ChannelInboundHandlerAdapter和ChannelOutboundHandlerAdapter功能的聚合,内部需要维护一个ChannelHandler(dubbo内部提供,通过扩展点机制可以加载具体实现),这个ChannelHandler是事件处理的核心,所有netty的事件都将被委托给ChannelHandler,具体的处理流程采用责任链模式(比如检查是否是心跳消息等),可以理解它被用于具体的处理各种事件,而ChannelHandler在处理具体事件的时候是将netty的Channel屏蔽了的,统一用NettyChannel去处理,NettyChannel内部会维护Netty原生的channel,由此可知,dubbo完全屏蔽了netty的处理逻辑,实现了具体业务和通信底层的剥离。
  在构建这个handler的时候,会将NettyServer(NettyServer拥有ChannelHandler的特性)维护进去。然后将NettyServer的channels指向NettyHandler的channels,方便获取;

文末

总结

  上诉简单的说了远程服务暴露的两个步骤,即:生成Exporter,开启传输服务(netty)。距离完整暴露完成还差一个针对注册中心的相关操作。

还望赐教

  鉴于本人才疏学浅,谈到的内容若有不对的地方烦请告知~也期望与大家一起交流共同进步。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值