Dubbo之服务暴露

源码共读

共 5031字,需浏览 11分钟

 · 2021-02-21

Python实战社群

Java实战社群

长按识别下方二维码,按需求添加

扫码关注添加客服

进Python社群▲

扫码关注添加客服

进Java社群


作者丨ytao

来源丨ytao

前言

本文 Dubbo 使用版本 2.7.5

Dubbo 通过使用 dubbo:service配置或 @service在解析完配置后进行服务暴露,供服务消费者消费。

Dubbo 的服务暴露有两种:

  • 远程暴露

  • 本地暴露

可以通过 scope显式指定暴露方式:

  • none 不暴露

  • remote 远程暴露

  • local 本地暴露

服务暴露流程

下面是一个服务暴露的流程图:

ProxyFactory 是动态代理,用来创建 Invoker 对象,实现代理使用 JavassistProxyFactoryJdkProxyFactory

Invoker 是一个服务对象实例,Dubbo 框架的实体域。它可以是一个本地的实现,一个远程的实现或一个集群的实现,可以向它发起 Invoker 调用。

Protocol 是服务域,负责 Invoker 的生命周期管理,是 Invoker 暴露和引用的主要功能入口,对应该类的 exportrefer方法。

Exporter 是根据不同协议暴露 Invoker 进行封装的类,它会根据不同的协议头进行识别(比如:registry://dubbo://),调用对应 XXXProtocolexport()方法。

从上图中可以看到,Dubbo 中服务暴露分为两个大步骤:第一步通过代理将服务实例转换成 Invoker,这就是通过我们常用的反射实现。第二步将 Invoker 根据具体的协议转换成 Exporter,这是就是我们要分析的核心。从这里可以看到 Dubbo 服务对象都是围绕 Invoker 进行工作。

远程暴露

服务远程暴露从字面上理解,就是将服务跨网络进行远程通信,并非同一 JVM 中的服务进行调用。服务最后都是转换成 org.apache.dubbo.config.spring.ServiceBean,它的UML类图:

ServiceBean继承自 ServiceConfig,服务在 ServiceConfig#doExportUrls根据不同协议进行暴露。

通过获取所有注册中心实例(registryURLs)后,进行依次暴露,暴露操作在 doExportUrlsFor1Protocol中。

  1. privatevoid doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List registryURLs) {

  2. Map<String, String> map = newHashMap<String, String>();

  3. // 配置信息存入 map


  4. .....


  5. // 获取服务URL

  6. String host = findConfigedHosts(protocolConfig, registryURLs, map);

  7. Integer port = findConfigedPorts(protocolConfig, name, map);

  8. URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/"+ path).orElse(path), map);


  9. .....


  10. String scope = url.getParameter(SCOPE_KEY);

  11. // 如果 scope 配置为 none,则服务不进行暴露

  12. if(!SCOPE_NONE.equalsIgnoreCase(scope)) {


  13. // 本地暴露

  14. if(!SCOPE_REMOTE.equalsIgnoreCase(scope)) {

  15. exportLocal(url);

  16. }

  17. // 远程暴露

  18. if(!SCOPE_LOCAL.equalsIgnoreCase(scope)) {

  19. // 判断是否有注册中心

  20. if(CollectionUtils.isNotEmpty(registryURLs)) {

  21. for(URL registryURL : registryURLs) {

  22. //if protocol is only injvm ,not register

  23. if(LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {

  24. continue;

  25. }

  26. url = url.addParameterIfAbsent(DYNAMIC_KEY, registryURL.getParameter(DYNAMIC_KEY));

  27. // 获取监控URL

  28. URL monitorUrl = ConfigValidationUtils.loadMonitor(this, registryURL);

  29. if(monitorUrl != null) {

  30. // 追加监控上报地址,在拦截器上报数据

  31. url = url.addParameterAndEncoded(MONITOR_KEY, monitorUrl.toFullString());

  32. }

  33. // 日志打印

  34. if(logger.isInfoEnabled()) {

  35. if(url.getParameter(REGISTER_KEY, true)) {

  36. logger.info("Register dubbo service "+ interfaceClass.getName() + " url "+ url + " to registry "+ registryURL);

  37. } else{

  38. logger.info("Export dubbo service "+ interfaceClass.getName() + " to url "+ url);

  39. }

  40. }


  41. // For providers, this is used to enable custom proxy to generate invoker

  42. String proxy = url.getParameter(PROXY_KEY);

  43. if(StringUtils.isNotEmpty(proxy)) {

  44. registryURL = registryURL.addParameter(PROXY_KEY, proxy);

  45. }


  46. // 将服务对象转换成 Invoker

  47. Invoker invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));

  48. DelegateProviderMetaDataInvoker wrapperInvoker = newDelegateProviderMetaDataInvoker(invoker, this);


  49. // 暴露服务,向注册中心注册服务,进入对应的 RegistryProtocol

  50. Exporter exporter = protocol.export(wrapperInvoker);

  51. exporters.add(exporter);

  52. }

  53. } else{ // 没有注册中心时

  54. if(logger.isInfoEnabled()) {

  55. logger.info("Export dubbo service "+ interfaceClass.getName() + " to url "+ url);

  56. }

  57. Invoker invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, url);

  58. DelegateProviderMetaDataInvoker wrapperInvoker = newDelegateProviderMetaDataInvoker(invoker, this);

  59. // 直接暴露服务

  60. Exporter exporter = protocol.export(wrapperInvoker);

  61. exporters.add(exporter);

  62. }

  63. /**

  64. * 存储Dubbo服务的元数据,元数据可以存储在远端配置中心和本地,默认是存储在本地

  65. * @since 2.7.0

  66. * ServiceData Store

  67. */

  68. WritableMetadataService metadataService = WritableMetadataService.getExtension(url.getParameter(METADATA_KEY, DEFAULT_METADATA_STORAGE_TYPE));

  69. if(metadataService != null) {

  70. metadataService.publishServiceDefinition(url);

  71. }


  72. }

  73. }

  74. this.urls.add(url);

  75. }

上面是代码片段为暴露服务的核心,可以看到 scope 由三个值控制是否暴露和远程或本地暴露,默认远程和本地都暴露。在远程调用中,分为使用注册中心暴露直接暴露(默认dubbo协议),它们之间的区别在url上:

  • 无注册中心:dubbo://192.168.3.19:20880/xxxx

  • 有注册中心:registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=provider&dubbo=2.0.2&export=dubbo://192.168.3.19:20880/xxxx

无注册中心的直接暴露服务。有注册中心的先创建注册中心,再得到 export 的服务地址,然后暴露服务,当服务暴露成功后把服务元数据注册到注册中心。

代码中 protocol#export会根据服务 url 的请求头进行区分不同 XXXProtocol#export的逻辑,比如。目前 Dubbo 中有以下几种:

本地暴露

同一个应用中,可能既要提供服务远程暴露给其他应用引用,也要给自身提供引用。如果只提供远程暴露的话,当自身应用需要引用自身的服务时,需要通过远程通信访问,那么这大大浪费网络资源。这是就需要用 injvm 协议暴露,就是我们所说的本地暴露,无需跨网络远程通信,可以更好的节省资源。通过上面代码中,我们知道本地暴露调用的是 ServiceConfig#exportLocal方法。

本地暴露会指定 injvm 协议,并且 host 指定为本地 127.0.0.1和端口号为0。protocol.export 调用 InjvmProtocol#export 实现:

  1. @Override

  2. public Exporter export(Invoker invoker) throwsRpcException{

  3. returnnewInjvmExporter(invoker, invoker.getUrl().getServiceKey(), exporterMap);

  4. }

export 中返回了 InjvmExporter 实例化对象。

  1. classInjvmExporter extendsAbstractExporter {


  2. privatefinalString key;


  3. privatefinalMap<String, Exporter> exporterMap;


  4. InjvmExporter(Invoker invoker, String key, Map<String, Exporter> exporterMap) {

  5. super(invoker);

  6. this.key = key;

  7. this.exporterMap = exporterMap;

  8. exporterMap.put(key, this);

  9. }


  10. @Override

  11. publicvoid unexport() {

  12. super.unexport();

  13. exporterMap.remove(key);

  14. }


  15. }

本地暴露就比较简单,将 Invoker 直接保存在 InjvmExporter 的 exporterMap 中。

程序员专栏
 扫码关注填加客服 
长按识别下方二维码进群

近期精彩内容推荐:  

 10年巨变,我女神成了我老婆...

 一个程序员的水平能差到什么程度?

 在一个公司死磕5-10年的程序员都怎么样了?

 史上最污技术解读,我竟然秒懂了




在看点这里好文分享给更多人↓↓

浏览 7
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报