Dubbo实战运用
# 1 Dubbo与SpringBoot的整合
基于Zookeeper实现Dubbo与Spring Boot的集成整合。
# 1.1 工程POM依赖
<properties>
<dubbo-version>2.7.8</dubbo-version>
<spring-boot.version>2.3.0.RELEASE</spring-boot.version>
</properties>
<dependencyManagement>
<dependencies>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Apache Dubbo -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-dependencies-bom</artifactId>
<version>${dubbo-version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>${dubbo-version}</version>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring</artifactId>
</exclusion>
<exclusion>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
</exclusion>
<exclusion>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- Dubbo Spring Boot Starter -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>${dubbo-version}</version>
</dependency>
<!-- Dubbo核心组件 -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
</dependency>
<!--Spring Boot 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- Zookeeper客户端框架 -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>4.0.1</version>
</dependency>
<!-- Zookeeper dependencies -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-dependencies-zookeeper</artifactId>
<version>${dubbo-version}</version>
<type>pom</type>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
Dubbo采用2.7.8版本, Spring Boot采用的是2.3.0.RELEASE版本。
如果依赖下载出现问题, 可以指定具体的仓库:
<repositories>
<repository>
<id>apache.snapshots.https</id>
<name>Apache Development Snapshot Repository</name>
<url>https://repository.apache.org/content/repositories/snapshots</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
# 1.2 公用RPC接口工程
为便于客户端与服务端的RPC接口引用, 这里对RPC接口做统一封装。
定义了一个订单服务接口, 用于测试验证。
public interface OrderService {
/**
* 获取订单详情
* @param orderId
* @return
*/
String getOrder(Long orderId);
}
# 1.3. 服务端工程
- 工程结构
POM依赖
<dependencies> <!-- Dubbo Spring Boot Starter --> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-spring-boot-starter</artifactId> <version>${dubbo-version}</version> </dependency> <!-- Dubbo 核心依赖 --> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo</artifactId> </dependency> <!-- 公用RPC接口依赖 --> <dependency> <groupId>com.itheima</groupId> <artifactId>dubbo-spring-interface</artifactId> <version>${project.version}</version> </dependency> </dependencies>
RPC服务接口
@DubboService(version = "${dubbo.spring.provider.version}") public class OrderServiceImpl implements OrderService { /** * 服务端口 */ @Value("${server.port}") private String serverPort; @Value("${dubbo.spring.provider.version}") private String serviceVersion; /** * 获取订单详情 * @param orderId * @return */ public String getOrder(Long orderId) { String result = "get order detail ,orderId="+orderId +",serverPort="+serverPort +",serviceVersion="+serviceVersion; System.out.println(result); return result; } }
通过DubboService注解, 声明为RPC服务,version可以标识具体的版本号, 消费端需匹配保持一致。
工程配置
# 服务端口 server.port=18081 # 应用程序名称 spring.application.name=dubbo-spring-provider # Dubbo服务扫描路径 dubbo.scan.base-packages=com.itheima # Dubbo 通讯协议 dubbo.protocol.name=dubbo # Dubbo服务提供的端口, 配置为-1,代表为随机端口 默认20880 dubbo.protocol.port=-1 ## Dubbo 注册器配置信息 dubbo.registry.address=zookeeper://127.0.0.1:2181 dubbo.registry.file = ${user.home}/dubbo-cache/${spring.application.name}/dubbo.cache dubbo.spring.provider.version = 1.0.0
Spring Boot启动程序
@SpringBootApplication @ComponentScan(basePackages = {"com.itheima"}) public class DubboSpringProviderApplication { public static void main(String[] args) { SpringApplication.run(DubboSpringProviderApplication.class, args); } }
# 1.4. 消费端工程
- 工程结构
POM依赖:
<dependencies> <!-- Dubbo Spring Boot Starter --> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-spring-boot-starter</artifactId> <version>${dubbo-version}</version> </dependency> <!-- 公用RPC接口依赖 --> <dependency> <groupId>com.itheima</groupId> <artifactId>dubbo-spring-interface</artifactId> <version>${project.version}</version> </dependency> </dependencies>
消费端调用
@Controller @RequestMapping("/order") public class OrderController { private final Logger logger = LoggerFactory.getLogger(getClass()); /** * 订单服务接口 */ @DubboReference(version = "${dubbo.spring.provider.version}") private OrderService orderService; /** * 获取订单详情接口 * @param orderId * @return */ @RequestMapping("/getOrder") @ResponseBody public String getOrder(Long orderId) { String result = null; try { result = orderService.getOrder(orderId); }catch(Exception e) { logger.error(e.getMessage(), e); } return result; } }
工程配置
# 服务端口 server.port=18084 #服务名称 spring.application.name=dubbo-spring-consumer #服务版本号 dubbo.spring.provider.version = 1.0.0 #消费端注册器配置信息 dubbo.registry.address=zookeeper://127.0.0.1:2181 dubbo.registry.file = ${user.home}/dubbo-cache/${spring.application.name}/dubbo.cache
Spring Boot启动程序
@SpringBootApplication @ComponentScan(basePackages = {"com.itheima"}) public class DubboSpringConsumerApplication { public static void main(String[] args) { SpringApplication.run(DubboSpringConsumerApplication.class, args); } }
# 1.5. 工程调用验证
启动ZK注册中心
启动服务端, 运行DubboSpringProviderApplication
启动消费端, 运行DubboSpringConsumerApplication
请求获取订单接口, 地址: http://127.0.0.1:18084/order/getOrder?orderId=1001
调用成功:
# 2 Dubbo高阶配置运用
# 2.1 不同配置覆盖关系
关于配置参看官方文档:https://dubbo.apache.org/zh/docsv2.7/user/configuration/
演示:
1、通过ProviderConfig配置全局超时(可通过yml配置覆盖)
2、在@DubboService注解上配置接口超时
3、在@DubboService注解上配置接口方法超时
4、在消费方进行配置,查看覆盖情况
覆盖规则: 配置规则:
- 方法级优先,接口级次之,全局配置再次之。
- 如果级别一样,则消费方优先,提供方次之。
服务端超时设定
增加配置类:
@Configuration public class DubboCustomConfig { /** * 服务端 * @return */ @Bean public ProviderConfig registryConfig() { ProviderConfig providerConfig = new ProviderConfig(); // 设定超时时间为5S providerConfig.setTimeout(5000); return providerConfig; } }
修改服务接口实现:
设定模拟睡眠时间
/** * 获取订单详情 * @param orderId * @return */ public String getOrder(Long orderId) { try { // 休眠1.5秒 Thread.sleep(1500L); } catch (InterruptedException e) { e.printStackTrace(); } return "Get Order Detail, Id: " + orderId + ", serverPort: " + serverPort; }
客户端调用验证
http://127.0.0.1:18084/order/getOrder?orderId=123
服务端全局超时设为2秒(不触发超时), 消费端设定超时时间为1秒(触发超时)。
修改调用代码:
/** * 订单服务接口 */ @DubboReference(version = "${dubbo.spring.provider.version}", timeout = 1000) private OrderService orderService;
调用结果, 触发超时:
表明消费端配置优先。
服务端全局超时设定为1秒(触发超时), 消费端去掉超时时间配置。
触发超时, 表明服务提供方优先级次之。
# 2.2 属性配置优先级
优先级规则
优先级从高到低:
JVM -D 参数;
-Ddubbo.protocol.port=20881
XML(application.yml/application.properties)配置会重写 dubbo.properties 中的,一般配置项目特有的
dubbo: protocol: name: dubbo # 通讯协议 #port: 20880 # dubbo服务提供方的端口,该值是默认值 port: 20882
Properties默认配置(dubbo.properties),仅仅作用于以上两者没有配置时,一般配置全局公共配置
dubbo.protocol.port=20883
JVM参数优先级验证
application.properties里面配置了dubbo.protocol.port端口10888
JVM运行参数端口设定为-Ddubbo.protocol.port=10889
启动服务
服务启动完成, 可以看到端口优先以JVM参数为准。
Properties配置文件验证
注释掉application.properties里面配置的dubbo.protocol.name和dubbo.protocol.port配置
dubbo.properties里面配置dubbo.protocol.name和dubbo.protocol.port
在启动参数里, 添加-Ddubbo.properties.file=dubbo.properties
启动服务
查看dubbo.properties配置的端口, 可以看到正常生效:
C:\Users\hxx68>netstat -ano |findstr 30889 TCP 0.0.0.0:30889 0.0.0.0:0 LISTENING 49816 TCP [::]:30889 [::]:0 LISTENING 49816
# 2.3 重试与容错处理机制
容错机制:
Failfast Cluster
快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。
Failsafe Cluster
失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。
Failback Cluster
失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。
Forking Cluster
并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks="2" 来设置最大并行数。
Broadcast Cluster
广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息。
后面章节还会对其原理做详细讲解。
调整客户端重试次数
/** * 订单服务接口 */ @DubboReference(version = "${dubbo.spring.provider.version}",retries = 3) private OrderService orderService;
这里的重试次数设定为3次。
修改服务端超时时间
模拟超时, 让客户端触发重试。
/** * 服务端全局配置 * @return */ @Bean public ProviderConfig registryConfig() { ProviderConfig providerConfig = new ProviderConfig(); providerConfig.setTimeout(1000); return providerConfig; }
将超时时间设小一些为1秒。
客户端调用(单个服务)
http://127.0.0.1:18084/order/getOrder?orderId=123
查看服务端控制台,可以看到触发重试机制:
加上第一次的调用和3次重试, 共4次。
客户端调用(多个,涉及到负载均衡机制,后面再测试)
允许多个实例运行, 开启多个服务
访问接口, http://127.0.0.1:18084/order/getOrder?orderId=123
第一个服务实例,访问两次, 其他每个服务访问一次。
# 2.4 多版本控制
启动三个服务端
第一个服务端版本为1.0.0, 第二、三个服务端版本分别为:2.0.0 和 3.0.0
主要是修改application.properties配置:
#服务版本号 dubbo.spring.provider.version = 2.0.0
启动三个服务提供者,通过
-Ddubbo.spring.provider.version = 2.0.0 -Dserver.port=18082
来启动三个相关的端口不能重复
消费端指定版本号
同样修改application.properties配置:
#服务版本号 dubbo.spring.provider.version = 2.0.0
仍然通过
-Ddubbo.spring.provider.version = 2.0.0
来调用不同版本的服务测试时,注释掉超时的代码
仍是采用超时配置, 通过重试测试验证
测试验证结果:
请求只会访问至版本号为2.0.0的服务节点上面。
# 2.5 本地存根调用
实现流程
把 Stub 暴露给用户,Stub 可以决定要不要去调 Proxy。
客户端存根实现:
增加service接口
public class OrderServiceStub implements OrderService { private final OrderService orderService; // 构造函数传入真正的远程代理对象 public OrderServiceStub(OrderService orderService){ this.orderService = orderService; } /** * 获取订单详情 * @param orderId * @return */ public String getOrder(Long orderId){ // 在客户端执行, 预先验证参数是否合法,等等 try { if(null != orderId) { return orderService.getOrder(orderId); } return "参数校验错误!"; } catch (Exception e) { //容错处理 return "出现错误:" + e.getMessage(); } } }
修改客户端调用配置
/** * 订单服务接口 */ @DubboReference(version = "${dubbo.spring.provider.version}",retries = 3, stub = "com.itheima.dubbo.spring.consumer.service.OrderServiceStub") private OrderService orderService;
stub要配置存根接口的完整路径。
测试调用
访问不带参数的地址: http://127.0.0.1:18084/order/getOrder
会进入存根接口的处理逻辑, 提示校验异常。
# 2.6 负载均衡机制
默认负载策略
Dubbo默认采用的是随机负载策略。
开启三个服务节点,通过消费端访问验证: http://127.0.0.1:18084/order/getOrder?orderId=123
通过控制后台日志输出, 可以看到每个服务节点呈现不规则的调用。
Dubbo 支持的负载均衡策略,可用参看源码:
AbstractLoadBalance
Random LoadBalance:默认
随机,按权重设置随机概率。 在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。
RoundRobin LoadBalance
加权轮询负载均衡,按公约后的权重设置轮询比率。 存在慢的提供者累积请求的问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。
LeastActive LoadBalance
最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。 活跃数其实就是在当前这个服务调用者中当前这个时刻 某个invoker(某个服务提供者的某个接口)某个方法的调用并发数,在调用之前+1 调用之后-1的一个计数器,如果出现多个活跃数相等invoker的时候使用随机算法来选取一个
ConsistentHash LoadBalance
一致性 Hash,相同参数的请求总是发到同一提供者。 当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。
一致性Hash负载均衡涉及到两个主要的配置参数为hash.arguments 与hash.nodes。
hash.arguments : 当进行调用时候根据调用方法的哪几个参数生成key,并根据key来通过一致性hash算法来选择调用结点
hash.nodes: 为结点的副本数
ShortestResponseLoadBalance
2.7.7 +新增
最短响应时间负载均衡
从多个服务提供者中选择出调用成功的且响应时间最短的服务提供者,由于满足这样条件的服务提供者有可能有多个。所以当选择出多个服务提供者后要根据他们的权重做分析,如果权重一样,则随机
源码实现: org.apache.dubbo.rpc.cluster.loadbalance.AbstractLoadBalance
Dubbo提供了四种实现:
四种配置方式:
优先级从下至上:
服务端的服务级别:
<dubbo:service interface="..." loadbalance="roundrobin" />
客户端的服务级别:
<dubbo:reference interface="..." loadbalance="roundrobin" />
注意:服务提供者配置后最终也只是同步到消费者端。故一般在消费端配置
服务端方法级别:
<dubbo:service interface="..."> <dubbo:method name="..." loadbalance="roundrobin"/> </dubbo:service>
客户端方法级别:
<dubbo:reference interface="..."> <dubbo:method name="..." loadbalance="roundrobin"/> </dubbo:reference>
当然这里还有全局配置(略),
调用验证
修改客户端的负载策略, 改为轮询策略:
注意很多配置都有三个级别,针对方法的,针对接口的,全局的。
@DubboReference(version = "${dubbo.spring.provider.version}",retries = 3, loadbalance = "roundrobin", stub = "com.itheima.dubbo.spring.consumer.service.OrderServiceStub")
开启三个服务节点, 进行访问验证: http://127.0.0.1:18084/order/getOrder?orderId=123
会依次轮询进行调用。
注意:测试时将服务提供者的版本号都调成一致(1.0.0),超时配置注释掉!
动态权重调整验证
管理后台地址: http://127.0.0.1:8080/#
通过管理后台修改服务的权重配置:
将两台节点的权重降低至20:
调整后可以看到权重配置已经生效:
通过消费者接口访问, 会发现第一台权重较大的节点, 访问次数会明显增多。
# 2.7 服务降级运用
服务动态禁用
启动单个服务节点,进入Dubbo Admin, 创建动态配置规则:
configVersion: v2.7 enabled: true configs: - side: provider addresses: - '0.0.0.0:20880' parameters: timeout: 3000 disabled: true
将disabled属性设为true, 服务禁用, 可以错误提示:
将disabled属性改为false,
configVersion: v2.7 enabled: true configs: - side: provider addresses: - '0.0.0.0:20880' parameters: timeout: 3000 disabled: false
恢复正常访问:
服务降级
配置规则
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&mock=force:return+null"));
mock=force:return+null
表示消费方对该服务的方法调用都直接返回 null 值,不发起远程调用。用来屏蔽不重要服务不可用时对调用方的影响。- 还可以改为
mock=fail:return+null
表示消费方对该服务的方法调用在失败后,再返回 null 值,不抛异常。用来容忍不重要服务不稳定时对调用方的影响。
降级测试(force方式)
进入Dubbo Admin进行配置:
configVersion: v2.7 enabled: true configs: - side: consumer addresses: - 0.0.0.0 parameters: timeout: 3000 mock: 'force:return null'
客户端调用, 会直接屏蔽, 并且服务端控制台不会有任何调用记录:
降级测试(fail方式)
进入Dubbo Admin配置:
configVersion: v2.7 enabled: true configs: - side: consumer addresses: - 0.0.0.0 parameters: timeout: 1000 mock: 'fail:return null'
这里为了触发调用异常, 超时时间缩短为1秒。
注意这里的超时时间可能不会起作用,最终的超时时间还是得看项目中配置,故在服务提供方将线程休眠时间延长,造成调用超时。
客户端调用, 会出现降级显示为空:
同时服务端会有调用记录显示(请求会进入服务端,但由于超时, 调用是失败):
# 2.8 并发与连接控制
实际运用, 会碰到高并发与峰值场景, Dubbo是可以做到并发与连接数控制。
可使用jmeter进行测试!
并发数控制
服务端控制
服务级别
<dubbo:service interface="com.foo.BarService" executes="10" />
服务器端并发执行(或占用线程池线程数)不能超过 10 个。
方法级别
<dubbo:service interface="com.foo.BarService"> <dubbo:method name="sayHello" executes="3" /> </dubbo:service>
限制具体的方法,服务器端并发执行(或占用线程池线程数)不能超过3 个。
客户端控制
调用的服务控制
<dubbo:reference interface="com.foo.BarService" actives="10" />
每客户端并发执行(或占用连接的请求数)不能超过 10 个。
调用的服务方法控制
<dubbo:reference interface="com.foo.BarService"> <dubbo:method name="sayHello" actives="10" /> </dubbo:service>
客户端负载配置
<dubbo:reference interface="com.foo.BarService" loadbalance="leastactive" />
负载策略为最小连接数时, Loadbalance 会调用并发数最小的 Provider。
连接数控制
服务端连接控制
<dubbo:provider protocol="dubbo" accepts="10" />
限制服务器端接受的连接不能超过 10 个
客户端连接控制
<dubbo:reference interface="com.foo.BarService" connections="10" />
限制客户端服务使用连接不能超过 10 个
如果 dubbo:service 和 dubbo:reference 都配了 connections,dubbo:reference 优先。