Dubbo服务导出源码解析

Dubbo服务导出字面意思就是,把生产者的服务,注册到注册中心。加了@Service注解的服务可以被理解为Dubbo的一种服务,这些服务会被转化成ServiceBean(父类是ServiceConfig),一个服务对应一个ServiceBean,里面包装的参数会在dubbo中被解析成一个个Url然后上传到注册中心中去;所以Dubbo服务导出需要考虑以下几个问题

        1.服务导出是在什么时候

        2.Dubbo底层是netty,是怎么启动的

        3.该如何将Url注册到注册中心

        4.如果注册中心上的数据发生变更又该如何监听
        

1.Dubbo服务导出的入口是ServiceBean.export():
      Spring在启动完之后会发布事件(ContextRefreshedEvent),Dubbo会随着收到事件通知而去进行导出(调用ServiceConfig.export()),首先会确定服务的参数checkAndUpdateSubConfigs()

        @Service中可以配置一些参数,以此来确定一个服务,配置参数也不仅仅是通过这个方法,还可以通过在VmOptions中通过-D配置(系统环境变量),dubbo.properties文件,或者Dubbo自己所属的动态配置中心,可以通过这四种方法去确定服务的配置,这些配置如果配置了同一个参数,也有优先级

以下是通过这四种方式确定参数的优先级以及这四种方式在Dubbo中对应的实现类

        SystemConfiguration(系统环境变量) -> AppExternalConfiguration(应用动态配置) -> ExternalConfiguration(全局动态配置) ->
AbstractConfig(@Service) -> PropertiesConfiguration(dubbo.properties文件)
可以看出,-D⽅式配置的参数优先级最⾼,动态配置中⼼次之,注解随后,dubbo.properties最后;
d3bdf27a5e1d4ca8b31e68a5d7187d50.png
        确定参数这一块设计到的逻辑比较多,挑其中一块来看一下,在上述checkAndUpdateSubConfigs()方法中有启动Dubbo动态配置中心的一个方法(startConfigCenter()),这里面的主要逻辑就是会去启动态配置中心,然后会拉取一下配置里面的配置,然后将其放入到externalConfigurationMap    appExternalConfigurationMap这两个Map中,然后会根据这些参数刷新ServiceConfig的属性
1edae58b0b1246dd8f889d1d93b0d5b1.png
        紧接着就会执行doExport方法:因为上面已经构造了ServiceConfig以及确定了他的参数,那么后续的操作就是解析这个类->URL,确定注册中心地址,然后再根据协议(protocol去启动对应的服务),然后再将数据发送到注册中心,再去监听注册中心的Path用于实时更新数据和刷新;大致就是这个方法里面在做的事情;
05d4be9ed69544bb8f3d62ec951ffdca.png
      首先肯定是加载注册中心地址
             loadRegistries中做的事情就是根据你自己的配置,比如以下可以在properties文件中配置来确定到底有多少个注册中心
dubbo.registries.r1.address=zookeeper://127.0.0.1:2181
dubbo.registries.r1.timeout=3000

       registries就是上面对应的配置dubbo.registries后面跟着的数量,你配了几个就是几个,然后就会解析这些参数放到一个map里面,最后就是调用parseURLs(address,map),根据你传入的map来构造注册中心的地址(REGISTRY_URL),最后就是放到一个List中然后返回   

registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=dubbo-demo-provider1application&dubbo=2.0.2&logger=log4j&pid=10920&registry=zookeeper&release=2.7.0&timeout=3000&timestamp=1703071491179

de625403e04c48358f5cebd85c5a65e0.png

        然后接下来就会遍历你配置的protocol(协议)去确定对应的providerUrl->ServiceConfig.doExportUrlsFor1Protocol(),前面的那些方法也跟上面的差不多,会去确定一些服务的参数然后放到一个map里面,然后根据map生成一个providerUrl,这里面就包括很多东西,比如这个服务里面有多少个方法(methods,这个服务可以绑定对应的token,用什么协议(protocol))
providerUrl:dubbo://172.16.5.151:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=dubbo-demo-provider1-application&bean.name=ServiceBean:org.apache.dubbo.demo.DemoService:1.0.1:tulings&bind.ip=172.16.5.151&bind.port=20880&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&group=tulings&interface=org.apache.dubbo.demo.DemoService&logger=log4j&methods=sayHello&pid=10920&release=2.7.0&revision=1.0.1&side=provider&timestamp=1703071655680&version=1.0.1
851c1d32ca03474fb71cee4209f4a627.png
b03889f63b6f4e729629cde0ce2262e6.png
        确定完providerUrl之后,便会遍历刚刚确定完的registryList,然后这里面会用ProxyFactory(默认是用JavassistProxyFactory)生成一个Invoker,这个Invoker其实是一个执行器,里面的属性由ref,interfaceClass,还有registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString())这个方法的返回值构成,这个方法的作用就是将providerUrl拼接到registryUrl后面得到一个新的URL,所以这里面的URL属性是最全的可以通过getURL获取到ip,port,protocol等属性值,所以invoker可以认为是对ref(被代理对象)的一个代理增强,在执行Invoker.invoke()方法的时候实际上就是在执行被代理对象的那个方法
        生成invoker之后在外面再包一层Invoker(调用的时候会层层调用各个Invoker的invoke方法,责任链模式)
invoker.getUrl():registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=dubbo-demo-provider1-application&dubbo=2.0.2&export=dubbo://172.16.5.151:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=dubbo-demo-provider1-application&bean.name=ServiceBean:org.apache.dubbo.demo.DemoService:1.0.1:tulings&bind.ip=172.16.5.151&bind.port=20880&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&group=tulings&interface=org.apache.dubbo.demo.DemoService&logger=log4j&methods=sayHello&pid=10920&release=2.7.0&revision=1.0.1&side=provider&timestamp=1703071655680&version=1.0.1&logger=log4j&pid=10920&registry=zookeeper&release=2.7.0&timeout=3000&timestamp=1703071491179
a4d44d470b714917b19544d39554e079.png
ac65b209eef345bdbb3ed612d575cce4.png
        然后得到Invoker之后便会调用protocol来进行export(导出操作),每一个protocol肯定也有自己的export逻辑,因为你的invoker.getUrl()里面现在对应的protocol是registry,所以肯定是先调用RegistryProtocol.export()(这个Spi就是会解析你的invoker里面的getUrl方法,然后获取里面的协议再根据协议去调用export方法)
        RegistryProtocol.export()方法中首先做的就是得到两个地址,一个是registryUrl,一个是providerUrl,这两地址来源于传进来的Invoker.getUrl()
        registryUrl:其实就是将开头的协议registry:改成了Invoker.getUrl()中的registry参数对应的值,我们这边默认是zookeeper
        providerUrl就是之前我们配置的那个providerURL(因为invoker里面的Url是registryUrl和providerUrl拼接来的,所以里面的逻辑就是再拆出来),这里面的protocol是dubbo,所以后续肯定会调用DubboProtocol.export()
45633b99ae2449b98edb314e6d823cd5.png
        首先会得到注册中心和服务的url,然后通过调用overrideUrlWithConfig方法去重写providerUrl,然后再根据dubooProtocol启动netty,然后整合providerUrl和registryUrl并将其注册到注册中心去
ffd0303c9c1b4663b15a843ff32890b4.png

 重写url:在overrideUrlWithConfig这个方法里会利用providerConfigurationListener和serviceConfigurationListener去重写providerUrl

providerConfigurationListener表示应用级别的动态配置监听器, 

serviceConfigurationListener表示服务级别的动态配置监听器

可以看到服务级别的重写会覆盖应用级别的覆盖(因为写在最后一行)

178dd22a1f494c229e1eec31c83d094b.png

        因为应用监听是在属性那边就直接初始化好了,所以不需要再去初始化一遍,这边直接来看服务监听,可以看到调用了initWith方法并且传入了一个path进去(就是我们监听数据变化的地址)

451bc78163e24d98a126d90e3a859f42.png

在initWith方法中,便可看到为这个路径添加了监听器(this),所以数据有变动的时候便会触发到你自己本身的ServiceConfigurationListener.notifyOverrides()->RegistryProtocol.doOverrideIfNecessary()然后去重写Invoker重写推送数据至注册中心

5e817f1fd4fc4979841893ec3789506f.png

b57c931e6bfc4b47b3d9da0971b86256.png

启动netty服务

        doLocalExport方法下面就会根据你传进来的invokerDelegate.getUrl()中的protocol协议去调用对应的export方法

6b89dff931e844bc8f36350b8bf5f752.png

        然后之前的步骤会先根据invoker造出一个exporter,后续调用的时候就可以直接从本地缓存拿,然后接着就会开启openServer下的createSever方法

49f30a7af1e44f4d808bdb1c89a842f0.png

bcac0f8271df4107b17cf09f00964c7b.png

开启Netty服务的链路比较长,这边就用调用链来代替:

server = Exchangers.bind(url, requestHandler)->HeaderExchanger.bind(URL url, ExchangeHandler handler)->Transporters.bind(URL url, ChannelHandler... handlers)->NettyTransporter.bind(URL url, ChannelHandler listener)->NettyServer.super()->AbstractServer.doOpen()->NettyServer.doOpen():最终启动Netty服务

启动netty服务之后便会调用register(URL registryUrl, URL registeredProviderUrl)->FailbackRegistry.doRegister()->ZookeeperRegistry.doRegister()去注册数据,最后存到注册中心的url是简化后的registeredProviderUrl,而不是整个registryUrl和providerUrl拼起来的,因为有很多参数是不需要的或者重复的,这段简化的url逻辑在RegistryProtocol.getRegisteredProviderUrl(providerUrl, registryUrl)中体现a60b738e9d6c45beb21e4be0f8f48db2.png

  • 21
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值