服务导出的⼊⼝⽅法为ServiceBean.export(),此⽅法会调⽤ServiceConfig.export()⽅法,进⾏真正的服务导出。
服务导出⼤概原理
服务导出的⼊⼝为ServiceBean中的export()⽅法,当Spring启动完之后,通过接收Spring的
ContextRefreshedEvent事件来触发export()⽅法的执⾏。
⼀个ServiceBean对象就表示⼀个Dubbo服务,ServiceBean对象中的参数就表示服务的参数,⽐如timeout,该对象的参数值来⾄@Service注解中所定义的。
服务导出主要得做两件事情:
1. 根据服务的参数信息,启动对应的⽹络服务器(netty、tomcat、jetty等),⽤来接收⽹络请求
2. 将服务的信息注册到注册中⼼
但是在做这两件事情之前得先把服务的参数确定好,因为⼀个Dubbo服务的参数,除开可以在@Service注解中去配置,还会继承Dubbo服务所属应⽤(Application)上的配置,还可以在配置中⼼或JVM环境变量中去配置某个服务的参数,所以⾸先要做的是确定好当前服务最终的(优先级最⾼)的参数值。
确定好服务参数之后,就根据所配置的协议启动对应的⽹络服务器。在启动⽹络服务器时,并且在⽹络服务器接收请求的过程中,都可以从服务参数中获取信息,⽐如最⼤连接数,线程数,socket超时时间等等。
启动完⽹络服务器之后,就将服务信息注册到注册中⼼。同时还有向注册中⼼注册监听器,监听Dubbo的中的动态配置信息变更。
服务概念的演化
1. DemoService接⼝表示⼀个服务,此时的服务表示服务定义
2. DemoServiceImpl表示DemoService服务的具体实现,此时的服务表示服务的具体实现
3. DemoService+group+version表示⼀个服务,此时的服务增加了分组和版本概念
4.
http://192.168.1.112:80/com.test.DemoService
表示⼀个服务,此时的服务增加了机器IP和Port,表示远程机器可以访问这个URL来使⽤com.tuling.DemoService这个服务
5.
http://192.168.1.112:80/com.test.DemoService
?timeout=3000&version=1.0.1&application=dubbo-demo-provider-application表示⼀个服务,
此时的服务是拥有参数的,⽐如超时时间、版本号、所属应⽤在dubbo中就是⽤的最后⼀种⽅式来表示服务的。
服务导出思路
服务导出要做的⼏件事情:
1. 确定服务的参数
2. 确定服务⽀持的协议
3. 构造服务最终的URL
4. 将服务URL注册到注册中⼼去
5. 根据服务⽀持的不同协议,启动不同的Server,⽤来接收和处理请求
6. 因为Dubbo⽀持动态配置服务参数,所以服务导出时还需要绑定⼀个监听器Listener来监听服务的参数有修改,如果发现有修改,则需要重新进⾏导出。
确定服务的参数
在执⾏ServiceConfig.export()时,此时ServiceConfig对象就代表⼀个服务,我们已经知道了这个服务的名字(就是接⼝的名字),并且此时这个服务可能已经有⼀些参数了,就是@Service注解上所定义的参数。
但是在Dubbo中,除开可以在@Service注解中给服务配置参数,还有很多地⽅也可以给服务配置参数,⽐如:
1. dubbo.properties⽂件,你可以建⽴这个⽂件,dubbo会去读取这个⽂件的内容作为服务的参数,Dubob的源码中叫做PropertiesConfiguration
2. 配置中⼼,dubbo在2.7版本后就⽀持了分布式配置中⼼,你可以在Dubbo-Admin中去操作配置中⼼,分布式配置中⼼就相当于⼀个远程的dubbo.properties⽂件,你可以在Dubbo-Admin中去修改这个dubbo.properties⽂件,当然配置中⼼⽀持按应⽤进⾏配置,也可以按全局进⾏配置两种,在Dubbo的源码中AppExternalConfiguration表示应⽤配置,ExternalConfiguration表示全局配置。
3. 系统环境变量,你可以在启动应⽤程序时,通过-D的⽅式来指定参数,在Dubbo的源码中叫
SystemConfiguration
4. 再加上通过@Service注解所配置的参数,在Dubbo的源码中叫AbstractConfig
从以上分析我们可以看出,在服务导出时,⾸先得确定服务的参数。
当然,服务的参数除开来⾃于服务的⾃身配置外,还可以来⾃其上级。
⽐如如果服务本身没有配置timeout参数,但是如果服务所属的应⽤的配置了timeout,那么这个应⽤下的服务都会继承这个timeout配置。
所以在确定服务参数时,需要先从上级获取参数,获取之后,如果服务本身配置了相同的参数,那么则进⾏覆盖。
确定服务⽀持的协议
确定服务所⽀持的协议还是⽐较简单的,就是看⽤户配了多少个Protocol。和服务参数一样,Protocol也是可以在各个配置点进⾏配置的。
1. ⾸先在SpringBoot的application.properties⽂件中就可能配置了协议
2. 也可能在dubbo.properties⽂件中配置了协议
3. 也可能在配置中⼼中也配置了协议
4. 也可能通过-D的⽅式也配置了协议
所以在服务导出时,需要从以上⼏个地⽅获取协议,结果可能是⼀个协议,也可能是多个协议,从⽽确定出协议。
构造服务最终的URL
有了确定的协议,服务名,服务参数后,⾃然就可以组装成服务的URL了。
但是还有⼀点是⾮常重要的,在Dubbo中⽀持服务动态配置,注意,这个和配置中⼼不是同⼀概念,动态配置是可以在服务导出后动态的去修改服务配置的
。
动态配置,其实就是继续给服务增加了⼀些参数,所以在把服务的URL注册到注册中⼼去之前,得先按照动态配置中所添加的配置重写⼀下URL,也就是应⽤上动态配置中的参数。
只有这样作完之后得到的URL才是真正准确的服务提供者URL。
将服务URL注册到注册中⼼去
有了准确的服务URL之后,就可以把URL注册到注册中⼼上去了。
4
这个步骤并不麻烦,只不过这⾥要去寻找⽤户是否配置了多个注册中⼼,将服务URL注册到每个注册中⼼去。
根据服务URL启动Server
在服务URL中指定了协议,⽐如Http协议、Dubbo协议。根据不同的协议启动对应的Server。 ⽐如Http协议就启动Tomcat、Jetty。 ⽐如Dubbo协议就启动Netty。
不能只启动Server,还需要绑定⼀个RequestHandler,⽤来处理请求。 ⽐如,Http协议对应的就是InternalHandler。Dubbo协议对应的就是ExchangeHandler。
这⾥来详细分析⼀下Dubbo协议所启动的Server。
1. 调⽤DubboProtocol的openServer(URL url)⽅法开启启动Server
2. 调⽤DubboProtocol的createServer(url)⽅法,在createServer()⽅法中调⽤Exchangers.bind(url,
requestHandler)得到⼀个ExchangeServer
3. 其中requestHandler表示请求处理器,⽤来处理请求
4. 在Exchangers.bind(url, requestHandler)中,先会根据URL得到⼀个Exchanger,默认为
HeaderExchanger
5. HeaderExchanger中包括HeaderExchangeClient、HeaderExchangeServer
6. HeaderExchangeClient负责发送⼼跳,HeaderExchangeServer负责接收⼼跳,如果超时则会关闭channel
7. 在构造HeaderExchangeServer之前,会通过调⽤Transporters.
bind
(url, new DecodeHandler(new HeaderExchangeHandler(handler)))⽅法的到⼀个Server
8. 默认会使⽤getTransporter去bind(URL url, ChannelHandler listener)从⽽得到⼀个Servlet,此时
的listener就是外部传进来的DecodeHandler
9. 在NettyTransporter的bind⽅法中会去new NettyServer(url, listener),所以上⾯返回的Server默认就是NettyServer
10. 在构造NettyServer时,会调⽤ChannelHandlers.wrap(handler,ExecutorUtil.setThreadName(url,
SERVER_THREAD_POOL_NAME))再构造⼀个ChannelHandler。
11. wrap中的handler就是上⾯的listener
12. 在wrap⽅法中会调⽤new MultiMessageHandler(new
HeartbeatHandler(ExtensionLoader.getExtensionLoader(Dispatcher.class).getAdaptiveExtens
ion().dispatch(handler, url)));构造⼀个ChannelHandler。
13. 构造完ChannelHandler后,就是真正的去开启Server了,会调⽤AbstractServer抽象类的doOpen⽅法。
14. 在NettyServer中,会实现doOpen⽅法,会调⽤new NettyServerHandler(getUrl(), this)构造⼀个NettyServerHandler,并bind地址
15. ⾄此,DubboProtocol协议的启动Server流程就结束。
总结⼀下DubboProtocol协议的RequestHandler链路:
![](https://i-blog.csdnimg.cn/blog_migrate/ce50f990469745ea06620477d271145d.png)
1. NettyServerHandler:与NettyServer直接绑定的请求处理器,负责从Netty接收到请求,
channelRead()⽅法获取到请求,然后调⽤下⼀层的Handler(NettyServer)的received()⽅法将请求传递下去,此时的请求还是Object msg
2. NettyServer:NettyServer的⽗类AbstractPeer中存在received(),该⽅法没有做什么,直接把msg传递给下⼀层Handler(MultiMessageHandler)
3. MultiMessageHandler:此Handler会判断msg是否是⼀个MultiMessage,如果是,则对
MultiMessage进⾏拆分,则把拆分出来的msg传递给下层Handler(HeartbeatHandler),如果不
是,则直接把msg传递给下层Handler(HeartbeatHandler)
4. HeartbeatHandler:此Handler通过received()⽅法接收到msg,然后判断该msg是不是⼀个⼼跳请求或⼼跳响应,如果是⼼跳请求,则此Handler返回⼀个Response对象(很简单的⼀个对象),如果是⼼跳响应,则打印⼀个⽇志,不会有其他逻辑,如果都不是,则把msg传递给下层
Handler(AllChannelHandler)。
5. AllChannelHandler:此Handler通过received()⽅法接收到msg,然后把msg封装为⼀个
ChannelEventRunnable对象,并把ChannelEventRunnable扔到线程池中去,异步去处理该msg。在ChannelEventRunnable中会把msg交给下⼀个Handler(DecodeHandler)
6. DecodeHandler:此Handler通过received()⽅法接收到msg,会对msg解析decode解码,然后交给下⼀个Handler(HeaderExchangeHandler)
7. HeaderExchangeHandler:此Handler通过received()⽅法接收到msg,会判断msg的类型
a. 如果Request是TwoWay,则会调⽤下⼀个Handler(DubboProtocol中的requestHandler)的
reply⽅法得到⼀个结果,然后返回;
b. 如果Request不是TwoWay,则会调⽤下⼀个Handler(DubboProtocol中的requestHandler)的
received⽅法处理该请求,不会返回结果
8. requestHandler:此Handler是真正的处理请求逻辑,在received()⽅法中,如果msg是Invocation,则会调⽤reply⽅法,但不会返回reply⽅法所返回的结果,在reply⽅法中把msg强制转换为Invocation类型 inv,然后根据inv得到对应的服务Invoker,然后调⽤invoke(inv)⽅法,得到结果。
服务导出源码流程
1. ServiceBean.export()⽅法是导出的⼊⼝⽅法,会执⾏ServiceConfig.export()⽅法完成服务导出,导出完了之后会发布⼀个Spring事件ServiceBeanExportedEvent
2. 在ServiceConfig.export()⽅法中会先调⽤checkAndUpdateSubConfigs(),这个⽅法主要完成
AbstractConfig的参数刷新(从配置中⼼获取参数等等),AbstractConfig是指ApplicationConfig、
ProtocolConfig、ServiceConfig等等,刷新完后会检查stub、local、mock等参数是否配置正确
3. 参数刷新和检查完成了之后,就会开始导出服务,如果配置了延迟导出,那么则按指定的时间利⽤ScheduledExecutorService来进⾏延迟导出
4. 否则调⽤doExport()进⾏服务导出
5. 继续调⽤doExportUrls()进⾏服务导出
6. ⾸先通过loadRegistries()⽅法获得所配置的注册中⼼的URL,可能配了多个配置中⼼,那么当前所导出的服务需要注册到每个配置中⼼去,这⾥,注册中⼼是以URL的⽅式来表示的,使⽤的是什么注册中⼼、注册中⼼的地址和端⼝,给注册中⼼所配置的参数等等,都会存在在URL上,此URL以registry://开始
7. 获得到注册中⼼的registryURLs之后,就会遍历当前服务所有的ProtocolConfig,调⽤
doExportUrlsFor1Protocol(protocolConfig, registryURLs);⽅法把当前服务按每个协议每个注册中
⼼分别进⾏导出
8. 在doExportUrlsFor1Protocol()⽅法中,会先构造⼀个服务URL,包括
a. 服务的协议dubbo://,
b. 服务的IP和PORT,如果指定了就取指定的,没有指定IP就获取服务器上⽹卡的IP,
c. 以及服务的PATH,如果没有指定PATH参数,则取接⼝名
d. 以及服务的参数,参数包括服务的参数,服务中某个⽅法的参数
e. 最终得到的URL类似: dubbo://192.168.1.110:20880/com.tuling.DemoService?
timeout=3000&&sayHello.loadbalance=random
9. 得到服务的URL之后,会把服务URL作为⼀个参数添加到registryURL中去,然后把registryURL、服务的接⼝、当前服务实现类ref⽣成⼀个Invoker代理对象,再把这个代理对象和当前ServiceConfig对象包装成⼀个DelegateProviderMetaDataInvoker对象,DelegateProviderMetaDataInvoker就表示了完整的⼀个服务
10. 接下来就会使⽤Protocol去export导出服务了,导出之后将得到⼀个Exporter对象(该Exporter对象,可以理解为主要可以⽤来卸载(unexport)服务,什么时候会卸载服务?在优雅关闭Dubbo应⽤的时候)
11. 接下来我们来详细看看Protocol是怎么导出服务的?
12. 但调⽤protocol.export(wrapperInvoker)⽅法时,因为protocol是Protocol接⼝的⼀个Adaptive对
象,所以此时会根据wrapperInvoker的genUrl⽅法得到⼀个url,根据此url的协议找到对应的扩展点,此时扩展点就是RegistryProtocol,但是,因为Protocol接⼝有两个包装类,⼀个是
ProtocolFilterWrapper、ProtocolListenerWrapper,所以实际上在调⽤export⽅法时,会经过这两
个包装类的export⽅法,但是在这两个包装类的export⽅法中都会Registry协议进⾏了判断,不会做过多处理,所以最终会直接调⽤到RegistryProtocol的export(Invoker<
T
> originInvoker)⽅法
13. 在RegistryProtocol的export(Invoker<
T
> originInvoker)⽅法中,主要完成了以下⼏件事情:
a. ⽣成监听器,监听动态配置中⼼此服务的参数数据的变化,⼀旦监听到变化,则重写服务URL,并且在服务导出时先重写⼀次服务URL
b. 拿到重写之后的URL之后,调⽤doLocalExport()进⾏服务导出,在这个⽅法中就会调⽤
DubboProtocol的export⽅法去导出服务了,导出成功后将得到⼀个ExporterChangeableWrapper
i. 在DubboProtocol的export⽅法中主要要做的事情就是启动NettyServer,并且设置⼀系列的
RequestHandler,以便在接收到请求时能依次被这些RequestHandler所处理
ii. 这些RequestHandler在上⽂已经整理过了
c. 从originInvoker中获取注册中⼼的实现类,⽐如ZookeeperRegistry
d. 将重写后的服务URL进⾏简化,把不⽤存到注册中⼼去的参数去除
e. 把简化后的服务URL调⽤ZookeeperRegistry.registry()⽅法注册到注册中⼼去
f. 最后将ExporterChangeableWrapper封装为DestroyableExporter对象返回,完成服务导出
Exporter架构
⼀个服务导出成功后,会⽣成对应的Exporter:
1. DestroyableExporter:Exporter的最外层包装类,这个类的主要作⽤是可以⽤来unexporter对应的服务
2. ExporterChangeableWrapper:这个类主要负责在unexport对应服务之前,把服务URL从注册中⼼中移除,把该服务对应的动态配置监听器移除
3. ListenerExporterWrapper:这个类主要负责在unexport对应服务之后,把服务导出监听器移除
4. DubboExporter:这个类中保存了对应服务的Invoker对象,和当前服务的唯⼀标志,当NettyServer接收到请求后,会根据请求中的服务信息,找到服务对应的DubboExporter对象,然后从对象中得到Invoker对象
服务端Invoker架构
1. ProtocolFilterWrapper$CallbackRegistrationInvoker:会去调⽤下层Invoker,下层Invoker执⾏完了之后,会遍历过滤器,查看是否有过滤器实现了ListenableFilter接⼝,如果有,则回调对应的
onResponse⽅法,⽐如TimeoutFilter,当调⽤完下层Invoker之后,就会计算服务的执⾏时间
2. ProtocolFilterWrapper$1:ProtocolFilterWrapper中的过滤器组成的Invoker,利⽤该Invoker,可以执⾏服务端的过滤器,执⾏完过滤器之后,调⽤下层Invoker
3. RegistryProtocol$InvokerDelegate:服务的的委托类,⾥⾯包含了
DelegateProviderMetaDataInvoker对象和服务对应的providerUrl,执⾏时直接调⽤下层Invoker
4. DelegateProviderMetaDataInvoker:服务的的委托类,⾥⾯包含了AbstractProxyInvoker对象和
ServiceConfig对象,执⾏时直接调⽤下层Invoker
5. AbstractProxyInvoker:服务接⼝的代理类,绑定了对应的实现类,执⾏时会利⽤反射调⽤服务实现类实例的具体⽅法,并得到结果