Dubbo 基础笔记

1. RPC 场景和过程

1.1. RPC 场景

在微服务环境下,会存在大量的跨 JVM (不同服务器之间)进行方法调用的场景,服务结构如下图:
分布式服务结构
具体到一次调用过程来说, A 机器能通过网络,调用 B 机器内的某个服务方法,并得到返回值
服务调用

1.2. RPC 的逐步实现过程

1.2.1. 服务接口的本地调用

从本质上来讲,某个 JVM 内的对象方法,是无法在 JVM 外部被调用的,如下例:

  1. public interface OrderService {
  2. /**
  3. * 使用反射调用时,需要明确目标对象,方法,参数
  4. */
  5. OrderEntiry getDetail(String id);
  6. }
  7. @Service
  8. public class OrderServiceImpl implements OrderService {
  9. private static final Logger LOGGER = LoggerFactory.getLogger(OrderServiceImpl.class);
  10. @Override
  11. public OrderEntiry getDetail(String id) {
  12. LOGGER.info("{}被调用一次:{}", super.getClass().getName(), System.currentTimeMillis());
  13. OrderEntiry orderEntiry = new OrderEntiry();
  14. orderEntiry.setId("O0001");
  15. orderEntiry.setMoney(1000);
  16. orderEntiry.setUserId("U0001");
  17. return orderEntiry;
  18. }
  19. }

在本地调用接口

  1. @SpringBootApplication
  2. public class ProviderApplication {
  3. /* 日志对象 */
  4. private static final Logger LOGGER = LoggerFactory.getLogger(ProviderApplication.class);
  5. public static void main(String[] args) {
  6. // 启动容器
  7. ConfigurableApplicationContext context = SpringApplication.run(ProviderApplication.class, args);
  8. LOGGER.info("---------spring启动成功--------");
  9. /* 本地调用接口 */
  10. LOGGER.info("============ 调用本地接口 ==============");
  11. OrderService orderService = context.getBean(OrderService.class); // 根据接口类型获取实例
  12. OrderEntiry entiry = orderService.getDetail("1");
  13. LOGGER.info("测试orderService.getDetail调用功能,调用结果:{}", JSON.toJSONString(entiry));
  14. }
  15. }

注:以上示例orderService.getDetail("1")的这一行程序调用,是无法脱离本地 jvm 环境被调用的。

1.2.2. RPC 的实现切入口 - 反射调用

  • 为了解决不同 JVM 环境(不同服务器)之间的 JAVA 对象方法的调用,可以使用反射模式来解决
  • 可以设想,只要通过网络传输,传入反射需要的目标对象(如:OrderService)、方法名称(如:getDetail)、方法的参数值(如:”1”),这样就可以告知另一个JVM环境(服务器)需要调用反射的信息(target/method/arg),通过反射将 orderService.getDetail 调用起来

封装反射工具类

  1. package com.moon.utils;
  2. import org.springframework.context.ApplicationContext;
  3. import java.lang.reflect.InvocationTargetException;
  4. import java.lang.reflect.Method;
  5. import java.util.Map;
  6. /**
  7. * 反射调用方法工具类
  8. */
  9. public class InvokeUtils {
  10. /**
  11. * java反射
  12. */
  13. public static Object call(Object target, String methodName, Class[] argTypes, Object[] args)
  14. throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
  15. Method method = target.getClass().getMethod(methodName, argTypes);
  16. // 反射调用方法
  17. return method.invoke(target, args);
  18. }
  19. /* 重载方法 */
  20. public static Object call(Map<String, String> info, ApplicationContext context) {
  21. String targetStr = info.get("target"); // 获取需要调用的接口全限定名
  22. String methodName = info.get("methodName"); // 获取调用的方法名
  23. String arg = info.get("arg"); // 获取调用方法的参数值
  24. try {
  25. // context.getBean(Class.forName(targetStr)) 此方法根据接口的类型,获取spring容器中对应的实现类实例
  26. return call(context.getBean(Class.forName(targetStr)), methodName, new Class[]{String.class}, new Object[]{arg});
  27. } catch (Exception e) {
  28. e.printStackTrace();
  29. }
  30. return null;
  31. }
  32. }

通过反射调用方法

  1. /* 通过反射调用本地接口 */
  2. LOGGER.info("============ 反射调用本地接口 ==============");
  3. Map<String, String> info = new HashMap<>();
  4. info.put("target", "com.moon.dubbo.service.OrderService");
  5. info.put("methodName", "getDetail");
  6. info.put("arg", "1");
  7. Object result = InvokeUtils.call(info, context);
  8. LOGGER.info("测试InvokeUtils.call调用功能,调用结果:{}", JSON.toJSONString(result));

1.2.3. 网络通信传递反射信息

  • 在上一节的步骤中,可以知道,只要传递 target/method/arg等三要素,就可以执行想要的目标服务方法,所以目前只需要解决target/method/arg 三种信息的网络传输问题即可
  • 网络通信的方法很多,如 http/rmi/webservice 等等。本次示例选用 JDK 的 rmi 方式,其使用方式如下

    1.2.3.1. 定义一个接口继承自remote接口
  • 创建InfoService接口 ``` package com.moon.service;

import java.rmi.Remote; import java.rmi.RemoteException; import java.util.Map;

/**

  • 基础RPC测试网络传输远程请求接口,继承remote接口 */ public interface InfoService extends Remote {

    / 网络传输url / String RMI_URL = “rmi://127.0.0.1:9080/InfoService”; / 端口号 / int port = 9080;

    Object sayHello(String name) throws RemoteException;

    /**

    • 传输信息方法 *
    • @param info 需要调用的方法的反射信息
    • @return 远程方法执行的结果
    • @throws RemoteException */ Object passInfo(Map info) throws RemoteException; }
  1. - 创建一个实现类(为简化实现,继承 UnicastRemoteObject 类)

package com.moon.service;

import com.alibaba.fastjson.JSON; import org.slf4j.Logger; import org.slf4j.LoggerFactory;

import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; import java.util.Map;

/**

  • 网络传输远程请求接口实现 */ public class InfoServiceImpl extends UnicastRemoteObject implements InfoService {

    / 日志对象 / private static final Logger LOGGER = LoggerFactory.getLogger(InfoServiceImpl.class);

    public InfoServiceImpl() throws RemoteException {

    1. super();

    }

    @Override public Object sayHello(String name) throws RemoteException {

    1. return name + ",成功调用InfoService.sayHello()方法";

    }

    @Override public Object passInfo(Map info) throws RemoteException {

    1. LOGGER.info("成功调通了InfoService.passInfo()方法,参数:{}", JSON.toJSONString(info));
    2. // 增加返回的信息
    3. info.put("msg", "你好,调通了!");
    4. return info;

    } }

  1. ##### 1.2.3.2. 服务提供方通过 RMI 开放服务到指定 URL
  2. 只需要将实例绑定注册到指定的 `URL` `port` 上,远程即可调用此实例

@SpringBootApplication public class ProviderApplication { public static void main(String[] args) throws MalformedURLException, RemoteException, AlreadyBoundException { // ignore some code… / 3. 通过rmi网络通信进行调用接口 / initProtocol(); }

  1. /**
  2. * 初始化RmiServer,将Rmi实例绑定注册
  3. */
  4. private static void initProtocol() throws RemoteException, AlreadyBoundException, MalformedURLException {
  5. InfoService infoService = new InfoServiceImpl();
  6. // 注冊通讯端口
  7. LocateRegistry.createRegistry(InfoService.port);
  8. // 注冊通讯路径
  9. Naming.bind(InfoService.RMI_URL, infoService);
  10. LOGGER.info("初始化RMI绑定");
  11. }

}

  1. ##### 1.2.3.3. 消费端通过 RMI 远程 URL 连接并调用

package com.moon;

import com.alibaba.fastjson.JSON; import com.moon.service.InfoService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;

import java.net.MalformedURLException; import java.rmi.Naming; import java.rmi.NotBoundException; import java.rmi.RemoteException; import java.util.HashMap; import java.util.Map;

/**

  • 服务消费者 */ @SpringBootApplication public class ConsumerApplication {

    / 日志对象 / private static final Logger LOGGER = LoggerFactory.getLogger(ConsumerApplication.class);

    public static void main(String[] args) throws RemoteException, NotBoundException, MalformedURLException {

    1. // 启动容器
    2. SpringApplication.run(ConsumerApplication.class, args);
    3. LOGGER.info("---------spring启动成功--------");
    4. /* 如果不是远程调用其他服务的接口,想实现功能,只能编写与服务提供方一模一样的代码,一样的接口实现类 */
    5. /*LOGGER.info("============ 调用本地接口 ==============");
    6. OrderService orderService = context.getBean(OrderService.class);
    7. OrderEntiry entiry = orderService.getDetail("1");
    8. LOGGER.info("测试orderService.getDetail调用功能,调用结果:{}", JSON.toJSONString(entiry));*/
    9. /* 1. 测试远程服务调用实现 */
    10. // 获取远程服务实现
    11. InfoService infoService = (InfoService) Naming.lookup(InfoService.RMI_URL);
    12. Object ret = infoService.sayHello("MooNzerO");
    13. LOGGER.info("测试远程调用功能InfoService.sayHello(),调用结果:{}", JSON.toJSONString(ret));
    14. /* 2. 测试远程服务反射调用实现 */
    15. // 封装需要网络传输的反射调用信息
    16. Map<String, String> info = new HashMap<>();
    17. info.put("target", "com.moon.service.OrderService");
    18. info.put("methodName", "getDetail");
    19. info.put("arg", "1");
    20. // 调用传输数据方法
    21. Object result = infoService.passInfo(info);
    22. LOGGER.info("测试远程调用功能InfoService.passInfo(),调用结果:{}", JSON.toJSONString(result));

    } }

  1. > 至此,可以实现了通过RMI跨机器传递需要调用的反射信息(target/method/arg
  2. #### 1.2.4. 远程调用的融合
  3. 经过前两步的实践,已经实现了跨机器的反射信息收发和反射动作调用。现在只需要在InfoService的实现上,对传递过来的info信息,直接发起反射调用即可

@SpringBootApplication public class ProviderApplication { public static void main(String[] args) throws MalformedURLException, RemoteException, AlreadyBoundException { // ignore some code… / 4. 改造,初始化RmiServer时,重写InfoService.passInfo()方法,增加获取网络传输的反射信息后,调用相应方法的逻辑 / initProtocol2(context); }

  1. /**
  2. * 获取反射信息后调用相应的方法
  3. *
  4. * @param context spring 上下文容器
  5. */
  6. private static void initProtocol2(ApplicationContext context) throws RemoteException, AlreadyBoundException, MalformedURLException {
  7. // 创建InfoService实现
  8. InfoService infoService = new InfoServiceImpl() {
  9. // 重写passInfo方法
  10. @Override
  11. public Object passInfo(Map<String, String> info) throws RemoteException {
  12. // 调用父类方法,info包含对象,方法,参数等反射需要的信息
  13. super.passInfo(info);
  14. // 使用反射进去调用
  15. Object result = InvokeUtils.call(info, context);
  16. LOGGER.info("测试调用passInfo()获取反射信息,并反射调用相应方法。调用结果:{}", JSON.toJSONString(result));
  17. return result;
  18. }
  19. };
  20. // 注冊通讯端口
  21. LocateRegistry.createRegistry(InfoService.port);
  22. // 注冊通讯路径
  23. Naming.bind(InfoService.RMI_URL, infoService);
  24. LOGGER.info("初始化RMI绑定");
  25. }

}

  1. 现在远程机器只要通过infoService传递信息过来,就自动将目标服务反射调用,并返回结果值回去,整个RPC过程完成
  2. #### 1.2.5. 对客户端友好的透明化封装
  3. 经过前面的测试示例,RPC的整个调用链条已经拉通,但是还有一个易出错的地方,就是客户端封装反射信息的地方,功能不够内聚,容易出现错误,代码易读性也很差

@SpringBootApplication public class ConsumerApplication { public static void main(String[] args) throws RemoteException, NotBoundException, MalformedURLException { // ignore some code… / 2. 测试远程服务反射调用实现 / // 封装需要网络传输的反射调用信息 Map info = new HashMap<>(); info.put(“target”, “com.moon.dubbo.service.OrderService”); info.put(“methodName”, “getDetail”); info.put(“arg”, “1”); // 调用传输数据方法 Object result = infoService.passInfo(info); LOGGER.info(“测试远程调用功能InfoService.passInfo(),调用结果:{}”, JSON.toJSONString(result)); } }

  1. 改造思路:其实反射需要的target/method/arg这三个信息,全部都可以从接口方法调用`OrderService.getDetail("1")`中得到,所以可以为此接口做一个**静态代理对象**,在代理对象内部完成反射信息的包装

@SpringBootApplication public class ConsumerApplication { public static void main(String[] args) throws RemoteException, NotBoundException, MalformedURLException { // ignore some code… / 3. 优化改造,使用静态代理封装参数准备的过程 / OrderService orderService = getService(infoService); // 透明化调用,不需要调用者去关心调用的准备工作 OrderEntiry entiry = orderService.getDetail(“1”); LOGGER.info(“测试静态代理远程调用InfoService.passInfo()方法,调用结果:{}”, JSON.toJSONString(entiry)); }

  1. /**
  2. * 静态代理
  3. *
  4. * @param infoService 网络传输类
  5. * @return 返回OrderService代理示例
  6. */
  7. private static OrderService getService(InfoService infoService) {
  8. // 返回接口实现
  9. return new OrderService() {
  10. @Override
  11. public OrderEntiry getDetail(String id) {
  12. Map<String, String> info = new HashMap<>();
  13. // 因为知道反射的目标与参数,使用静态代理直接封装相应参数
  14. info.put("target", "com.moon.dubbo.service.OrderService"); // 调用对象
  15. info.put("methodName", "getDetail"); // 调用方法
  16. info.put("arg", id); // 调用参数
  17. OrderEntiry result = null;
  18. try {
  19. result = (OrderEntiry) infoService.passInfo(info);
  20. } catch (RemoteException e) {
  21. e.printStackTrace();
  22. }
  23. return result;
  24. }
  25. };
  26. }

}

  1. 优化后,客户端远程传递反射信息的过程,直接变成调用接口的代理对象即可。调用者,甚至不再需要区分,此接口代理对象到底是谁,像调用正常的本地服务一样使用即可
  2. ## 2. Dubbo 简介
  3. - Apache Dubbo 是一款高性能Java RPC框架
  4. - 官网:[https://dubbo.apache.org/zh/](https://dubbo.apache.org/zh/)
  5. ### 2.1. Dubbo 实现RPC框架
  6. 分布式服务架构下,各个服务间的相互RPC调用会越来越复杂。最终形成网状结构,此时服务的治理极为关键<br />Dubbo 是一个带有服务治理功能的 RPC 框架,提供了一套较为完整的服务治理方案,其底层直接实现了 RPC 调用的全过程,并尽力使 RPC 远程对使用者透明。下图展示了Dubbo服务治理的功能<br />![dubbo-service-governance](https://gitee.com/moonzero/images/raw/master/code-note/20200125181329487_3748.jpg)<br />简单的说,Dubbo本质上就是个服务调用的框架,如果没有分布式的需求,其实是不需要用的,只有在分布式的时候,才有使用Dubbo这样的分布式服务框架的需求。其核心部分包含:
  7. - 远程通讯:提供对多种基于长连接的NIO框架抽象封装,包括多种线程模型、序列化以及“请求-响应”模式的信息交换方式。
  8. - 集群容错:提供基于接口方法的透明远程过程调用,包括多协议支持以及软负载均衡,失败容错、地址路由、动态配置等集群支持。
  9. - 自动发现:基于注册中心目录服务,使服务消费方能动态的查找到×××提供方,使地址透明,使服务提供方可以平滑增加或减少机器。
  10. ### 2.2. Dubbo 的架构及特点
  11. dubbo 的整体结构如下图:<br />![Dubbo架构设计图](https://gitee.com/moonzero/images/raw/master/code-note/20200125183026192_30674.png)
  12. > 参考来源:[http://dubbo.apache.org/zh-cn/docs/dev/design.html](769d0a9fc306a0b27eeaca70a30f4f74)
  13. Dubbo总体架构设计一共划分了10层,而最上面的Service层是留给实际想要使用Dubbo开发分布式服务的开发者实现业务逻辑的接口层。图中左边淡蓝背景的为服务消费方使用的接口,右边淡绿色背景的为服务提供方使用的接口,位于中轴线上的为双方都用到的接口
  14. - 服务接口层(Service):该层是与实际业务逻辑相关的,根据服务提供方和服务消费方的业务设计对应的接口和实现。
  15. - 配置层(Config):对外配置接口,以ServiceConfigReferenceConfig为中心,可以直接new配置类,也可以通过Spring解析配置生成配置类。
  16. - 服务代理层(Proxy):服务接口透明代理,生成服务的客户端Stub和服务器端Skeleton,以ServiceProxy为中心,扩展接口为ProxyFactory
  17. - 服务注册层(Registry):封装服务地址的注册与发现,以服务URL为中心,扩展接口为RegistryFactoryRegistryRegistryService。可能没有服务注册中心,此时服务提供方直接暴露服务。
  18. - 集群层(Cluster):封装多个提供者的路由及负载均衡,并桥接注册中心,以Invoker为中心,扩展接口为ClusterDirectoryRouterLoadBalance。将多个服务提供方组合为一个服务提供方,实现对服务消费方透明,只需要与一个服务提供方进行交互。
  19. - 监控层(Monitor):RPC调用次数和调用时间监控,以Statistics为中心,扩展接口为MonitorFactoryMonitorMonitorService
  20. - 远程调用层(Protocol):封将RPC调用,以InvocationResult为中心,扩展接口为ProtocolInvokerExporterProtocol是服务域,它是Invoker暴露和引用的主功能入口,它负责Invoker的生命周期管理。Invoker是实体域,它是Dubbo的核心模型,其他模型都向它靠扰,或转换成它,它代表一个可执行体,可向它发起invoke调用。它有可能是一个本地的实现,也可能是一个远程的实现,也可能是一个集群实现。
  21. - 信息交换层(Exchange):封装请求响应模式,同步转异步,以RequestResponse为中心,扩展接口为ExchangerExchangeChannelExchangeClientExchangeServer
  22. - 网络传输层(Transport):抽象minanetty为统一接口,以Message为中心,扩展接口为ChannelTransporterClientServerCodec
  23. - 数据序列化层(Serialize):可复用的一些工具,扩展接口为Serialization ObjectInputObjectOutputThreadPool
  24. 从上图可以看出,Dubbo对于服务提供方和服务消费方,从框架的10层中分别提供了各自需要关心和扩展的接口,构建整个服务生态系统(服务提供方和服务消费方本身就是一个以服务为中心的)
  25. ### 2.3. Dubbo 服务的角色关系
  26. 服务提供方和服务消费方之间的调用关系,如图所示:<br />![提供方和消费方的调用关系](https://gitee.com/moonzero/images/raw/master/code-note/20200125184203559_27446.png)
  27. - 节点角色说明
  28. |
  29. 节点
  30. | 角色说明
  31. |
  32. | --- | --- |
  33. |
  34. Provider
  35. | 暴露服务的服务提供方
  36. |
  37. |
  38. Consumer
  39. | 调用远程服务的服务消费方
  40. |
  41. |
  42. Registry
  43. | 服务注册与发现的注册中心
  44. |
  45. |
  46. Monitor
  47. | 统计服务的调用次数和调用时间的监控中心
  48. |
  49. |
  50. Container
  51. | 服务运行容器
  52. |
  53. - 调用关系说明
  54. 1. 服务容器负责启动,加载,运行服务提供者。
  55. 1. 服务提供者在启动时,向注册中心注册自己提供的服务。
  56. 1. 服务消费者在启动时,向注册中心订阅自己所需的服务。
  57. 1. 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
  58. 1. 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
  59. 1. 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心
  60. ## 3. Dubbo 基础配置使用
  61. ### 3.1. xml 文件配置方式
  62. #### 3.1.1. dubbo 标签关系图
  63. ![](https://gitee.com/moonzero/images/raw/master/code-note/20200126122444209_30129.jpg)
  64. > 1. 标签属性有继承关系,即:下层有设置则使用,未配置则沿用上一级的设置
  65. > 1. timeout/retries/loadbalance消费方未设置,则沿用服务方的设置
  66. #### 3.1.2. dubbo 各标签作用汇总表
  67. |
  68. 标签
  69. | 用途
  70. | 解释
  71. |
  72. | --- | --- | --- |
  73. |
  74. `<dubbo:service/>`
  75. | 服务配置
  76. | 用于暴露一个服务,定义服务的元信息,一个服务可以用多个协议暴露,一个服务也可以注册到多个注册中心
  77. |
  78. |
  79. `<dubbo:reference/>`
  80. | 引用配置
  81. | 用于创建一个远程服务代理,一个引用可以指向多个注册中心
  82. |
  83. |
  84. `<dubbo:protocol/>`
  85. | 协议配置
  86. | 用于配置提供服务的协议信息,协议由提供方指定,消费方被动接受
  87. |
  88. |
  89. `<dubbo:application/>`
  90. | 应用配置
  91. | 用于配置当前应用信息,不管该应用是提供者还是消费者
  92. |
  93. |
  94. `<dubbo:module/>`
  95. | 模块配置
  96. | 用于配置当前模块信息,可选
  97. |
  98. |
  99. `<dubbo:registry/>`
  100. | 注册中心配置
  101. | 用于配置连接注册中心相关信息
  102. |
  103. |
  104. `<dubbo:monitor/>`
  105. | 监控中心配置
  106. | 用于配置连接监控中心相关信息,可选
  107. |
  108. |
  109. `<dubbo:provider/>`
  110. | 提供方配置
  111. | `ProtocolConfig` `ServiceConfig` 某属性没有配置时,采用此缺省值,可选
  112. |
  113. |
  114. `<dubbo:consumer/>`
  115. | 消费方配置
  116. | `ReferenceConfig` 某属性没有配置时,采用此缺省值,可选
  117. |
  118. |
  119. `<dubbo:method/>`
  120. | 方法配置
  121. | 用于 `ServiceConfig` `ReferenceConfig` 指定方法级的配置信息
  122. |
  123. |
  124. `<dubbo:argument/>`
  125. | 参数配置
  126. | 用于指定方法参数配置
  127. |
  128. #### 3.1.3. 标签详解
  129. 所有配置项分为三大类,参见以下各个标签作用表中的"作用"一列
  130. - 服务发现:表示该配置项用于服务的注册与发现,目的是让消费方找到提供方
  131. - 服务治理:表示该配置项用于治理服务间的关系,或为开发测试提供便利条件
  132. - 性能调优:表示该配置项用于调优性能,不同的选项对性能会产生影响
  133. - 所有配置最终都将转换为URL表示,并由服务提供方生成,经注册中心传递给消费方,各属性对应URL的参数,参见配置项一览表中的"对应URL参数"列。
  134. > URL 格式:`protocol://username:password@host:port/path?key=value&key=value`
  135. **注意:只有groupinterfaceversion是服务的匹配条件,三者决定是不是同一个服务,其它配置项均为调优和治理参数。**
  136. > **各个标签详细属性配置参考官网:[http://dubbo.apache.org/zh-cn/docs/user/references/xml/introduction.html](da2bebab367232adedabdc1c81d4cae5)**
  137. ##### 3.1.3.1. dubbo:service【常用】
  138. 服务提供者暴露服务配置。对应的配置类:`org.apache.dubbo.config.ServiceConfig`。常用属性如下:
  139. |
  140. 属性
  141. | 对应URL参数
  142. | 类型
  143. | 是否必填
  144. | 缺省值
  145. | 作用
  146. | 描述
  147. | 兼容性
  148. |
  149. | --- | --- | --- | --- | --- | --- | --- | --- |
  150. |
  151. interface
  152. |
  153. | class
  154. | **必填**
  155. |
  156. | 服务发现
  157. | 服务接口名
  158. | 1.0.0+
  159. |
  160. |
  161. ref
  162. |
  163. | object
  164. | **必填**
  165. |
  166. | 服务发现
  167. | 服务对象实现引用
  168. | 1.0.0+
  169. |
  170. |
  171. version
  172. | version
  173. | string
  174. | 可选
  175. | 0.0.0
  176. | 服务发现
  177. | 服务版本,建议使用两位数字版本,如:1.0,通常在接口不兼容时版本号才需要升级
  178. | 1.0.0+
  179. |
  180. |
  181. timeout
  182. | timeout
  183. | int
  184. | 可选
  185. | 1000
  186. | 性能调优
  187. | 远程服务调用超时时间(毫秒)
  188. | 2.0.0+
  189. |
  190. |
  191. protocol
  192. |
  193. | string
  194. | 可选
  195. |
  196. | 配置关联
  197. | 使用指定的协议暴露服务,在多协议时使用,值为`<dubbo:protocol>`id属性,多个协议ID用逗号分隔
  198. | 2.0.5+
  199. |
  200. > 全部属性列表详见官方文档(2.7版本):[https://dubbo.apache.org/zh/docs/v2.7/user/references/xml/dubbo-service/](https://dubbo.apache.org/zh/docs/v2.7/user/references/xml/dubbo-service/)
  201. ##### 3.1.3.2. dubbo:reference【常用】
  202. 服务消费者引用服务配置。对应的配置类:`org.apache.dubbo.config.ReferenceConfig`。常用属性如下:
  203. |
  204. 属性
  205. | 对应URL参数
  206. | 类型
  207. | 是否必填
  208. | 缺省值
  209. | 作用
  210. | 描述
  211. | 兼容性
  212. |
  213. | --- | --- | --- | --- | --- | --- | --- | --- |
  214. |
  215. id
  216. |
  217. | string
  218. | **必填**
  219. |
  220. | 配置关联
  221. | 服务引用BeanId
  222. | 1.0.0以上版本
  223. |
  224. |
  225. interface
  226. |
  227. | class
  228. | **必填**
  229. |
  230. | 服务发现
  231. | 服务接口名
  232. | 1.0.0以上版本
  233. |
  234. ##### 3.1.3.3. dubbo:protocol【常用】
  235. 服务提供者协议配置。对应的配置类:`org.apache.dubbo.config.ProtocolConfig`。同时,如果需要支持多协议,可以声明多个 `<dubbo:protocol>` 标签,并在 `<dubbo:service>` 中通过 protocol 属性指定使用的协议。常用属性如下:
  236. |
  237. 属性
  238. | 对应URL参数
  239. | 类型
  240. | 是否必填
  241. | 缺省值
  242. | 作用
  243. | 描述
  244. | 兼容性
  245. |
  246. | --- | --- | --- | --- | --- | --- | --- | --- |
  247. |
  248. id
  249. |
  250. | string
  251. | 可选
  252. | dubbo
  253. | 配置关联
  254. | 协议BeanId,可以在`<dubbo:service protocol="">`中引用此ID,如果ID不填,缺省和name属性值一样,重复则在name后加序号。
  255. | 2.0.5以上版本
  256. |
  257. |
  258. name
  259. | `<protocol>`
  260. | string
  261. | **必填**
  262. | dubbo
  263. | 性能调优
  264. | 协议名称
  265. | 2.0.5以上版本
  266. |
  267. |
  268. port
  269. | `<port>`
  270. | int
  271. | 可选
  272. | dubbo协议缺省端口为20880rmi协议缺省端口为1099httphessian协议缺省端口为80;如果没有配置port,则自动采用默认端口,如果配置为-1,则会分配一个没有被占用的端口。Dubbo 2.4.0+,分配的端口在协议缺省端口的基础上增长,确保端口段可控。
  273. | 服务发现
  274. | 服务端口
  275. | 2.0.5以上版本
  276. |
  277. |
  278. host
  279. | `<host>`
  280. | string
  281. | 可选
  282. | 自动查找本机IP
  283. | 服务发现
  284. | -服务主机名,多网卡选择或指定VIP及域名时使用,为空则自动查找本机IP,-**建议不要配置,让Dubbo自动获取本机IP**
  285. | 2.0.5以上版本
  286. |
  287. ##### 3.1.3.4. dubbo:registry【常用】
  288. 注册中心配置。对应的配置类:`org.apache.dubbo.config.RegistryConfig`。同时如果有多个不同的注册中心,可以声明多个 `<dubbo:registry>` 标签,并在 `<dubbo:service>` `<dubbo:reference>` registry 属性指定使用的注册中心。常用属性如下:
  289. |
  290. 属性
  291. | 对应URL参数
  292. | 类型
  293. | 是否必填
  294. | 缺省值
  295. | 作用
  296. | 描述
  297. | 兼容性
  298. |
  299. | --- | --- | --- | --- | --- | --- | --- | --- |
  300. |
  301. id
  302. |
  303. | string
  304. | 可选
  305. |
  306. | 配置关联
  307. | 注册中心引用BeanId,可以在`<dubbo:service registry="">``<dubbo:reference registry="">`中引用此ID
  308. | 1.0.16以上版本
  309. |
  310. |
  311. address
  312. | `<host:port>`
  313. | string
  314. | **必填**
  315. |
  316. | 服务发现
  317. | 注册中心服务器地址,如果地址没有端口缺省为9090,同一集群内的多个地址用逗号分隔,如:`ip:port,ip:port`,不同集群的注册中心,请配置多个`<dubbo:registry>`标签
  318. | 1.0.16以上版本
  319. |
  320. |
  321. protocol
  322. | `<protocol>`
  323. | string
  324. | 可选
  325. | dubbo
  326. | 服务发现
  327. | 注册中心地址协议,支持dubbo, multicast, zookeeper, redis, consul(2.7.1), sofa(2.7.2), etcd(2.7.2), nacos(2.7.2)等协议
  328. | 2.0.0以上版本
  329. |
  330. ##### 3.1.3.5. dubbo:monitor
  331. 监控中心配置。对应的配置类:`org.apache.dubbo.config.MonitorConfig`
  332. ##### 3.1.3.6. dubbo:application【常用】
  333. 应用信息配置。对应的配置类:`org.apache.dubbo.config.ApplicationConfig`。常用属性如下:
  334. |
  335. 属性
  336. | 对应URL参数
  337. | 类型
  338. | 是否必填
  339. | 缺省值
  340. | 作用
  341. | 描述
  342. | 兼容性
  343. |
  344. | --- | --- | --- | --- | --- | --- | --- | --- |
  345. |
  346. name
  347. | application
  348. | string
  349. | **必填**
  350. |
  351. | 服务治理
  352. | 当前应用名称,用于注册中心计算应用间依赖关系,**注意:消费者和提供者应用名不要一样,此参数不是匹配条件**,你当前项目叫什么名字就填什么,和提供者消费者角色无关,比如:kylin应用调用了morgan应用的服务,则kylin项目配成kylinmorgan项目配成morgan,可能kylin也提供其它服务给别人使用,但kylin项目永远配成kylin,这样注册中心将显示kylin依赖于morgan
  353. | 1.0.16以上版本
  354. |
  355. ##### 3.1.3.7. dubbo:module
  356. 模块信息配置。对应的配置类:`org.apache.dubbo.config.ModuleConfig`
  357. ##### 3.1.3.8. dubbo:provider
  358. 服务提供者缺省值配置。对应的配置类:`org.apache.dubbo.config.ProviderConfig`。同时该标签为 `<dubbo:service>` `<dubbo:protocol>` 标签的缺省值设置
  359. ##### 3.1.3.9. dubbo:consumer
  360. 服务消费者缺省值配置。配置类:`org.apache.dubbo.config.ConsumerConfig`。同时该标签为 `<dubbo:reference>` 标签的缺省值设置
  361. ##### 3.1.3.10. dubbo:method
  362. 方法级配置。对应的配置类:`org.apache.dubbo.config.MethodConfig`。同时该标签为 `<dubbo:service>` `<dubbo:reference>` 的子标签,用于控制到方法级。如下例:

  1. ##### 3.1.3.11. dubbo:argument
  2. 方法参数配置。对应的配置类:`org.apache.dubbo.config.ArgumentConfig`。该标签为 `<dubbo:method>` 的子标签,用于方法参数的特征描述。如下例:

  1. ##### 3.1.3.12. dubbo:parameter
  2. 选项参数配置。对应的配置类:`java.util.Map`。同时该标签为`<dubbo:protocol>``<dubbo:service>``<dubbo:provider>``<dubbo:reference>``<dubbo:consumer>`的子标签,用于配置自定义参数,该配置项将作为扩展点设置自定义参数使用。如下例:

  1. 或者:
  1. ##### 3.1.3.13. dubbo:config-center
  2. 配置中心。对应的配置类:`org.apache.dubbo.config.ConfigCenterConfig`
  3. #### 3.1.4. xml配置使用示例
  4. > - xml的配置使用示例。详细参考dubbo-sample-xml工程
  5. > - Springmvc的集成Dubbo的使用示例,详细参考busi-mvc工程
  6. ### 3.2. 属性配置方式
  7. 如果项目应用足够简单,例如,不需要多注册中心或多协议,并且需要在spring容器中共享配置。可以直接使用 `dubbo.properties` 作为默认配置。
  8. ### 3.3. 注解配置方式
  9. > 详细示例参考:dubbo-sample-annotation工程
  10. - 注解方式的底层与XML一致,只是表现形式上的不同。
  11. - 目标都是配置Dubbo基础信息,主要涉及以下五个必不可少的信息:`ApplicationConfig``ProtocolConfig``RegistryConfig``service``reference`
  12. #### 3.3.1. [EnableDubbo ](/EnableDubbo ) 开启服务
  13. `@EnableDubbo` 注解:开启注解 Dubbo 功能,其中可以加入 `scanBasePackages` 属性配置包扫描的路径,用于扫描并注册bean。其中封装了组件 `@DubboComponentScan`,来扫描Dubbo框架的 `@Service` 注解暴露 Dubbo 服务,以及扫描 Dubbo 框架的 `@Reference` 字段或者方法注入 Dubbo 服务代理。
  14. #### 3.3.2. [Configuration ](/Configuration ) 方式配置公共信息
  15. `@Configuration`指定配置类,在类中分别将ApplicationConfigProtocolConfigRegistryConfig等类创建到IOC容器中即可,提供者配置(消费者配置方式一样)示例如下:

package com.moon.dubbo.annotation.config;

import com.alibaba.dubbo.config.ApplicationConfig; import com.alibaba.dubbo.config.ProtocolConfig; import com.alibaba.dubbo.config.ProviderConfig; import com.alibaba.dubbo.config.RegistryConfig; import com.alibaba.dubbo.config.spring.context.annotation.EnableDubbo; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;

/**

  • 服务提供者配置类 - 用于配置提供者需要几个主要注解 */ @Configuration // 开启dubbo注解扫描,指定Spring扫描包路径 @EnableDubbo(scanBasePackages = “com.moon.dubbo.annotation.service”) public class ProviderConfiguration {

    /**

    • 提供者全局配置,用于减少重复的配置
    • 相当于xml配置文件中的标签 *
    • @return ProviderConfig */ @Bean public ProviderConfig providerConfig() { ProviderConfig providerConfig = new ProviderConfig(); providerConfig.setTimeout(1000); return providerConfig; }

      /**

    • 必需配置。服务提供方应用名称
    • 相当于xml配置文件中的标签 *
    • @return ApplicationConfig */ @Bean public ApplicationConfig applicationConfig() { ApplicationConfig applicationConfig = new ApplicationConfig(); applicationConfig.setName(“dubbo-annotation-provider”); return applicationConfig; }

      /**

    • 必需配置。注册中心配置
    • 相当于xml配置文件中的标签 *
    • @return RegistryConfig */ @Bean public RegistryConfig registryConfig() { RegistryConfig registryConfig = new RegistryConfig(); registryConfig.setProtocol(“zookeeper”); registryConfig.setAddress(“127.0.0.1”); registryConfig.setPort(2181); return registryConfig; }

      /**

    • 必需配置。通信协议与监听端口
    • 相当于xml配置文件中的标签 *
    • @return ProtocolConfig */ @Bean public ProtocolConfig protocolConfig() { ProtocolConfig protocolConfig = new ProtocolConfig(); protocolConfig.setName(“dubbo”); protocolConfig.setPort(20880); return protocolConfig; } }
  1. #### 3.3.3. property 属性配置方式自动装配公共信息
  2. `@PropertySource`注解方式:使用Springboot属性文件方式,由Dubbo自动将文件信息配置入容器,就类似spring boot自动装配一样,提供者示例如下:
  3. 1. 创建dubbo-provider.properties文件

dubbo.application.name=dubbo-annotation-property-provider dubbo.registry.address=zookeeper://127.0.0.1:2181 dubbo.protocol.name=dubbo dubbo.protocol.port=20880

  1. 2. 使用`@PropertySource`注解读取properties文件

package com.moon.dubbo.annotation.config;

import com.alibaba.dubbo.config.spring.context.annotation.EnableDubbo; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource;

/**

  • 服务提供者配置类 - 用于配置提供者需要几个主要注解 @Configuration // 开启dubbo注解扫描,指定Spring扫描包路径 @EnableDubbo(scanBasePackages = “com.moon.dubbo.annotation.service”) // 自动装配dubbo公共信息 @PropertySource(“classpath:/dubbo-provider.properties”) public class ProviderPropertyConfiguration { }
  1. ### 3.4. API 配置方式
  2. API 配置的方式来配置你的 Dubbo 应用
  3. > 详细示例参考:dubbo-sample-api工程
  4. API 属性与xml配置项一对一,各属性含义。详情参考官网:[https://dubbo.apache.org/zh/docs/v2.7/user/configuration/api/](https://dubbo.apache.org/zh/docs/v2.7/user/configuration/api/)<br />![提供者](https://gitee.com/moonzero/images/raw/master/code-note/20200127123511449_27286.png)
  5. - 提供者

package com.moon.dubbo.api;

import com.alibaba.dubbo.config.ApplicationConfig; import com.alibaba.dubbo.config.RegistryConfig; import com.alibaba.dubbo.config.ServiceConfig; import com.moon.dubbo.api.service.OrderServiceImpl; import com.moon.service.OrderService; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;

/**

  • dubbo服务提供者 - 基于API配置方式 */ @SpringBootApplication public class ApiProvider { public static void main(String[] args) {
    1. SpringApplication.run(ApiProvider.class, args);
    2. // 服务提供者暴露服务配置
    3. ServiceConfig<OrderService> config = new ServiceConfig<>(); // 此实例很重,封装了与注册中心的连接,请自行缓存,否则可能造成内存和连接泄漏
    4. config.setApplication(new ApplicationConfig("dubbo-api-provider"));
    5. // 设置注册中心,多个注册中心可以用setRegistries()
    6. config.setRegistry(new RegistryConfig("zookeeper://127.0.0.1:2181"));
    7. config.setInterface(OrderService.class);
    8. config.setRef(new OrderServiceImpl());
    9. // 暴露及注册服务
    10. config.export();
    11. System.out.println("dubbo-api-provider is running...");
    } }
  1. ![消费者](https://gitee.com/moonzero/images/raw/master/code-note/20200127123537171_23898.png)
  2. - 消费者

package com.moon.dubbo.api;

import com.alibaba.dubbo.config.ApplicationConfig; import com.alibaba.dubbo.config.ReferenceConfig; import com.alibaba.dubbo.config.RegistryConfig; import com.moon.entity.OrderEntiry; import com.moon.service.OrderService; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;

/**

  • ubbo服务消费者 - 基于API配置方式 */ @SpringBootApplication public class ApiConsumer { public static void main(String[] args) {
    1. SpringApplication.run(ApiConsumer.class, args);
    2. // 注意:ReferenceConfig为重对象,内部封装了与注册中心的连接,以及与服务提供方的连接
    3. // 引用远程服务
    4. ReferenceConfig<OrderService> reference = new ReferenceConfig<>(); // 此实例很重,封装了与注册中心的连接以及与提供者的连接,请自行缓存,否则可能造成内存和连接泄漏
    5. reference.setApplication(new ApplicationConfig("dubbo-api-consumer"));
    6. // 设置注册中心,多个注册中心可以用setRegistries()
    7. reference.setRegistry(new RegistryConfig("zookeeper://127.0.0.1:2181"));
    8. reference.setInterface(OrderService.class);
    9. System.out.println("dubbo-api-consumer is running...");
    10. // 和本地bean一样使用xxxService
    11. OrderService orderService = reference.get();
    12. OrderEntiry entiry = orderService.getDetail("2");
    13. System.out.println("基于API配置的消费者调用OrderService.getDetail()接口成功,result: " + entiry.getMoney());
    } }
  1. ## 4. Dubbo控制台部署(前后端分离版本)
  2. - 2.6版本前,在dubbo源码包里,有一个adminwar包,使用tomcat将其部署即可
  3. - 2.6版本之后,dubbo控制台已单独版本管理(目前只到0.1版本),使用了前后端分离的模式。前端使用VueVuetify分别作为Javascript框架和UI框架,后端采用Spring Boot框架。
  4. > 部署参考官网:[http://dubbo.apache.org/zh-cn/docs/admin/introduction.html](983ab5ea0a0423d53a25aba536234a62)
  5. 拉取项目源码:`git clone https://github.com/apache/dubbo-admin.git`
  6. ### 4.1. Maven方式部署
  7. - 安装,此方式即将前端vue产出的静态内容集成到springboot包内

cd dubbo-admin mvn clean package

如果打包不成功,则尝试跳过test。mvn clean package -Dmaven.test.skip=true

cd dubbo-admin-distribution/target java -jar dubbo-admin-0.1.jar

  1. - 访问`http://localhost:8080`
  2. ### 4.2. 前后端分离部署
  3. - 前端

cd dubbo-admin-ui npm install npm run dev

  1. - 后端

cd dubbo-admin-server mvn clean package cd target java -jar dubbo-admin-server-0.1.jar

  1. - 访问:`http://localhost:8081`
  2. ### 4.3. 配置
  3. - 配置文件为:`dubbo-admin-server/src/main/resources/application.properties`
  4. - 主要的配置

admin.config-center=zookeeper://127.0.0.1:2181 admin.registry.address=zookeeper://127.0.0.1:2181 admin.metadata-report.address=zookeeper://127.0.0.1:2181

  1. 三个配置项分别指定了配置中心,注册中心和元数据中心的地址,关于这三个中心的详细说明,可以参考这里。 也可以和Dubbo2.7一样,在配置中心指定元数据和注册中心的地址,以zookeeper为例,配置的路径和内容如下:

/dubbo/config/dubbo/dubbo.properties

dubbo.registry.address=zookeeper://127.0.0.1:2181 dubbo.metadata-report.address=zookeeper://127.0.0.1:2181

  1. 配置中心里的地址会覆盖掉本地application.properties的配置
  2. ### 4.4. 服务治理
  3. 服务治理主要作用是改变运行时服务的行为和选址逻辑,达到限流,权重配置等目的,主要有以下几个功能:
  4. - 应用级别的服务治理
  5. - 标签路由
  6. - 条件路由
  7. - 黑白名单
  8. - 动态配置
  9. - 权重调节
  10. - 负载均衡
  11. - 配置管理
  12. > 详细用法参考官网:[http://dubbo.apache.org/zh-cn/docs/admin/serviceGovernance.html](225c070af32e55c8828b4d1c07efcfc5)
  13. # Dubbo 高级特性进阶 - 常用配置策略用法
  14. ## 1. 启动时检查
  15. Dubbo缺省会在启动时检查依赖的服务是否可用,不可用时会抛出异常,阻止Spring初始化完成,以便上线时,能及早发现问题,默认`check="true"`<br />可以通过`check="false"`关闭检查,比如,测试时,有些服务不关心,或者出现了循环依赖,必须有一方先启动<br />另外,如果Spring容器是懒加载的,或者通过API编程延迟引用服务,也需要关闭`check`属性,否则服务临时不可用时,会抛出异常,拿到null引用,如果`check="false"`,总是会返回引用,当服务恢复时,能自动连上
  16. ### 1.1. 通过 spring 配置文件
  17. - 关闭某个服务的启动时检查 (没有提供者时报错):
  1. - 关闭所有服务的启动时检查 (没有提供者时报错):
  1. - 关闭注册中心启动时检查 (注册订阅失败时报错):
  1. ### 1.2. 通过 dubbo.properties

dubbo.reference.com.foo.BarService.check=false dubbo.reference.check=false # 强制改变所有 reference 的 check 值,就算配置中有声明,也会被覆盖 dubbo.consumer.check=false # 是设置 check 的缺省值,如果配置中有显式的声明,如:,不会受影响 dubbo.registry.check=false # 前面两个都是指订阅成功,但提供者列表是否为空是否报错,如果注册订阅失败时,也允许启动,需使用此选项,将在后台定时重试

  1. ### 1.3. 通过 -D 参数

java -Ddubbo.reference.com.foo.BarService.check=false java -Ddubbo.reference.check=false java -Ddubbo.consumer.check=false java -Ddubbo.registry.check=false

  1. ### 1.4. 配置的含义
  2. - `dubbo.reference.check=false`,强制改变所有 reference check 值,就算配置中有声明,也会被覆盖。
  3. - `dubbo.consumer.check=false`,是设置 check 的缺省值,如果配置中有显式的声明,如:`<dubbo:reference check="true"/>`,不会受影响。
  4. - `dubbo.registry.check=false`,前面两个都是指订阅成功,但提供者列表是否为空是否报错,如果注册订阅失败时,也允许启动,需使用此选项,将在后台定时重试。
  5. ## 2. Dubbo超时重连
  6. Dubbo 服务在尝试调用一次之后,如出现非业务异常(服务突然不可用、超时等),Dubbo 默认会进行额外的最多2次重试。重试次数支持两种自定义配置:
  7. 1. 通过注解或者xml配置进行固定配置
  8. 1. 通过上下文进行运行时动态配置
  9. ### 2.1. 重试次数配置
  10. Dubbo消费端在发出请求后,需要有一个临界时间界限来判断服务端是否正常。这样消费端达到超时时间,那么Dubbo会进行重试机制,不合理的重试在一些特殊的业务场景下可能会引发很多问题,需要合理设置接口超时时间。Dubbo超时和重试配置示例如下:
  11. - xml配置
  1. - 注解配置

@Reference(retries = 3) private XxxService xxxService;

  1. - 通过RpcContext进行运行时动态配置,优先级高于注解或者xml进行的固定配置(两者都配置的情况下,以RpcContext配置为准)

// dubbo服务调用前,通过RpcContext动态设置本次调用的重试次数 RpcContext rpcContext = RpcContext.getContext(); rpcContext.setAttachment(“retries”, 5);

  1. ### 2.2. 重试机制注意点
  2. - Dubbo在调用服务不成功时,_默认会重试2次,即`retries="2"`_。通过设置`<dubbo:reference>`标签中`retries="0"`属性控制重试次数
  3. - Dubbo的路由机制,会把超时的请求路由到其他机器上,而不是本机尝试,所以Dubbo的重试机制也能一定程度的保证服务的质量
  4. ## 3. 集群容错
  5. 当消费端某次调用失败是一些环境偶然因素造成的(如网络抖动),dubbo还给予了容错补救机会。在集群调用失败时,Dubbo 提供了多种容错方案,缺省为 `failover` 重试。容错方案关系图如下:<br />![](https://gitee.com/moonzero/images/raw/master/code-note/20200129162636712_10255.png)<br />各节点关系:
  6. - 这里的 `Invoker` `Provider` 的一个可调用 `Service` 的抽象,`Invoker` 封装了 `Provider` 地址及 `Service` 接口信息
  7. - `Directory` 代表多个`Invoker`,可以把它看成 `List<Invoker>` ,但与 `List` 不同的是,它的值可能是动态变化的,比如注册中心推送变更
  8. - `Cluster` `Directory` 中的多个 `Invoker` 伪装成一个 `Invoker`,对上层透明,伪装过程包含了容错逻辑,调用失败后,重试另一个
  9. - `Router` 负责从多个 `Invoker` 中按路由规则选出子集,比如读写分离,应用隔离等
  10. - `LoadBalance` 负责从多个 `Invoker` 中选出具体的一个用于本次调用,选的过程包含了负载均衡算法,调用失败后,需要重选
  11. ### 3.1. 集群容错模式
  12. #### 3.1.1. Failover Cluster
  13. 失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。可通过 `retries="2"` 来设置重试次数(不含第一次)。

  1. > **注:如果服务提供方与消费方都设置了重试次数,最终与消费方的重试次数为准**
  2. #### 3.1.2. Failfast Cluster
  3. 快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录
  4. #### 3.1.3. Failsafe Cluster
  5. 失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。
  6. #### 3.1.4. Failback Cluster
  7. 失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。
  8. #### 3.1.5. Forking Cluster
  9. 并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 `forks="2"` 来设置最大并行数。
  10. #### 3.1.6. Broadcast Cluster
  11. 广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息
  12. ### 3.2. 集群模式配置
  13. 按照以下示例在服务提供方和消费方配置集群模式

  1. ## 4. 负载均衡
  2. 在集群负载均衡时,Dubbo 提供了多种均衡策略,缺省为 `random` 随机调用
  3. ### 4.1. 负载均衡策略
  4. - Random LoadBalance
  5. - 随机,按权重设置随机概率。
  6. - 在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。
  7. - RoundRobin LoadBalance
  8. - 轮询,按公约后的权重设置轮询比率。
  9. - 存在慢的提供者累积请求的问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。
  10. - LeastActive LoadBalance
  11. - 最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。
  12. - 使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。
  13. - ConsistentHash LoadBalance
  14. - 一致性 Hash,相同参数的请求总是发到同一提供者。
  15. - 当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。
  16. - 缺省只对第一个参数 Hash,如果要修改,请配置 `<dubbo:parameter key="hash.arguments" value="0,1" />`
  17. - 缺省用 160 份虚拟节点,如果要修改,请配置 `<dubbo:parameter key="hash.nodes" value="320" />`
  18. > 注意:配置时负载均衡策略的单词都是**全小写**,如果出现大写会报错
  19. ### 4.2. 配置示例
  20. - 服务端服务级别
  1. - 客户端服务级别
  1. - 服务端方法级别

  1. - 客户端方法级别

  1. ## 5. 线程模型
  2. ### 5.1. 配置 Dubbo 中的线程模型
  3. 如果事件处理的逻辑能迅速完成,并且不会发起新的 IO 请求,比如只是在内存中记个标识,则直接在 IO 线程上处理更快,因为减少了线程池调度。<br />但如果事件处理逻辑较慢,或者需要发起新的 IO 请求,比如需要查询数据库,则必须派发到线程池,否则 IO 线程阻塞,将导致不能接收其它请求。<br />如果用 IO 线程处理事件,又在事件处理过程中发起新的 IO 请求,比如在连接事件中发起登录请求,会报“可能引发死锁”异常,但不会真死锁。<br />![](https://gitee.com/moonzero/images/raw/master/code-note/20210712214936913_20605.png)<br />需要通过不同的派发策略和不同的线程池配置的组合来应对不同的场景:
  1. ### 5.2. Dispatcher 属性
  2. - `all` 所有消息都派发到线程池,包括请求,响应,连接事件,断开事件,心跳等。
  3. - `direct` 所有消息都不派发到线程池,全部在 IO 线程上直接执行。
  4. - `message` 只有请求响应消息派发到线程池,其它连接断开事件,心跳等消息,直接在 IO 线程上执行。
  5. - `execution` 只有请求消息派发到线程池,不含响应,响应和其它连接断开事件,心跳等消息,直接在 IO 线程上执行。
  6. - `connection` IO 线程上,将连接断开事件放入队列,有序逐个执行,其它消息派发到线程池。
  7. ### 5.3. ThreadPool 属性
  8. - `fixed` 固定大小线程池,启动时建立线程,不关闭,一直持有。(缺省)
  9. - `cached` 缓存线程池,空闲一分钟自动删除,需要时重建。
  10. - `limited` 可伸缩线程池,但池中的线程数只会增长不会收缩。只增长不收缩的目的是为了避免收缩时突然来了大流量引起的性能问题。
  11. - `eager` 优先创建Worker线程池。在任务数量大于`corePoolSize`但是小于`maximumPoolSize`时,优先创建Worker来处理任务。当任务数量大于`maximumPoolSize`时,将任务放入阻塞队列中。阻塞队列充满时抛出`RejectedExecutionException`。(相比于`cached`:`cached`在任务数量超过`maximumPoolSize`时直接抛出异常而不是将任务放入阻塞队列)
  12. ## 6. 直连提供者
  13. 在开发及测试环境下,经常需要绕过注册中心,只测试指定服务提供者,这时候可能需要点对点直连,点对点直连方式,将以服务接口为单位,忽略注册中心的提供者列表,A 接口配置点对点,不影响 B 接口从注册中心获取列表。
  14. > **为了避免复杂化线上环境,不要在线上使用这个功能,只应在测试阶段使用。**
  15. ### 6.1. 通过 XML 配置
  16. 如果是线上需求需要点对点,可在 `<dubbo:reference>` 中配置 url 指向提供者,将绕过注册中心,多个地址用分号隔开,配置如下:
  1. ### 6.2. 通过 -D 参数指定
  2. JVM启动参数中加入`-D`参数映射服务地址,如:

java -Dcom.alibaba.xxx.XxxService=dubbo://localhost:20890

  1. > key 为服务名,value 为服务提供者 url,此配置优先级最高
  2. ### 6.3. 通过文件映射
  3. 如果服务比较多,也可以用文件映射,用 `-Ddubbo.resolve.file` 指定映射文件路径,此配置优先级高于 `<dubbo:reference>` 中的配置,如:

java -Ddubbo.resolve.file=xxx.properties

  1. 然后在映射文件 xxx.properties 中加入配置,其中 key 为服务名,value 为服务提供者 URL

com.alibaba.xxx.XxxService=dubbo://localhost:20890

  1. > 1.0.15 及以上版本支持,2.0 以上版本自动加载`${user.home}/dubbo-resolve.properties`文件,不需要配置
  2. ## 7. 只订阅
  3. ### 7.1. 只订阅不注册
  4. 场景:在本地开发的时候,不能把自己机器的未开发好的服务注册到开发环境,但是又需要使用注册中心的其他服务。服务提供者配置禁止注册`register="false"`<br />为方便开发测试,经常会在线下共用一个所有服务可用的注册中心,这时,如果一个正在开发中的服务提供者注册,可能会影响消费者不能正常运行。可以让服务提供者开发方,只订阅服务(开发的服务可能依赖其它服务),而不注册正在开发的服务,通过直连测试正在开发的服务。<br />![](https://gitee.com/moonzero/images/raw/master/code-note/20210714204839930_470.png)<br />禁用注册配置如下:

  1. ### 7.2. 只注册(已废弃?)
  2. 如果有两个镜像环境,两个注册中心,有一个服务只在其中一个注册中心有部署,另一个注册中心还没来得及部署,而两个注册中心的其它应用都需要依赖此服务。这个时候,可以让服务提供者方只注册服务到另一注册中心,而不从另一注册中心订阅服务<br />比如开发环境为了省机器,没有部署某个服务,如短信/邮件功能。但整个系统又必须要调用它。此时可以借用一下测试环境的此服务(不能影响测试环境原本的服务闭环)。将测试环境的短信/邮件服务也向开发环境注册一份,只注册(其依赖的服务必须还是测试环境的)。服务提供者配置禁止订阅`subscribe="false"`

  1. ## 8. 多协议
  2. Dubbo 允许配置多协议,在不同服务上支持不同协议或者同一服务上同时支持多种协议。
  3. ### 8.1. 不同服务不同协议
  4. 不同服务在性能上适用不同协议进行传输,比如大数据用短连接协议,小数据大并发用长连接协议

  1. ### 8.2. 多协议暴露服务
  2. 同一个服务,使用多个协议暴露

  1. ## 9. 多注册中心
  2. Dubbo 支持同一服务向多注册中心同时注册,或者不同服务分别注册到不同的注册中心上去,甚至可以同时引用注册在不同注册中心上的同名服务。另外,注册中心是支持自定义扩展的。
  3. ### 9.1. 多注册中心注册
  4. 比如:中文站有些服务来不及在青岛部署,只在杭州部署,而青岛的其它应用需要引用此服务,就可以将服务同时注册到两个注册中心。

dubbo:application name=”world” />

  1. ### 9.2. 不同服务使用不同注册中心
  2. 比如:CRM 有些服务是专门为国际站设计的,有些服务是专门为中文站设计的。

  1. ### 9.3. 多注册中心引用
  2. 比如:CRM 需同时调用中文站和国际站的 PC2 服务,PC2 在中文站和国际站均有部署,接口及版本号都一样,但连的数据库不一样。

  1. 如果只是测试环境临时需要连接两个不同注册中心,使用竖号分隔多个不同注册中心地址:

  1. ## 10. 服务分组
  2. **使用服务分组区分服务接口的不同实现**<br />如果想在测试、开发环境等多套环境中共用同一个注册中心。或者当一个接口有多种实现时,可以用 group 区分
  3. - 服务提供方

  1. - 引用消费方

  1. - 任意组:2.2.0 以上版本支持,总是只调一个可用组的实现
  1. ## 11. 分组聚合
  2. dubbo提供了通过分组对结果进行聚合并返回聚合后的结果的功能。用`group`区分同一接口的多种实现,现在消费方需从每种`group`中调用一次并返回结果,对结果进行合并之后返回<br />dubbo提供了以下类型的合并的实现,是根据服务接口的返回值的类型去找相应的实现
  3. > **值得注意:这个服务返回值合并只是一次PRC调用,如果其中某个服务执行失败,则调用结果失败。**
  4. ![](https://gitee.com/moonzero/images/raw/master/code-note/20210717095903124_3599.png)
  5. ### 11.1. 配置示例
  6. - 搜索所有分组
  1. - 合并指定分组
  1. - 指定方法合并结果,其它未指定的方法,将只调用一个 Group

  1. - 某个方法不合并结果,其它都合并结果

  1. - 指定合并策略,缺省根据返回值类型自动匹配,如果同一类型有两个合并器时,需指定合并器的名称

  1. - 指定合并方法,将调用返回结果的指定方法进行合并,合并方法的参数类型必须是返回结果类型本身

  1. ### 11.2. 聚合实现示例
  2. 官方没有提供`String`类型的实现,如合并字符串类型,需要自己实现。具体的实现步骤如下:
  3. - 根据dubboSPI机制(参考`com.alibaba.dubbo.rpc.cluster.Merger`文件),在创建与官方一样的文件`com.alibaba.dubbo.rpc.cluster.Merger`,定义字符串合并与实现类的映射
  4. ![](https://gitee.com/moonzero/images/raw/master/code-note/20210717101939101_13028.png)
  5. - 编写合并逻辑的实现类,需要实现dubbo`Merger<T>`接口。

public class StringMerger implements Merger {

  1. /**
  2. * 定义了所有group实现类返回值的合并规则。
  3. * 注:此示例简单实现
  4. *
  5. * @param items
  6. * @return
  7. */
  8. @Override
  9. public String merge(String... items) {
  10. if (items.length == 0) {
  11. return null;
  12. }
  13. StringJoiner joiner = new StringJoiner("|", "[", "]");
  14. for (String s : items) {
  15. joiner.add(s);
  16. }
  17. return joiner.toString();
  18. }

}

  1. - 服务引用配置`group`属性,通过 parameters 属性修改为 `{"merger", "true"}`,开启返回结果合并

@RestController @RequestMapping(“group”) public class GroupController {

  1. /*
  2. * group属性:指定相应的服务接口的实现
  3. * 如果取值为"*",则代表任意组,是随机调用不同的实现
  4. * 如果需要将服务分组的返回结果进行合并,只需修改 parameters 属性,{"merger", "true"}
  5. */
  6. // @Reference(group = "groupB")
  7. @Reference(group = "*", parameters = {"merger", "true"})
  8. private GroupService groupService;
  9. @GetMapping
  10. public List<String> testGroup() {
  11. return groupService.queryGroupData();
  12. }
  13. @GetMapping("mergerString")
  14. public String testMergerString() {
  15. return groupService.getGroupMessage();
  16. }

}

  1. 测试结果<br />![](https://gitee.com/moonzero/images/raw/master/code-note/20210717103446097_9776.png)
  2. ## 12. 静态服务
  3. **将 Dubbo 服务标识为非动态管理模式**。如希望人工管理服务提供者的上线和下线,此时需将注册中心标识为非动态管理模式。

  1. 服务提供者初次注册时为禁用状态,需人工启用。断线时,将不会被自动删除,需人工禁用。(_通过代码调用接口的方式去操作`Registry`注册服务_)<br />如果是一个第三方服务提供者,比如 memcached,可以直接向注册中心写入提供者地址信息,消费者正常使用:

RegistryFactory registryFactory = ExtensionLoader.getExtensionLoader(RegistryFactory.class).getAdaptiveExtension(); Registry registry = registryFactory.getRegistry(URL.valueOf(“zookeeper://10.20.153.10:2181”)); registry.register(URL.valueOf(“memcached://10.20.153.11/com.foo.BarService?category=providers&dynamic=false&application=foo”));

  1. ## 13. 多版本
  2. - 当一个接口实现,出现不兼容升级时,可以用版本号过渡,版本号不同的服务相互间不引用
  3. - 服务端提供接口的实现升级时,可由dubbo的版本号操作进行过渡。如果上线上测试新版本接口有缺陷,为了不影响业务,要迅速切回原版本接口,最大程度减少损失。
  4. 版本迁移步骤:
  5. 1. 在低压力时间段,先升级一半提供者为新版本
  6. 1. 再将所有消费者升级为新版本
  7. 1. 然后将剩下的一半提供者升级为新版本
  8. - 提供者配置

  1. - 服务消费者配置

  1. ## 14. 参数校验
  2. dubbo的参数验证功能是基于 JSR303 实现的,用户只需标识 JSR303 标准的验证 annotation,并通过声明 filter 来实现验证。
  3. ### 14.1. Maven 依赖
javax.validation validation-api 2.0.1.Final org.hibernate hibernate-validator 7.0.0.Final
  1. ### 14.2. 参数校验示例
  2. ## 15. 结果缓存
  3. 结果缓存(_2.1.0 以上版本支持_),用于加速热门数据的访问速度,Dubbo 提供声明式缓存,以减少用户加缓存的工作量
  4. ### 15.1. 缓存类型
  5. - `lru`:基于最近最少使用原则删除多余缓存,保持最热的数据被缓存。
  6. - `threadlocal`:当前线程缓存,比如一个页面渲染,用到很多 portal,每个 portal 都要去查用户信息,通过线程缓存,可以减少这种多余访问。
  7. - `jcache`:与 JSR107 集成,可以桥接各种缓存实现
  8. ### 15.2. 配置示例

  1. > **服务方配置方法与消费端完全一样**
  2. ## 16. 泛化调用
  3. ### 16.1. 使用泛化调用
  4. 实现一个通用的服务测试框架,可通过 `GenericService` 调用所有服务实现。<br />泛化接口调用方式主要用于客户端没有 API 接口及模型类元的情况,参数及返回值中的所有 POJO 均用 `Map` 表示,通常用于框架集成,比如:实现一个通用的服务测试框架,可通过 `GenericService` 调用所有服务实现。
  5. > 注:此功能一般只是用于开发/测试阶段。
  6. #### 16.1.1. 通过 Spring 使用泛化调用
  7. Spring 配置申明 `generic="true"`
  1. Java 代码获取 barService 并开始泛化调用:

GenericService barService = (GenericService) applicationContext.getBean(“barService”); Object result = barService.$invoke(“sayHello”, new String[] { “java.lang.String” }, new Object[] { “World” });

  1. #### 16.1.2. 通过 API 方式使用泛化调用

import org.apache.dubbo.rpc.service.GenericService; …

// 引用远程服务 // 该实例很重量,里面封装了所有与注册中心及服务提供方连接,请缓存 ReferenceConfig reference = new ReferenceConfig(); // 弱类型接口名 reference.setInterface(“com.xxx.XxxService”); reference.setVersion(“1.0.0”); // 声明为泛化接口 reference.setGeneric(true);

// 用org.apache.dubbo.rpc.service.GenericService可以替代所有接口引用 GenericService genericService = reference.get();

// 基本类型以及Date,List,Map等不需要转换,直接调用 Object result = genericService.$invoke(“sayHello”, new String[] {“java.lang.String”}, new Object[] {“world”});

// 用Map表示POJO参数,如果返回值为POJO也将自动转成Map Map person = new HashMap(); person.put(“name”, “xxx”); person.put(“password”, “yyy”); // 如果返回POJO将自动转成Map Object result = genericService.$invoke(“findPerson”, new String[]{“com.xxx.Person”}, new Object[]{person}); …

  1. POJO

@Data public class PersonImpl implements Person { private String name; private String password; }

  1. POJO 请求数据:

Person person = new PersonImpl(); person.setName(“xxx”); person.setPassword(“yyy”);

  1. 请求时可用下面 Map 表示:

Map map = new HashMap(); // 注意:如果参数类型是接口,或者List等丢失泛型,可通过class属性指定类型。 map.put(“class”, “com.xxx.PersonImpl”); map.put(“name”, “xxx”); map.put(“password”, “yyy”);

  1. ### 16.2. 实现泛化调用示例
  2. 通过实现 `GenericService` 接口处理所有服务请求<br />泛接口实现方式主要用于服务器端没有 API 接口及模型类元的情况,参数及返回值中的所有 POJO 均用 Map 表示,通常用于框架集成,比如:实现一个通用的远程服务 Mock 框架,可通过实现 `GenericService` 接口处理所有服务请求。<br />以下是一个实现调用其他业务层方法的简单思路。

@Service public class MyGenericService implements GenericService, ApplicationContextAware {

  1. private ApplicationContext context;
  2. /**
  3. * Generic invocation
  4. *
  5. * @param method Method name, e.g. findPerson. If there are overridden methods, parameter info is
  6. * required, e.g. findPerson(java.lang.String)
  7. * @param parameterTypes Parameter types
  8. * @param args Arguments
  9. * @return invocation return value
  10. * @throws Throwable potential exception thrown from the invocation
  11. */
  12. @Override
  13. public Object $invoke(String method, String[] parameterTypes, Object[] args) throws GenericException {
  14. StringJoiner joiner = new StringJoiner("; ", "[ ", " ]");
  15. joiner.add("method name is " + method);
  16. if (parameterTypes.length > 0) {
  17. for (int i = 0; i < parameterTypes.length; i++) {
  18. joiner.add("parameterType[" + i + "] is " + parameterTypes[i]);
  19. }
  20. }
  21. if (args.length > 0) {
  22. for (int i = 0; i < args.length; i++) {
  23. joiner.add("args[" + i + "] is " + args[i]);
  24. }
  25. }
  26. String result = joiner.toString();
  27. System.out.println("泛化调用 MyGenericService 实现==> " + result);
  28. /*
  29. * 这里做简单的判断,直接调用spring容器中的实例方法。
  30. * 注:这是写死的调用,实际项目中的应用是通过反射或者从spring容器去调用相应的方法
  31. */
  32. if ("getResult".equals(method)) {
  33. GenericCallService genericCallService = context.getBean(GenericCallService.class);
  34. result = genericCallService.getResult((String) args[0]);
  35. }
  36. return result;
  37. }
  38. @Override
  39. public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
  40. this.context = applicationContext;
  41. }

}

  1. #### 16.2.1. 通过 Spring 暴露泛化实现
  2. Spring 配置申明服务的实现:

  1. #### 16.2.2. 通过 API 方式暴露泛化实现

… // 用org.apache.dubbo.rpc.service.GenericService可以替代所有接口实现 GenericService xxxService = new XxxGenericService();

// 该实例很重量,里面封装了所有与注册中心及服务提供方连接,请缓存 ServiceConfig service = new ServiceConfig(); // 弱类型接口名 service.setInterface(“com.xxx.XxxService”); service.setVersion(“1.0.0”); // 指向一个通用服务实现 service.setRef(xxxService);

// 暴露及注册服务 service.export();

  1. ## 17. 回声测试
  2. 回声测试用于检测服务是否可用,回声测试按照正常请求流程执行,能够测试整个调用是否通畅,可用于监控。<br />所有服务自动实现 `EchoService` 接口,只需将任意服务引用强制转型为 `EchoService`,即可使用。<br />Spring 配置:
  1. 代码:

// 远程服务引用 MemberService memberService = ctx.getBean(“memberService”); EchoService echoService = (EchoService) memberService; // 强制转型为EchoService // 回声测试可用性 String status = echoService.$echo(“OK”); assert(status.equals(“OK”));

  1. ## 18. 上下文信息
  2. 上下文中存放的是当前调用过程中所需的环境信息。所有配置信息都将转换为 URL 的参数。<br />`RpcContext` 是一个 `ThreadLocal` 的临时状态记录器,当接收到 RPC 请求,或发起 RPC 请求时,`RpcContext` 的状态都会变化。比如:A BB 再调 C,则 B 机器上,在 B C 之前,`RpcContext` 记录的是 A B 的信息,在 B C 之后,`RpcContext` 记录的是 B C 的信息。
  3. > **注:每一次RPC调用的上下文信息对象都不一样。**
  4. ### 18.1. 服务消费方

// 远程调用 xxxService.xxx(); // 本端是否为消费端,这里会返回true boolean isConsumerSide = RpcContext.getContext().isConsumerSide(); // 获取最后一次调用的提供方IP地址 String serverIP = RpcContext.getContext().getRemoteHost(); // 获取当前服务配置信息,所有配置信息都将转换为URL的参数 String application = RpcContext.getContext().getUrl().getParameter(“application”); // 注意:每发起RPC调用,上下文状态会变化 yyyService.yyy();

  1. ### 18.2. 服务提供方

public class XxxServiceImpl implements XxxService { public void xxx() { // 本端是否为提供端,这里会返回true boolean isProviderSide = RpcContext.getContext().isProviderSide(); // 获取调用方IP地址 String clientIP = RpcContext.getContext().getRemoteHost(); // 获取当前服务配置信息,所有配置信息都将转换为URL的参数 String application = RpcContext.getContext().getUrl().getParameter(“application”); // 注意:每发起RPC调用,上下文状态会变化 yyyService.yyy(); // 此时本端变成消费端,这里会返回false boolean isProviderSide = RpcContext.getContext().isProviderSide(); } }

  1. ## 19. 上下文信息 - 隐式参数
  2. Dubbo 中,可以通过 `RpcContext` 上的 `setAttachment` `getAttachment` 来设置在服务消费方和提供方之间隐式传递参数。
  3. > 注意:path, group, version, dubbo, token, timeout 几个 key 是保留字段,请使用其它值。
  4. ![](https://gitee.com/moonzero/images/raw/master/code-note/20210721222611679_8883.png)
  5. ### 19.1. 在服务消费方端设置隐式参数
  6. `setAttachment` 设置的 KV 对,在完成下面一次远程调用会被清空,即多次远程调用要多次设置。

RpcContext.getContext().setAttachment(“index”, “1”); // 隐式传参,后面的远程调用都会隐式将这些参数发送到服务器端,类似cookie,用于框架集成,不建议常规业务使用 xxxService.xxx(); // 远程调用 // …

  1. ### 19.2. 在服务提供方端获取隐式参数

public class XxxServiceImpl implements XxxService { public void xxx() { // 获取客户端隐式传入的参数,用于框架集成,不建议常规业务使用 String index = RpcContext.getContext().getAttachment(“index”); } }

  1. ## 20. 异步
  2. > 注意:
  3. > Provider 端异步执行和 Consumer 端异步调用是相互独立的,你可以任意正交组合两端配置
  4. > - Consumer同步 - Provider同步
  5. > - Consumer异步 - Provider同步
  6. > - Consumer同步 - Provider异步
  7. > - Consumer异步 - Provider异步
  8. ### 20.1. 异步执行
  9. Dubbo 服务提供方的异步执行。Provider端异步执行将阻塞的业务从Dubbo内部线程池切换到业务自定义线程,避免Dubbo线程池的过度占用,有助于避免不同服务间的互相影响。**异步执行无益于节省资源或提升RPC响应性能(只是提高了应用的吞量)**,因为如果业务执行需要阻塞,则始终还是要有线程来负责执行。
  10. #### 20.1.1. 定义 CompletableFuture 签名的接口
  11. 服务接口定义

public interface AsyncService { CompletableFuture doAsync(String name); }

  1. 服务实现

@Service public class AsyncServiceImpl implements AsyncService { /**

  1. * 定义 CompletableFuture 签名的接口.
  2. * 通过 return CompletableFuture.supplyAsync()
  3. * 业务执行已从 Dubbo 线程切换到业务线程,避免了对 Dubbo 线程池的阻塞。
  4. *
  5. * @param name
  6. * @return
  7. */
  8. @Override
  9. public CompletableFuture<String> doAsync(String name) {
  10. System.out.println("[annotation provider] AsyncService 接口实现 doAsync 方法执行...");
  11. RpcContext savedContext = RpcContext.getContext();
  12. // 建议为supplyAsync提供自定义线程池,避免使用JDK公用线程池。
  13. // 业务执行已从 Dubbo 线程切换到业务线程,避免了对 Dubbo 线程池的阻塞。
  14. return CompletableFuture.supplyAsync(() -> {
  15. System.out.println("receive form consumer: " + savedContext.getAttachment("consumer-key"));
  16. try {
  17. Thread.sleep(30000); // 休眠,模拟处理复杂业务
  18. } catch (InterruptedException e) {
  19. e.printStackTrace();
  20. }
  21. return "async response from annotation provider doAsync: " + name;
  22. });
  23. }

}

  1. 注:通过 `return CompletableFuture.supplyAsync()`,业务执行已从 Dubbo 线程切换到业务线程,避免了对 Dubbo 线程池的阻塞。
  2. #### 20.1.2. 使用AsyncContext(2.7.0版本后)
  3. Dubbo 2.7.0版本后提供了一个类似 Serverlet 3.0 的异步接口`AsyncContext`,在没有 `CompletableFuture` 签名接口的情况下,也可以实现 Provider 端的异步执行。<br />服务接口定义:

public interface AsyncService { String doAsyncOther(String name); }

  1. 服务暴露,和普通服务完全一致:

  1. 服务实现:

@Service public class AsyncServiceImpl implements AsyncService { /**

  1. * 2.7.0版本 使用AsyncContext
  2. * Dubbo 提供了一个类似 Serverlet 3.0 的异步接口AsyncContext
  3. * 在没有 CompletableFuture 签名接口的情况下,也可以实现 Provider 端的异步执行。
  4. *
  5. * @param name
  6. * @return
  7. */
  8. @Override
  9. public String doAsyncOther(String name) {
  10. System.out.println("[annotation provider] AsyncService 接口实现 doAsyncOther 方法执行...");
  11. // 以下方式是 dubbo 2.7.0 版本后的全异步编程
  12. final AsyncContext asyncContext = RpcContext.startAsync();
  13. new Thread(() -> {
  14. // 如果要使用上下文,则必须要放在第一句执行
  15. asyncContext.signalContextSwitch();
  16. try {
  17. Thread.sleep(15000); // 休眠,模拟处理复杂业务
  18. } catch (InterruptedException e) {
  19. e.printStackTrace();
  20. }
  21. // 写回响应
  22. asyncContext.write("Hello " + name + ", async response from annotation provider.");
  23. }).start();
  24. return "async response from annotation provider doAsyncOther: " + name;
  25. }

}

  1. ### 20.2. 异步调用(Consumer端调用)
  2. > v2.7.0开始,Dubbo的所有异步编程接口开始以CompletableFuture为基础
  3. Dubbo的异步调用是非阻塞的NIO调用,一个线程可同时并发调用多个远程服务,每个服务的调用都是非阻塞的,线程立即返回。就是对javaFutrue模式的扩展支持。异步调用流程图如下:<br />![](https://gitee.com/moonzero/images/raw/master/code-note/20200129192643662_18150.png)<br />如上图,userThread发出调用后,IOThread会立即返回,并在RPC上下文RpcContext中设置Future。userThread后续可以从RpcContext中取得此Future,然后wait这个Future其它的事情都由IOThread完成<br />总之,userThread发出调用后IOThread会立刻返回,而不是等调用在服务端执行完代码、返回结果后返回。用户返回后可以去做点其它事情,比如调用另外一个服务,然后回头等待前一个调用完成。从上图可以看出,异步调用完全是Consumer端的行为
  4. > 详细案例参考busi-mall工程中的dubbo.xmlIndexController
  5. #### 20.2.1. 在消费端配置

  1. - 也可以设置是否等待消息发出,即是否等待IOThread发送完Request后再返回
  2. - `sent="true"` 等待消息发出去再返回,如果消息发送失败将抛出异常。
  3. - `sent="false"` 不等待消息发出,将消息放入 IO 队列,即刻返回。
  1. - 如果你只是想异步,完全忽略返回值,可以配置 `return="false"`,以减少 Future 对象的创建和管理成本:
  1. #### 20.2.2. 调用代码

// 此调用会立即返回null asyncService.sayHello(“world”); // 拿到调用的Future引用,当结果返回后,会被通知和设置到此Future CompletableFuture helloFuture = RpcContext.getContext().getCompletableFuture(); // 为Future添加回调 helloFuture.whenComplete((retValue, exception) -> { if (exception == null) { System.out.println(retValue); } else { exception.printStackTrace(); } });

  1. 或者

CompletableFuture future = RpcContext.getContext().asyncCall( () -> { asyncService.sayHello(“oneway call request1”); } );

future.get();

  1. > 注意:如果xml配置文件中没有对消费标签配置`async="true"`属性,则以上示例代码不生效,还是同步调用。获取到的Future对象为null
  2. ## 21. 本地调用
  3. Dubbo 中进行本地调用。本地调用使用了 injvm 协议,是一个伪协议,它不开启端口,不发起远程调用,只在 JVM 内直接关联,但执行 Dubbo Filter 链。
  4. ### 21.1. 配置
  5. 定义 injvm 协议
  1. 设置默认协议
  1. 设置服务协议
  1. 优先使用 injvm

  1. > 注:Dubbo 2.2.0 每个服务默认都会在本地暴露,无需进行任何配置即可进行本地引用,如果不希望服务进行远程暴露,只需要在 provider protocol 设置成 injvm 即可
  2. ### 21.2. 自动暴露、引用本地服务
  3. 2.2.0 开始,每个服务默认都会在本地暴露。在引用服务的时候,默认优先引用本地服务。如果希望引用远程服务可以使用一下配置强制引用远程服务。
  1. ## 22. 参数回调
  2. 通过参数回调从服务器端调用客户端逻辑。参数回调方式与调用本地 `callback` `listener` 相同,只需要在 Spring 的配置文件中声明哪个参数是 `callback` 类型即可。Dubbo 将基于长连接生成反向代理,这样就可以从服务器端调用客户端逻辑。
  3. ### 22.1. 服务接口示例
  4. - 定义回调的接口

public interface CallbackParameterService { void addListener(String key, CallbackListener listener); String doSomething(String param); }

  1. ```
  2. public interface CallbackListener {
  3. void changed(String msg);
  4. }
  • 服务提供示例 ``` /*

    • @Method 指定方法的名称
    • @Argument 指定回调参数的信息。
    • index属性:设置回调的参数位置
    • callback属性:是否为回调的方法,设置true则定义回调方法 */ @Service(methods = {@Method(name = “addListener”, arguments = {@Argument(index = 1, callback = true)})}) public class CallbackParameterServiceImpl implements CallbackParameterService { @Override public void addListener(String key, CallbackListener listener) { // CallbackListener 是消费端去实现的回调方法 listener.changed(doSomething(key)); }

      / 与回调无关的其他方法 / @Override public String doSomething(String param) { return param + new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”).format(new Date()); } }

  1. ### 22.2. 服务消费者
  2. - 配置示例
  1. - 调用示例

@Reference(check = false) private CallbackParameterService callbackParameterService;

@GetMapping public String testCallbackParameter() { callbackParameterService.addListener(“MooN”, new CallbackListener() { // 此方法会在服务端回调 @Override public void changed(String msg) { System.out.println(“consumer callbackParameter: “ + msg); } }); return “success!”; }

  1. ## 23. 事件通知(未整理)
  2. 在调用之前、调用之后、出现异常时,会触发 `oninvoke``onreturn``onthrow` 三个事件,可以配置当事件发生时,通知哪个类的哪个方法。在Consumer端,可以为三个事件指定事件处理方法
  3. ### 23.1. 服务消费者 Callback 接口

interface Notify { public void onreturn(Person msg, Integer id); public void onthrow(Throwable ex, Integer id); }

  1. ### 23.2. 服务消费者 Callback 实现

class NotifyImpl implements Notify { public Map ret = new HashMap(); public Map errors = new HashMap();

  1. /**
  2. * 成功调用后回调方法
  3. *
  4. * @param msg 返回结果值
  5. * @param ig 原方法调用的入参
  6. */
  7. public void onreturn(Person msg, Integer id) {
  8. System.out.println("onreturn:" + msg);
  9. ret.put(id, msg);
  10. }
  11. /**
  12. * 调用失败后回调方法
  13. *
  14. * @param ex 出现异常返回结果值
  15. * @param id 原方法调用的入参
  16. */
  17. public void onthrow(Throwable ex, Integer id) {
  18. errors.put(id, ex);
  19. }

}

  1. ### 23.3. 服务消费者 Callback 配置

  1. ### 23.4. 配置几种组合情况
  2. - callback async 功能正交分解,async=true 表示结果是否马上返回,onreturn 表示是否需要回调。两者叠加存在以下几种组合情况
  3. - 异步回调模式:`async=true onreturn="xxx"`
  4. - 同步回调模式:`async=false onreturn="xxx"`
  5. - 异步无回调:`async=true`
  6. - 同步无回调:`async=false`
  7. ## 24. 本地存根
  8. Dubbo 中利用本地存根在客户端执行部分逻辑<br />远程服务后,客户端通常只剩下接口,而实现全在服务器端,但提供方有些时候想在客户端也执行部分逻辑,比如:做 ThreadLocal 缓存,提前验证参数,调用失败后伪造容错数据等等,此时就需要在 API 中带上 Stub,客户端生成 Proxy 实例,会把 Proxy 通过构造函数传给 Stub,然后把 Stub 暴露给用户,Stub 可以决定要不要去调 Proxy。<br />![](https://gitee.com/moonzero/images/raw/master/code-note/20210727223739272_31076.png)
  9. ### 24.1. 服务端示例
  10. spring 配置文件中按以下方式配置:
  1. 使用注解方式的示例,使用`@Service`注解暴露服务

// Stub接口 public interface LocalStubService { String execute(String params); }

// 服务端Stub接口实现 @Service public class LocalStubServiceImpl implements LocalStubService { @Override public String execute(String params) { System.out.println(“[annotation provider] LocalStubService 接口实现 execute 方法执行…”); return “Annotation provider LocalStubServiceImpl execute: “ + params; } }

  1. ### 24.2. 客户端示例
  2. 在客户端创建与服务提供者同一个接口的实现类,此类会接管服务提供者的接口调用。通过此代理可以实现要不要远程调用服务提供方

public class LocalStubProxy implements LocalStubService {

  1. private final LocalStubService localStubService;
  2. // 注意,此构造函数必须定义,用于传入真正的远程代理对象
  3. public LocalStubProxy(LocalStubService localStubService) {
  4. this.localStubService = localStubService;
  5. }
  6. @Override
  7. public String execute(String params) {
  8. System.out.println("[annotation consumer] LocalStubService 接口实现 execute 方法执行...");
  9. // 此代码在客户端执行, 你可以在客户端做ThreadLocal本地缓存,或预先验证参数是否合法,等等
  10. try {
  11. if ("local".equalsIgnoreCase(params)) {
  12. // 模拟业务,不调用远程接口
  13. return "Annotation consumer LocalStubProxy execute: " + params;
  14. } else if ("remote".equalsIgnoreCase(params)) {
  15. // 模拟业务,调用远程服务
  16. return localStubService.execute(params);
  17. }
  18. } catch (Exception e) {
  19. // 可以容错,可以做任何AOP拦截事项
  20. e.printStackTrace();
  21. return "服务器出错了";
  22. }
  23. return null;
  24. }

}

  1. > 注:
  2. > 1. Stub (即上示例`LocalStubProxy`类)必须有可传入 Proxy 的构造函数。
  3. > 1. interface 本地 Stub 实现,它与服务端实现同一个接口,并有一个传入远程接口实现实例的构造函数
  4. > 1. 如果引入服务接口时配置属性`stub = "true"`,则需要本地的实现类与接口在同一个包路径下,并且类名必须是`接口名+Stub`结尾,如`LocalStubServiceStub`
  5. 使用`@Reference`注解方式引入测试接口,其中`stub`属性用于指定测试接口的本地实现类。

@Reference(check = false, stub = “com.moon.dubbo.annotation.stub.LocalStubProxy”) private LocalStubService localStubService;

@GetMapping public String testLocalStub(@RequestParam(“params”) String params) { return localStubService.execute(params); }

  1. ## 25. 本地伪装
  2. 本地伪装通常用于服务降级,比如某验权服务,当服务提供方全部挂掉后,客户端不抛出异常,而是通过 Mock 数据返回授权失败。
  3. ### 25.1. 配置
  4. spring 配置文件中按以下方式配置:
  1. > 注:
  2. > 1. `Mock` `Stub` 的一个子集,便于服务提供方在客户端执行容错逻辑,因经常需要在出现 `RpcException` (比如网络失败,超时等)时进行容错,而在出现业务异常(比如登录用户名密码错误)时不需要容错,如果用 `Stub`,可能就需要捕获并依赖 `RpcException` 类,而用 `Mock` 就可以不依赖 `RpcException`,因为它的约定就是只有出现 `RpcException` 时才执行。
  3. > 1. interface 本地 Mock 实现,它与服务端实现同一个接口,并提供一个无参构造函数
  4. > 1. 如果引入服务接口时配置属性`mock = "true"`,则需要本地的实现类与接口在同一个包路径下,并且类名必须是`接口名+Mock`,如`LocalMockServiceMock`
  5. ### 25.2. 基础示例
  6. - 编写一个测试接口
  7. - 在服务提供者工程中,编写一个测试接口的实现,并在相应的方法中模拟出现异常(如通过`Thread.sleep`模拟超时)
  8. - 在服务消费者工程中,提供 Mock 实现

/**

  • 消费者,本地伪装降级类。
  • 这个是配置 mock=”true”时默认加载的降级类,其名称必须是 “接口名 + Mock”。
  • 这个类中方法调用的前提是出现了远程调用异常(RpcException),此时才会调用此类其中的方法 */ public class LocalMockServiceMock implements LocalMockService { @Override public String mock(String params) {
    1. System.out.println("[annotation consumer] LocalMockService 接口实现 mock 方法执行...");
    2. // 返回伪造容错数据,此方法只在远程调用出现 RpcException 时被执行
    3. return "[annotation consumer] LocalMockServiceMock: " + params;
    } }
  1. - 在消费者工程

/*

  • mock属性:用于配置本地伪装
  • mock = “true”,默认加载的降级类,其名称必须是 “接口名 + Mock”,并且与接口在同一个包下
  • mock = “类全限定名”,指定降级类,此类可以在任意包下,名称也没有固定限制 */ // @Reference(check = false, mock = “true”) @Reference(check = false, mock = “com.moon.dubbo.service.LocalMockServiceMock”) private LocalMockService localMockService;

@GetMapping public String testLocalMock(@RequestParam(“params”) String params) { return localMockService.mock(params); }

  1. ### 25.3. 进阶用法
  2. #### 25.3.1. return
  3. 使用 `return` 来返回一个字符串表示的对象,作为 Mock 的返回值。合法的字符串可以是:
  4. - empty: 代表空,基本类型的默认值,或者集合类的空值
  5. - null: `null`
  6. - true: `true`
  7. - false: `false`
  8. - JSON 格式: 反序列化 JSON 所得到的对象

@Reference(check = false, mock = “return null”) @Reference(check = false, mock = “return true”) @Reference(check = false, mock = “return MooN”)

  1. #### 25.3.2. throw
  2. 使用 `throw` 来返回一个 `Exception` 对象,作为 Mock 的返回值。当调用出错时,抛出一个默认的 `RPCException`:
  1. 当调用出错时,抛出指定的 `Exception`
  1. #### 25.3.3. force 和 fail
  2. 2.6.6 以上的版本,可以开始在 Spring XML 配置文件中使用 `fail:` `force:`
  3. - `force:` 代表强制使用 Mock 行为,在这种情况下不会走远程调用。
  4. - `fail:` 与默认行为一致,只有当远程调用发生错误时才使用 Mock 行为。
  5. 注:`force:` `fail:` 都支持与 `throw` 或者 `return` 组合使用。<br />强制返回指定值:
  1. 强制抛出指定异常:
  1. #### 25.3.4. 在方法级别配置 Mock
  2. Mock 可以在方法级别上指定,假定 `com.foo.BarService` 上有好几个方法,可以单独为 `sayHello()` 方法指定 Mock 行为。具体配置如下所示,在本例中,只要 `sayHello()` 被调用到时,强制返回 fake”:

  1. ## 26. 延迟暴露
  2. 如果服务需要预热时间,比如初始化缓存,等待相关资源就位等,可以使用 `delay` 进行延迟暴露。在 Dubbo 2.6.5 版本中对服务延迟暴露逻辑进行了细微的调整,将需要延迟暴露(`delay > 0`)服务的倒计时动作推迟到了 Spring 初始化完成后进行。在使用 Dubbo 的过程中,并不会感知到此变化。
  3. ### 26.1. Dubbo 2.6.5 之前版本
  4. 延迟到 Spring 初始化完成后,再暴露服务(基于 Spring `ContextRefreshedEvent` 事件触发暴露
  1. 延迟 5 秒暴露服务
  1. ### 26.2. Dubbo 2.6.5 及以后版本
  2. 所有服务都将在 Spring 初始化完成后进行暴露,如果不需要延迟暴露服务,无需配置 `delay`。如:延迟 5 秒暴露服务
  1. ### 26.3. Spring 2.x 初始化死锁问题
  2. **触发条件**:在 Spring 解析到 `<dubbo:service />` 时,就已经向外暴露了服务,而 Spring 还在接着初始化其它 Bean。如果这时有请求进来,并且服务的实现类里有调用 `applicationContext.getBean()` 的用法。
  3. 1. 请求线程的 `applicationContext.getBean()` 调用,先同步 `singletonObjects` 判断 Bean 是否存在,不存在就同步 `beanDefinitionMap` 进行初始化,并再次同步 `singletonObjects` 写入 Bean 实例缓存。
  4. ![](https://gitee.com/moonzero/images/raw/master/code-note/20210801170323412_19834.jpg)
  5. 2. Spring 初始化线程,因不需要判断 Bean 的存在,直接同步 `beanDefinitionMap` 进行初始化,并同步 `singletonObjects` 写入 Bean 实例缓存。
  6. ![](https://gitee.com/moonzero/images/raw/master/code-note/20210801170338894_11770.jpg)<br />这样就导致 `getBean` 线程,先锁 `singletonObjects`,再锁 `beanDefinitionMap`,再次锁 `singletonObjects`。而 Spring 初始化线程,先锁 `beanDefinitionMap`,再锁 `singletonObjects`。反向锁导致线程死锁,不能提供服务,启动不了。<br />**规避办法**:
  7. 1. 强烈建议不要在服务的实现类中有 `applicationContext.getBean()` 的调用,全部采用 IoC 注入的方式使用 SpringBean
  8. 1. 如果实在要调 `getBean()`,可以将 Dubbo 的配置放在 Spring 的最后加载。
  9. 1. 如果不想依赖配置顺序,可以使用 `<dubbo:provider delay=”-1” />`,使 Dubbo Spring 容器初始化完后,再暴露服务。
  10. 1. 如果大量使用 `getBean()`,相当于已经把 Spring 退化为工厂模式在用,可以将 Dubbo 的服务隔离单独的 Spring 容器。
  11. ## 27. 并发控制
  12. ### 27.1. 配置示例
  13. - 限制 `com.foo.BarService` 的每个方法,服务器端并发执行(或占用线程池线程数)不能超过 10 个:
  1. - 限制 `com.foo.BarService` `sayHello` 方法,服务器端并发执行(或占用线程池线程数)不能超过 10 个:

  1. - 限制 `com.foo.BarService` 的每个方法,每客户端并发执行(或占用连接的请求数)不能超过 10 个:

  1. - 限制 `com.foo.BarService` `sayHello` 方法,每客户端并发执行(或占用连接的请求数)不能超过 10 个:

  1. > 注:如果 `<dubbo:service>` `<dubbo:reference>` 都配置`actives`属性,则以`<dubbo:reference>` 优先。详见:
  2. ### 27.2. Load Balance 均衡
  3. 配置服务的客户端的 `loadbalance` 属性为 `leastactive`,此 `Loadbalance` 会调用并发数最小的 ProviderConsumer端并发数)。

  1. ## 28. 连接控制(未整理示例代码)
  2. ### 28.1. 服务端连接控制
  3. 限制服务器端接受的连接不能超过指定的数量

  1. ### 28.2. 客户端连接控制
  2. 限制客户端服务使用连接不能超过指定的数量

  1. > 注:
  2. > 1. 如果 `<dubbo:service>` `<dubbo:reference>` 都配置了`connections`属性,则以`<dubbo:reference>`优先
  3. > 1. 因为连接是在服务提供者Server上,所以配置在 Provider
  4. > 1. 如果是长连接,比如 Dubbo 协议,connections 表示该服务对每个提供者建立的长连接数
  5. ## 29. 延迟连接(未整理示例代码)
  6. Dubbo 中配置延迟连接用于减少长连接数。当有调用发起时,再创建长连接。

用于减少长连接数。当有调用发起时,再创建长连接。

  1. > **提示**:该配置只对使用长连接的 dubbo 协议生效。
  2. ## 30. 粘滞连接(未整理示例代码)
  3. 粘滞连接用于有状态服务,尽可能让客户端总是向同一提供者发起调用,除非该提供者挂了,再连另一台。粘滞连接将自动开启**延迟连接**,以减少长连接数。
  1. Dubbo 支持方法级别的粘滞连接,如果想进行更细粒度的控制,还可以这样配置。

  1. ## 31. 令牌验证(未整理示例代码)
  2. 通过令牌验证在注册中心控制权限,以决定要不要下发令牌给消费者,可以防止消费者绕过注册中心访问提供者,另外通过注册中心可灵活改变授权方式,而不需修改或升级提供者<br />![](https://gitee.com/moonzero/images/raw/master/code-note/20210803220649659_32538.jpg)<br />全局设置开启令牌验证:

  1. 服务级别设置开启令牌验证:

  1. ## 32. 配置规则
  2. Dubbo 中配置应用级治理规则和服务级治理规则
  3. ### 32.1. 旧版本的规则配置方式
  4. 2.2.0 以上版本支持,向注册中心写入动态配置覆盖规则。该功能通常由监控中心或治理中心的页面完成。

RegistryFactory registryFactory = ExtensionLoader.getExtensionLoader(RegistryFactory.class).getAdaptiveExtension(); Registry registry = registryFactory.getRegistry(URL.valueOf(“zookeeper://10.20.153.10:2181”)); registry.register(URL.valueOf(“override://0.0.0.0/com.foo.BarService?category=configurators&dynamic=false&application=foo&timeout=1000”));

  1. URL分解说明
  2. - `override://` 表示数据采用覆盖方式,支持 `override` `absent`,可扩展,**必填**。
  3. - `0.0.0.0` 表示对所有 IP 地址生效,如果只想覆盖某个 IP 的数据,请填入具体 IP,**必填**。
  4. - `com.foo.BarService` 表示只对指定服务生效,**必填**。
  5. - `category=configurators` 表示该数据为动态配置类型,**必填**。
  6. - `dynamic=false` 表示该数据为持久数据,当注册方退出时,数据依然保存在注册中心,**必填**。
  7. - `enabled=true` 覆盖规则是否生效,可不填,缺省生效。
  8. - `application=foo` 表示只对指定应用生效,可不填,表示对所有应用生效。
  9. - `timeout=1000` 表示将满足以上条件的 `timeout` 参数的值覆盖为 `1000`。如果想覆盖其它参数,直接加在 `override` URL 参数上。
  10. 其他示例:
  11. - 禁用提供者:(通常用于临时踢除某台提供者机器,相似的,禁止消费者访问请使用路由规则)

override://10.20.153.10/com.foo.BarService?category=configurators&dynamic=false&disbaled=true

  1. - 调整权重:(通常用于容量评估,缺省权重为 100)

override://10.20.153.10/com.foo.BarService?category=configurators&dynamic=false&weight=200

  1. - 调整负载均衡策略:(缺省负载均衡策略为 random)

override://10.20.153.10/com.foo.BarService?category=configurators&dynamic=false&loadbalance=leastactive

  1. - 服务降级:(通常用于临时屏蔽某个出错的非关键服务)

override://0.0.0.0/com.foo.BarService?category=configurators&dynamic=false&application=foo&mock=force:return+null

  1. ### 32.2. 新版本规则配置
  2. 覆盖规则是 Dubbo 设计的在无需重启应用的情况下,动态调整 RPC 调用行为的一种能力。2.7.0 版本开始,支持从服务和应用两个粒度来调整动态配置。
  3. #### 32.2.1. 概览
  4. 一般在**服务治理控制台**查看或修改覆盖规则。<br />应用粒度

将应用demo(key:demo)在20880端口上提供(side:provider)的所有服务(scope:application)的权重修改为1000(weight:1000)。


configVersion: v2.7 scope: application key: demo enabled: true configs:

  • addresses: [“0.0.0.0:20880”] side: provider parameters: weight: 1000 …
  1. 服务粒度

所有消费(side:consumer)DemoService服务(key:org.apache.dubbo.samples.governance.api.DemoService)的应用实例(addresses:[0.0.0.0]),超时时间修改为6000ms


configVersion: v2.7 scope: service key: org.apache.dubbo.samples.governance.api.DemoService enabled: true configs:

  • addresses: [0.0.0.0] side: consumer parameters: timeout: 6000 …
  1. #### 32.2.2. 规则配置模板详解

configVersion: v2.7 scope: application/service key: app-name/group+service+version enabled: true configs:

  • addresses: [“0.0.0.0”] providerAddresses: [“1.1.1.1:20880”, “2.2.2.2:20881”] side: consumer applications/services: [] parameters: timeout: 1000 cluster: failfase loadbalance: random
  • addresses: [“0.0.0.0:20880”] side: provider applications/services: [] parameters: threadpool: fixed threads: 200 iothreads: 4 dispatcher: all weight: 200 …
  1. - `configVersion` 表示 dubbo 的版本
  2. - `scope` 表示配置作用范围,分别是应用(application)或服务(service)粒度。**必填**。
  3. - `key` 指定规则体作用在哪个服务或应用。**必填**。
  4. - `scope=service`时,key取值为`[{group}:]{service}[:{version}]`的组合
  5. - `scope=application`时,key取值为application名称
  6. - `enabled=true` 覆盖规则是否生效,可不填,缺省生效。
  7. - `configs` 定义具体的覆盖规则内容,可以指定`n(n>=1)`个规则体。**必填**。
  8. - side
  9. - applications
  10. - services
  11. - parameters
  12. - addresses
  13. - providerAddresses
  14. #### 32.2.3. 规则配置示例
  15. - 禁用提供者:(通常用于临时踢除某台提供者机器,相似的,禁止消费者访问请使用路由规则)

configVersion: v2.7 scope: application key: demo-provider enabled: true configs:

  • addresses: [“10.20.153.10:20880”] side: provider parameters: disabled: true …
  1. - 调整权重:(通常用于容量评估,缺省权重为 200)

configVersion: v2.7 scope: application key: demo-provider enabled: true configs:

  • addresses: [“10.20.153.10:20880”] side: provider parameters: weight: 200 …
  1. - 调整负载均衡策略:(缺省负载均衡策略为 random)

configVersion: v2.7 scope: application key: demo-consumer enabled: true configs:

  • side: consumer parameters: loadbalance: random …
  1. - 服务降级:(通常用于临时屏蔽某个出错的非关键服务)

configVersion: v2.7 scope: service key: org.apache.dubbo.samples.governance.api.DemoService enabled: true configs:

  • side: consumer parameters: force: return null …
  1. # 服务化最佳实践
  2. > 详情参考官网:[http://dubbo.apache.org/zh-cn/docs/user/best-practice.html](1a2c9e0281a1e9cc4c4023831c7ece0a)
  3. ## 1. 在 Provider 端应尽量配置的属性
  4. Dubbo的属性配置优先度上,遵循顺序:`reference属性 --> service属性 --> Consumer 属性`<br />其中referenceConsumer是消费端配置,service是服务端配置<br />而对于服务调用的超时时间、重试次数等属性,服务的提供方比消费方更了解服务性能,因此我们应该在 Provider 端尽量多配置 Consumer 端属性,让其漫游到消费端发挥作用
  5. ### 1.1. 在 Provider 端尽量多配置 Consumer 端属性
  6. - Provider 端尽量多配置 Consumer 端的属性,让 Provider 的实现者一开始就思考 Provider 端的服务特点和服务质量等问题

  1. - 建议在 Provider 端配置的 Consumer 端属性有:
  2. 1. `timeout`:方法调用的超时时间
  3. 1. `retries`:失败重试次数,缺省是 2
  4. 1. `loadbalance`:负载均衡算法,缺省是随机 random。还可以配置轮询 roundrobin、最不活跃优先 leastactive 和一致性哈希 consistenthash
  5. 1. `actives`:消费者端的最大并发调用限制,即当 Consumer 对一个服务的并发调用到上限后,新调用会阻塞直到超时,在方法上配置 dubbo:method 则针对该方法进行并发限制,在接口上配置 dubbo:service,则针对该服务进行并发限制
  6. ### 1.2. 在 Provider 端配置合理的 Provider 端属性

  1. - 建议在 Provider 端配置的 Provider 端属性有:
  2. 1. `threads`:服务线程池大小
  3. 1. `executes`:一个服务提供者并行执行请求上限,即当 Provider 对一个服务的并发调用达到上限后,新调用会阻塞,此时 Consumer 可能会超时。在方法上配置 `dubbo:method` 则针对该方法进行并发限制,在接口上配置 `dubbo:service`,则针对该服务进行并发限制
  4. ## 2. 服务拆分最佳实现
  5. ### 2.1. 分包
  6. 建议将服务接口、服务模型、服务异常等均放在 API 包中,因为服务模型和异常也是 API 的一部分,这样做也符合分包原则:重用发布等价原则(REP),共同重用原则(CRP)。<br />如果需要,也可以考虑在 API 包中放置一份 Spring 的引用配置,这样使用方只需在 Spring 加载过程中引用此配置即可。配置建议放在模块的包目录下,以免冲突,如:com/alibaba/china/xxx/dubbo-reference.xml
  7. ### 2.2. 粒度
  8. 服务接口尽可能大粒度,每个服务方法应代表一个功能,而不是某功能的一个步骤,否则将面临分布式事务问题,Dubbo 暂未提供分布式事务支持。<br />服务接口建议以业务场景为单位划分,并对相近业务做抽象,防止接口数量爆炸。<br />不建议使用过于抽象的通用接口,如:Map query(Map),这样的接口没有明确语义,会给后期维护带来不便。
  9. ### 2.3. 版本
  10. 每个接口都应定义版本号,为后续不兼容升级提供可能,如:`<dubbo:service interface="com.xxx.XxxService" version="1.0" />`<br />建议使用两位版本号,因为第三位版本号通常表示兼容升级,只有不兼容时才需要变更服务版本。<br />当不兼容时,先升级一半提供者为新版本,再将消费者全部升为新版本,然后将剩下的一半提供者升为新版本。
  11. ### 2.4. 异常
  12. 建议使用异常汇报错误,而不是返回错误码,异常信息能携带更多信息,并且语义更友好。<br />如果担心性能问题,在必要时,可以通过 override 掉异常类的 `fillInStackTrace()` 方法为空方法,使其不拷贝栈信息。<br />查询方法不建议抛出 checked 异常,否则调用方在查询时将过多的 `try...catch`,并且不能进行有效处理。<br />服务提供方不应将 DAO SQL 等异常抛给消费方,应在服务实现中对消费方不关心的异常进行包装,否则可能出现消费方无法反序列化相应异常。
  13. ## 3. SPI机制原理
  14. Dubbo框架是建立的SPI机制之上
  15. ### 3.1. Java SPI 机制
  16. #### 3.1.1. SPI 简述
  17. SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件<br />![Java SPI 机制调用流程图](https://gitee.com/moonzero/images/raw/master/code-note/20200130184515935_6858.jpg)<br />可以看到,SPI的本质,其实是帮助程序,为某个特定的接口寻找它的实现类。而且哪些实现类的会加载,是个动态过程(不是提前预定好的)<br />有点类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。所以SPI的核心思想就是**解耦**
  18. - 常见的例子:
  19. - 数据库驱动加载接口实现类的加载。JDBC加载不同类型数据库的驱动
  20. - 日志门面接口实现类加载,SLF4J加载不同提供商的日志实现类
  21. - Spring中大量使用了SPI,比如:对servlet3.0规范对ServletContainerInitializer的实现、自动类型转换Type Conversion SPI(Converter SPIFormatter SPI)等
  22. #### 3.1.2. 基础使用
  23. - 要使用Java SPI,需要遵循如下约定:
  24. 1. 当服务提供者提供了接口的一种具体实现后,在jar包的`META-INF/services`目录下创建一个以“接口全限定名”为命名的文件,内容为实现类的全限定名
  25. 1. 接口实现类所在的jar包放在主程序的classpath
  26. 1. 程序通过`java.util.ServiceLoder`动态装载实现模块,它通过扫描`META-INF/services`目录下的配置文件找到实现类的全限定名,把类加载到JVM
  27. 1. SPI的实现类必须有无参构造方法
  28. > 详情示例参考dubbo-thought示例工程
  29. - 先定义一个接口

public interface InfoService { Object sayHello(String name); }

  1. - 定义多个接口的实现

public class InfoServiceAImpl implements InfoService { @Override public Object sayHello(String name) { System.out.println(name + “,你好,调通了A实现!”); return name + “,你好,调通了A实现!”; } }

public class InfoServiceBImpl implements InfoService { @Override public Object sayHello(String name) { System.out.println(name + “,你好,调通了B实现!”); return name + “,你好,调通了B实现!”; } }

  1. - 创建接口的全限定名文件`com.moon.service.InfoService`,内容为该接口的实现类名称

com.moon.service.impl.info.InfoServiceAImpl com.moon.service.impl.info.InfoServiceBImpl

  1. ![实现约定](https://gitee.com/moonzero/images/raw/master/code-note/20200131125629336_8727.jpg)
  2. - 测试

@Test public void testBaseSpi() { //服务加载器,加载实现类 ServiceLoader serviceLoader = ServiceLoader.load(InfoService.class); // ServiceLoader是实现了Iterable的迭代器,直接遍历实现类 for (InfoService service : serviceLoader) { Object result = service.sayHello(“Moon”); // 依次调用文件中配置的所有实现类 } }

  1. #### 3.1.3. 核心功能类
  2. 需要指出的是,java之所以能够顺利根据配置加载这个实现类,完全依赖于jdk内的一个核心类:<br />![SPI的核心类](https://gitee.com/moonzero/images/raw/master/code-note/20200130231835343_15049.jpg)
  3. ### 3.2. Dubbo SPI机制
  4. - Java SPI机制非常简单,就是读取指定的配置文件,将所有的类都加载到程序中。而这种机制,存在很多缺陷,比如:
  5. 1. 所有实现类无论是否使用,直接被加载,可能存在浪费
  6. 1. 不能够灵活控制什么时候什么时机,匹配什么实现,功能太弱
  7. - Dubbo框架就基于自己的需要,对JAVASPI机制进行增强
  8. #### 3.2.1. [SPI ](/SPI ) 注解
  9. Java SPI 实现类配置不同,Dubbo SPI 是通过键值对的方式进行配置,这样就可以按需加载指定的实现类。另外,需要在接口上标注 `@SPI` 注解。表明此接口是SPI的扩展点:

/**

  • SPI 全称为 Service Provider Interface,是一种服务发现机制,目标是为接口寻找实现类。
  • Java SPI 的作法:
  • 1.在类路径下META-INF/service下创建文件,名称为接口的全限定名。
  • 2.将接口实现类的全限定名配置在文件中
  • 3.服务启动时,将由服务加载器读取配置文件,并加载实现类。
  • Dubbo SPI的作法:
  • 1.Dubbo 增强原生的SPI机制来更好的满足拓展要求,其以键值对的方式对接口的实现进行配置管理。
  • 2.Dubbo引入三个注解: @SPI、@Adaptive和@Activate。
  • 只有接口上标注 @SPI 注解,才能被Dubbo框架管理起来 */ @SPI(“b”) // @SPI注解的值是指定默认的实现类对应的key值 public interface InfoService { Object sayHello(String name);

    Object passInfo(String msg, URL url); }

  1. - Dubbo SPI 所需的配置文件需放置在 `META-INF/dubbo` 路径下,配置文件的名称为接口的全限定名。文件的内容以key-value的形式,配置该接口对应的实现类的全限定名
  2. ![dubboSPI机制使用约定](https://gitee.com/moonzero/images/raw/master/code-note/20200131150555960_4262.jpg)
  3. - 测试

/**

  • dubbo SPI类加载验证
  • extensionLoader.getExtension(“a”) —> 取到key对应的扩展类
  • extensionLoader.getDefaultExtension() —> 取得在接口上@SPI注解指定的默认扩展类 */ @Test public void dubboSPI() { // 获取InfoService的 Loader 实例 ExtensionLoader extensionLoader = ExtensionLoader.getExtensionLoader(InfoService.class); // 取得a拓展类 InfoService infoServiceA = extensionLoader.getExtension(“a”); infoServiceA.sayHello(“AAAA”); // 取得接口上@SPI注解指定的默认拓展类(b拓展类) InfoService infoServiceB = extensionLoader.getDefaultExtension(); infoServiceB.sayHello(“I’m default”); }
  1. 测试结果

AAAA,你好,调通了A实现! I’m default,你好,调通了B实现!

  1. #### 3.2.2. [Activate ](/Activate ) 注解
  2. DubboSpi机制虽然对原生SPI有了增强,但功能还远远不够。<br />在工作中,某种时候存在这样的情形,需要同时启用某个接口的多个实现类,如Filter过滤器。希望某种条件下启用这一批实现,而另一种情况下启用那一批实现,比如:希望的RPC调用的消费端和服务端,分别启用不同的两批Filter,这样就需要使用`@Activate`注解<br />`@Activate`注解表示一个扩展是否被激活(使用),可以放在类定义和方法上,dubbo用它在spi扩展类定义上,表示这个扩展实现激活条件和时机。它有两个设置过滤条件的字段,`group``value` 都是字符数组。用来指定这个扩展类在什么条件下激活。简单示例如下:

// 表示如果过滤器使用方(通过group指定)属于Constants.PROVIDER(服务提供方)或者Constants.CONSUMER(服务消费方)就激活使用这个过滤器 @Activate(group = {Constants.PROVIDER, Constants.CONSUMER}) public class testActivate1 implements Filter { }

// 表示如果过滤器使用方(通过group指定)属于Constants.PROVIDER(服务提供方)并且 URL中有参数 Constants.TOKEN_KEY(token)时就激活使用这个过滤器 @Activate(group = Constants.PROVIDER, value = Constants.TOKEN_KEY) public class testActivate2 implements Filter { }

  1. > 详细示例参考dubbo-thought项目的base-spibusi-logic工程
  2. #### 3.2.3. Javassist动态编译
  3. -
  4. SPI寻找实现类的过程中,`getAdaptiveExtension`方法得到的对象,只是个接口代理对象,此代理对象是由临时编译的类来实现的。
  5. -
  6. javassist动态编译类有两种方式,此动态编译生成类是没有class文件
  7. -
  8. 方式一:Javassist是一个开源的分析、编辑和创建Java字节码的类库。
  9. - Javassist中最为重要的是ClassPoolCtClass CtMethod 以及 CtField这几个类。
  10. - `ClassPool`:一个基于HashMap实现的CtClass对象容器,其中键是类名称,值是表示该类的CtClass对象。默认的ClassPool使用与底层JVM相同的类路径,因此在某些情况下,可能需要向ClassPool添加类路径或类字节。
  11. - `CtClass`:表示一个类,这些CtClass对象可以从ClassPool获得。
  12. - `CtMethods`:表示类中的方法。
  13. - `CtFields`:表示类中的字段。

/**

  • javassist动态生成类示例 */ @Test public void createClassByJavassist() throws Exception { // 1. ClassPool:Class对象的容器 ClassPool pool = ClassPool.getDefault();

    // 2. 通过ClassPool生成一个public类,指定生成的全限定名 CtClass ctClass = pool.makeClass(“com.moon.service.DemoImpl”);

    // 3. 给生成的类添加属性 private String name CtField nameFild = new CtField(pool.getCtClass(“java.lang.String”), “name”, ctClass); nameFild.setModifiers(Modifier.PRIVATE); ctClass.addField(nameFild);

    // 添加属性 private int age CtField ageField = new CtField(pool.getCtClass(“int”), “age”, ctClass); ageField.setModifiers(Modifier.PRIVATE); ctClass.addField(ageField);

    // 4. 为属性添加getXXX和setXXX方法 ctClass.addMethod(CtNewMethod.getter(“getName”, nameFild)); ctClass.addMethod(CtNewMethod.setter(“setName”, nameFild)); ctClass.addMethod(CtNewMethod.getter(“getAge”, ageField)); ctClass.addMethod(CtNewMethod.setter(“setAge”, ageField));

    // 5. 添加方法,如:void sayHello(String name) {…} CtMethod ctMethod = new CtMethod(CtClass.voidType, “sayHello”, new CtClass[]{}, ctClass); // 将方法设置为public ctMethod.setModifiers(Modifier.PUBLIC); // 设置方法体内容 ctMethod.setBody(“{\nSystem.out.println(\”hello \” + getName() + \” !\”);\n}”); ctClass.addMethod(ctMethod); // 设置方法到类中

    // 6. 生成Class类对象 Class clazz = ctClass.toClass();

    // 通过反射去创建实例 Object obj = clazz.newInstance(); // 反射执行方法sayHello obj.getClass().getMethod(“setName”, new Class[]{String.class}).invoke(obj, new Object[]{“moon”}); obj.getClass().getMethod(“sayHello”, new Class[]{}).invoke(obj, new Object[]{}); }

  1. - 方式二:动态编译是从Java 6开始支持的,主要是通过一个JavaCompiler接口来完成的。通过这种方式我们可以直接编译一个已经存在的java文件,也可以在内存中动态生成Java代码,动态编译执行

/**

  • JavassistCompiler 动态编译类示例 */ @Test public void createClassByCompile() throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { // 1. 创建JavassistCompiler JavassistCompiler compiler = new JavassistCompiler(); // 2. 直接字符串的形式编写一个类,转成Class Class<?> clazz = compiler.compile(“public class DemoImpl implements DemoService { public String sayHello(String name) { System.out.println(\”hello \” + name); return \”Hello, \” + name; }}”, JavassistTest.class.getClassLoader()); // 3. 创建对象 Object obj = clazz.newInstance(); // 4. 反射 执行方法sayHello obj.getClass().getMethod(“sayHello”, new Class[]{String.class}).invoke(obj, new Object[]{“moon”}); }
  1. #### 3.2.4. [Adaptive ](/Adaptive ) 注解
  2. 扩展点对应的实现类不能在程序运行时动态指定,就是`extensionLoader.getExtension`方法写死了扩展点对应的实现类,不能在程序运行期间根据运行时参数进行动态改变。而在程序使用时会希望对实现类进行懒加载,并且能根据运行时情况来决定,应该启用哪个扩展类。为了解决这个问题,dubbo引入了`@Adaptive`注解,也就是dubbo的自适应机制
  3. 1. `@Adaptive`注解在加在具体的扩展实现类上,此类会当成最佳的适配

// @Adaptive // 有@Adaptive注解的实现类,优先选择做为SPI扩展接口的适配类 public class InfoServiceCImpl implements InfoService { …… }

/ 获取扩展点接口的加载器对象 / ExtensionLoader loader = ExtensionLoader.getExtensionLoader(InfoService.class); InfoService adaptiveExtension = loader.getAdaptiveExtension(); URL url = URL.valueOf(“test://localhost/test”); adaptiveExtension.passInfo(“moon”, url)

  1. 2. `@Adaptive`注解在接口的某个方法上,使用时,需要在URL对象中增加选择规则参数,如:`info.service=a`。参数名格式是接口类xxxXxxx的驼峰大小写拆分

@SPI(“b”) // @SPI注解的值是指定默认的实现类对应的key值 public interface InfoService { @Adaptive Object passInfo(String msg, URL url); }

/ 获取扩展点接口的加载器对象 / ExtensionLoader loader = ExtensionLoader.getExtensionLoader(InfoService.class); InfoService adaptiveExtension = loader.getAdaptiveExtension(); URL url = URL.valueOf(“test://localhost/test?info.service=a”); adaptiveExtension.passInfo(“moon”, url)

  1. 3. `@Adaptive({"xxxx"})`注解在接口的某个方法,直接指定url对象后的参数名称。

@SPI(“b”) // @SPI注解的值是指定默认的实现类对应的key值 public interface InfoService { @Adaptive({“abc”}) Object passInfo(String msg, URL url); }

/ 获取扩展点接口的加载器对象 / ExtensionLoader loader = ExtensionLoader.getExtensionLoader(InfoService.class); InfoService adaptiveExtension = loader.getAdaptiveExtension(); URL url = URL.valueOf(“test://localhost/test?info.service=a&abc=c”); adaptiveExtension.passInfo(“moon”, url)

  1. **注意:`ExtensionLoader<xxx>.getAdaptiveExtension()`方法获取的不是实例对象,而是静态代理对象**
  2. > 详细示例参考dubbo-thought项目
  3. #### 3.2.5. Dubbo SPI的依赖注入
  4. Dubbo SPI的核心实现类为ExtensionLoader,此类的使用几乎遍及Dubbo的整个源码体系
  5. - ExtensionLoader有三个重要的入口方法,分别与`@SPI``@Activate``@Adaptive`注解对应
  6. - `getExtension`方法,对应加载所有的实现
  7. - `getActivateExtension`方法,对应解析加载`@Activate`注解对应的实现
  8. - `getAdaptiveExtension`方法,对应解析加载`@Adaptive`注解对应的实现
  9. - 其中,`@Adaptive`注解作的自适应功能,还涉及到了代理对象(而Dubbo的代理机制,有两种选择,jdk动态代理和javassist动态编译类)
  10. ##### 3.2.5.1. Dubbo SPI 依赖注入场景
  11. DubboSPI机制,除上以上三种注解的用法外,还有一个重要的功能依赖注入,下面是依赖注入的一个示例:
  12. - 一个接口扩展点

@SPI(“Zero”) public interface OrderService { String getDetail(String id, URL url); }

  1. - 在其实现类中,引入了另一个扩展点接口对象InfoService

public class OrderServiceImpl implements OrderService { / 是dubbo的扩展点,是spring的bean接口 / private InfoService infoService;

  1. public void setInfoService(InfoService infoService) {
  2. this.infoService = infoService;
  3. }
  4. @Override
  5. public String getDetail(String name, URL url) {
  6. infoService.passInfo(name, url);
  7. System.out.println(name + ",OrderServiceImpl订单处理成功!");
  8. return name + ",你好,OrderServiceImpl订单处理成功!";
  9. }

}

  1. - 测试OrderService加载过程

@Test public void iocSPI() { // 获取OrderService的 Loader 实例 ExtensionLoader loader = ExtensionLoader.getExtensionLoader(OrderService.class); // 取得默认拓展类 OrderService orderService = loader.getDefaultExtension(); URL url = URL.valueOf(“test://localhost/test?info.service=b”); orderService.getDetail(“MoonZero”, url); }

  1. > 最后dubbo调用了InfoServiceB实现
  2. ##### 3.2.5.2. Dubbo依赖注入的过程分析
  3. `loader.getDefaultExtension();`开始,通过`getExtensionClasses();`方法获取所有扩展类Class对象,如果第一次创建,则参过`instance = createExtension(name);`创建实例<br />![创建扩展实例](https://gitee.com/moonzero/images/raw/master/code-note/20200202185605860_9906.png)<br />先创建扩展实例,此时实例中的引用扩展对象的值为null。通过`injectExtension()`方法中的`Object object = objectFactory.getExtension(pt, property);`进行扩展属性对象依赖注入

@SPI public interface ExtensionFactory { /**

  1. * Get extension.
  2. *
  3. * @param type object type.
  4. * @param name object name.
  5. * @return object instance.
  6. */
  7. <T> T getExtension(Class<T> type, String name);

}

`` 这是dubbo的一个扩展点工厂接口,只有一个方法,作用是根据class和name查找实现类。这个接口,是dubbo一个扩展点,接下来看看此接口的实现类<br />![! 修改文件](https://gitee.com/moonzero/images/raw/master/code-note/20200202191251020_579.jpg)<br />ExtensionFactory接口有两个实现类,一个适配类(adaptive,接口的默认实现)。AdaptiveExtensionFactory在内部持有了所有的factory实现工厂,即SpiExtensionFactorySpringExtensionFactory`两个实现类。一个为SPI工厂(依赖类是扩展接口时发挥作用),一个为Spring工厂(依赖的是springbean时发挥作用)。于是,当需要为某个生成的对象注入依赖时,直接调用此对象即可。从而实现Dubbo SPI的IOC功能

4. 其他

4.1. dubbo框架使用示例

dubbo框架使用示例项目参考:D:\code\dubbo-note\dubbo-sample\

4.2. 相关RPC服务框架(HSF) — 网络资料

高速服务框架 HSF (High-speed Service Framework),是在阿里巴巴内部广泛使用的分布式 RPC 服务框架。
HSF 联通不同的业务系统,解耦系统间的实现依赖。HSF 从分布式应用的层面,统一了服务的发布/调用方式,从而帮助方便、快速的开发分布式应用,以及提供或使用公共功能模块,并屏蔽了分布式领域中的各种复杂技术细节,如:远程通讯、序列化实现、性能损耗、同步/异步调用方式的实现等
HSF 纯客户端架构的 RPC 框架,本身是没有服务端集群的,所有的 HSF 服务调用都是服务消费方(Consumer)与服务提供方(Provider)点对点进行的。然而,为了实现整套分布式服务体系,HSF 还包含了其他组件,完整的组件如下:
Provider——服务提供者
通常是某个业务系统,提供相关的业务服务,一般都是个服务集群。业务系统初期使用HSF,通过引入SDK的方式。后期阿里定制化改造了容器(tomcat/docker),通过配置完成HSF的接入使用,作为架构中的服务提供方。对业务系统本身(war/jar)不造成侵入性。
Consumer——服务消费者
通常也是某个业务系统集群,跟服务提供者非常类型,只是服务、消费角度区别。
ConfigServer——配置服务器
HSF 依赖注册中心进行服务发现,如果没有注册中心,HSF 只能完成简单的点对点调用。因为作为服务提供端,没有办法将自己的服务信息对外发布,让外界知晓;作为服务消费端,可能已经知道需要调用的服务,但是无法获取能够提供这些服务的机器。而ConfigServer就是服务信息的中介,提供服务发现的能力。

  • 注册与订阅
  • 服务注册——在服务提供方启动时,完成该服务的注册——上报当前服务的ip:port、服务类名方法名、版本号、分组等信息。
  • 服务订阅——在服务消费方启动时,完成某些服务的订阅——通过订阅的服务标识(类名+方法名+版本号等),在内存中查找相关服务,一旦找到则将服务提供者的IP端口等信息推送给当前服务消费方,以便消费。
  • 长连接与内存级
  • 服务提供方与服务消费方均与配置服务器保持长连接,一旦服务提供方发生变化,会立即被通知,更新内存中的列表数据,并同步给订阅该服务的服务消费方。

Diamond——持久化配置中心
持久化的配置中心用于存储 HSF 服务的各种治理规则,HSF 客户端在启动的过程中会向持久化配置中心订阅各种服务治理规则,如白名单、权限、路由规则、归组规则、权重规则等,从而根据规则对调用过程的选址逻辑进行干预。持久化配置中心的角色是由 Diamond组件承担的。
当需要时,用户通过在界面上设计规则,Diamond会快速的将新的规则推送给相关的服务提供方
addressServer——地址服务
地址服务器的职责是保存上文的ConfigServer与Diamond服务器的地址列表。在consumer、provider启动时,会首先以域名访问的方式访问地址服务器,从地址服务器获取到ConfigServer与Diamond服务器的地址列表信息,以便访问。
元数据存储
并没有在图中展示出来。元数据是指 HSF 服务对应的方法列表以及参数结构等信息,元数据不会对 HSF 的调用过程产生影响,因此元数据存储中心也并不是必须的。但考虑到服务运维的便捷性,HSF客户端在启动时会将元数据上报到元数据存储中心,以便提供给服务运维使用。元数据存储中心的角色是由 Redis 承担的。
完成初始化后,服务调用者内保存了相关的服务信息,通过内部设置的调用方式(如随机访问一台,权重等)可以进行点对点的调用。其内部通过NIO多路复用(Netty)+Hessian序列化实现RPC调用,使用Hessian主要是压测表现出稳定性以及高效的综合特性。