目录
1.Dubbo介绍
Apache Dubbo是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。
Dubbo就是个服务框架,如果没有分布式的需求,其实是不需要用的,只有在分布式的时候,才有Dubbo这样的分布式服务框架的需求。
1.1工作原理
0:start >>>项目启动,将服务提供者(Prvider)提供的服务加载到容器(Container).中.Container就是spring容器,这个学过spring的应该都知道。
1:register >>>将容器中的服务注册到注册中心(Registry)去。
2:subscribe >>>消费者(Consumer)订阅注册中心(Registry)。
3:notify >>> 注册中心(Registry)告诉消费者(Consumer)我有那些服务。
4:invoke >>> 消费者(Consumer)向发起服务提供者(Prvider)调用。
5:count>>>监听器(Monitor)会监听所有的的执行过程。
2.集成Dubbo
2.1基础用法
1)公共接口工程
1、新建module,名为alibaba-dubbo-common
2、新建公共接口及实体类
public interface OrderDubboService {
List<OrderInfo> findAllOrder();
OrderInfo findOrderByUserId(Long userId);
String getInfo();
}
OrderInfo.java
public class OrderInfo implements Serializable {
private Long orderId;
private Long userId;
public Long getOrderId() {
return orderId;
}
public void setOrderId(Long orderId) {
this.orderId = orderId;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
}
2)服务提供者
1、新建module,模块名称为alibaba-cloud-order
2、引入相关的依赖
<!--boot web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- nacos注册中心 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- nacos配置中心-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- dubbo RPC调用,注意是spring-cloud-starter-dubbo-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-dubbo</artifactId>
</dependency>
<!-- 公共依赖 -->
<dependency>
<groupId>com.harvey</groupId>
<artifactId>alibaba-dubbo-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
3、bootstrap.yml配置
server:
port: 8888
spring:
application:
name: alibaba-cloud-order
cloud:
nacos:
#服务注册
discovery:
enabled: true
#nacos地址
server-addr: http://localhost:8848
#当前应用是否注册到注册中心,默认是true
register-enabled: true
#配置中心
config:
enabled: true
#nacos地址
server-addr: http://localhost:8848
#配合文件后缀
file-extension: yaml
# dubbo配置
dubbo:
application:
name: ${spring.application.name}
#扫描java包,多个包用逗号分割
scan:
base-packages: com.harvey.demo
#注册中心
registry:
#注册中心地址列表,同一集群内的多个地址用逗号分隔
address: nacos://127.0.0.1:8848
#是否向此注册中心订阅服务
subscribe: true
#注册中心会话超时时间(毫秒)
session: 6000
#服务提供者
provider:
register: true
#服务消费者
consumer:
#远程服务调用重试次数,不包括第一次调用,不需要重试请设为0
retries: 2
protocol:
#随机端口,默认是20880
port: -1
name: dubbo
4、启动类
@SpringBootApplication
public class OrderApp {
public static void main(String[] args) {
SpringApplication.run(OrderApp.class, args);
}
}
5、Dubbo接口的实现
@DubboService
public class OrderDubboServiceImpl implements OrderDubboService {
private static Map<Long, OrderInfo> orderInfoMap = new HashMap();
static {
orderInfoMap.put(10001L, new OrderInfo(1L, 10001L));
orderInfoMap.put(10002L, new OrderInfo(2L, 10002L));
orderInfoMap.put(10003L, new OrderInfo(3L, 10003L));
}
@Override
public List<OrderInfo> findAllOrder() {
return new ArrayList(orderInfoMap.values());
}
@Override
public OrderInfo findOrderByUserId(Long userId) {
return orderInfoMap.get(userId);
}
@Override
public String getInfo() {
return "alibaba-cloud-order-01";
}
}
6、启动项目,结果报错:
***************************
APPLICATION FAILED TO START
***************************
Description:
An attempt was made to call a method that does not exist. The attempt was made from the following location:
org.apache.dubbo.config.spring.beans.factory.annotation.ReferenceAnnotationBeanPostProcessor.<init>(ReferenceAnnotationBeanPostProcessor.java:106)
The following method did not exist:
org.apache.dubbo.config.spring.beans.factory.annotation.ReferenceAnnotationBeanPostProcessor.setClassValuesAsString(Z)V
The method's class, org.apache.dubbo.config.spring.beans.factory.annotation.ReferenceAnnotationBeanPostProcessor, is available from the following locations:
jar:file:/E:/englishPath/selfrespority/org/apache/dubbo/dubbo/2.7.13/dubbo-2.7.13.jar!/org/apache/dubbo/config/spring/beans/factory/annotation/ReferenceAnnotationBeanPostProcessor.class
The class hierarchy was loaded from the following locations:
org.apache.dubbo.config.spring.beans.factory.annotation.ReferenceAnnotationBeanPostProcessor: file:/E:/englishPath/selfrespority/org/apache/dubbo/dubbo/2.7.13/dubbo-2.7.13.jar
com.alibaba.spring.beans.factory.annotation.AbstractAnnotationBeanPostProcessor: file:/E:/englishPath/selfrespority/com/alibaba/spring/spring-context-support/1.0.10/spring-context-support-1.0.10.jar
org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter: file:/E:/englishPath/selfrespority/org/springframework/spring-beans/5.2.8.RELEASE/spring-beans-5.2.8.RELEASE.jar
Action:
Correct the classpath of your application so that it contains a single, compatible version of org.apache.dubbo.config.spring.beans.factory.annotation.ReferenceAnnotationBeanPostProcessor
一直以为是依赖冲突问题。改了无数个版本都不行,最后终于在github,spring-cloud-alibaba issues中找到了答案:升级 2.2.7 报错 · Issue #2310 · alibaba/spring-cloud-alibaba · GitHub
单独添加依赖:
<dependency>
<groupId>com.alibaba.spring</groupId>
<artifactId>spring-context-support</artifactId>
<version>1.0.11</version>
</dependency>
重新启动,控制台没有报错,查看nacos服务列表:
实际开发中,常用namespace来隔离应用和Dubbo服务的注册。
例如我们创建两个命名空间dev和dubbo,应用注册到dev,dubbo服务注册到命名空间dubbo,修改bootstrap.yml:
spring:
cloud:
nacos:
#服务注册
discovery:
namespace: 2f7930c8-ccae-4577-bdcd-0cf874cf1297
# dubbo配置
dubbo:
#注册中心
registry:
address: nacos://127.0.0.1:8848?namespace=fab88f57-87a7-4e9b-a3a5-fcdee1605282
最终效果:
3)服务消费者
1、新建module,名为alibaba-cloud-user
2、引入相关的依赖
<!--boot web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- nacos注册中心 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- nacos配置中心-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- dubbo RPC调用,注意是spring-cloud-starter-dubbo-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-dubbo</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.spring</groupId>
<artifactId>spring-context-support</artifactId>
<version>1.0.11</version>
</dependency>
<!-- 公共依赖 -->
<dependency>
<groupId>com.harvey</groupId>
<artifactId>alibaba-dubbo-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
3、bootstrap.yml
server:
port: 9999
spring:
application:
name: alibaba-cloud-user
cloud:
nacos:
#服务注册
discovery:
enabled: true
#nacos地址
server-addr: http://localhost:8848
#当前应用是否注册到注册中心,默认是true
register-enabled: true
namespace: 2f7930c8-ccae-4577-bdcd-0cf874cf1297
#配置中心
config:
enabled: true
#nacos地址
server-addr: http://localhost:8848
#配合文件后缀
file-extension: yaml
# dubbo配置
dubbo:
application:
name: ${spring.application.name}
#扫描java包,多个包用逗号分割
scan:
base-packages: com.harvey.demo
#注册中心
registry:
#注册中心地址列表,同一集群内的多个地址用逗号分隔
address: nacos://127.0.0.1:8848?namespace=fab88f57-87a7-4e9b-a3a5-fcdee1605282
#是否向此注册中心订阅服务
subscribe: true
#注册中心会话超时时间(毫秒)
session: 6000
#服务提供者
provider:
register: true
#服务消费者
consumer:
#远程服务调用重试次数,不包括第一次调用,不需要重试请设为0
retries: 2
protocol:
#随机端口,默认是20880
port: -1
name: dubbo
4、启动类
@SpringBootApplication
public class CloudUserApp {
public static void main(String[] args) {
SpringApplication.run(CloudUserApp.class, args);
}
}
5、新建UserService、UserController完成调用
UserService.java
public interface UserService {
Object findAllOrders();
Object getOrderInfo(Long userId);
Object getInfo();
}
UserServiceImpl.java
@Service
public class UserServiceImpl implements UserService{
@DubboReference
private OrderDubboService orderDubboService;
@Override
public Object findAllOrders() {
return orderDubboService.findAllOrder();
}
@Override
public Object getOrderInfo(Long userId) {
return orderDubboService.findOrderByUserId(userId);
}
@Override
public Object getInfo(){
return orderDubboService.getInfo();
}
}
UserController.java
@RequestMapping("/user")
@RestController
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/findAllOrders")
public Object findAllOrders() {
return userService.findAllOrders();
}
@RequestMapping("/getOrderInfo")
public Object getOrderInfo(@RequestParam("userId") Long userId) {
return userService.getOrderInfo(userId);
}
@RequestMapping("/getInfo")
public Object getInfo() {
return userService.getInfo();
}
}
6、测试验证
启动项目,访问 http://127.0.0.1:9999/user/findAllOrders
3.服务提供者和消费者的配置规则
dubbo推荐在Provider上尽量多配置Consumer端属性:
作为服务的提供者,⽐服务⽅更清楚服务性能的参数,如调⽤时间,合理的重试次数等,所以这些参数应尽量配置在服务的提供者⽅;
在provider配置后,Consumer不配置则会使⽤provider的配置值,即provider的配置会作为consumer配置的缺省值。如果使⽤consumer的全局配置,这对于provider是不可控的,并且是不合理的。
配置优先顺序为:消费端⽅法级 > 服务端⽅法级 > 消费端接⼝级 > 服务端接⼝级 > 消费端全局 > 服务端全局
3.1.负载均衡
我们与alibaba-cloud-order一样,创建一个新的module,alibaba-cloud-order2,将bootstrap.yml中的server.port改为7777,OrderDubboServiceImpl的getInfo方法改为:
@Override
public String getInfo() {
return "alibaba-cloud-order-02";
}
启动项目后,我们在nacos可以看到alibaba-cloud-order有两个实例:
Dubbo主要提供了以下几种负载均衡策略:
- RandomLoadBalance (随机-random):默认策略,实际上是考虑了权重之后的随机选择,如果每个服务提供者的权重都一致,那么就使用 java 的随机函数去选择一个。
- RoundRobinLoadBalance (轮询-roundrobin):轮询负载均衡策略,本质上也是考虑了权重之后的轮循。如果 A 服务提供者的权重是 B 服务提供者的两倍,那么理论上 A 被轮循到的次数就会是 B 的两倍。
- LeastActiveLoadBalance (最少活跃-leastactive):最少活跃调用数,相同活跃数的随机。活跃数指调用前后计数差。使慢的 Provider 收到更少请求,因为越慢的 Provider 的调用前后计数差会越大。
- ConsistentHashLoadBalance (一致性 hash-consistenthash):使用一致性 Hash 算法,让相同参数的请求总是发到同一 Provider。 当某一台 Provider 崩溃时,原本发往该 Provider 的请求,基于虚拟节点,平摊到其它 Provider,不会引起剧烈变动。
- ShortestResponseLoadBalance (最短反馈):根据响应时间和当前服务的请求量去获得一个最优解。如果存在多个最优解,则考虑权重,如果仅有一个则权重无效。
1、修改服务消费者alibaba-cloud-user的配置,负载均衡策略改为轮询
dubbo:
#服务消费者
consumer:
#负载均衡
loadbalance: roundrobin #轮询
2、重启项目,访问 http://127.0.0.1:9999/user/getInfo,我们发现,alibaba-cloud-order-01 和 alibaba-cloud-order-02 是轮着展示的,说明配置成功了。
3、上面我们讲到轮询也是基于权重后的轮询,我们在nacos上修改alibaba-cloud-order的两个dubbo服务提供者实例的权重,看看效果怎样(本人测试貌似没起作用)。
编辑其中一个实例的权重,点击"确认"时报错:
caused: errCode: 500, errMsg: do metadata operation failed ;
caused: com.alibaba.nacos.consistency.exception.ConsistencyException: com.alibaba.nacos.core.distributed.raft.exception.NoLeaderException: The Raft Group [naming_instance_metadata] did not find the Leader node;
caused: com.alibaba.nacos.core.distributed.raft.exception.NoLeaderException: The Raft Group [naming_instance_metadata] did not find the Leader node;
Nacos 采用 raft 算法来计算 Leader,并且会记录前一次启动的集群地址,所以当我们自己的服务器 IP 改变时(这里特指自己学习时,在本地启动的同学,因为有时候我们的网络环境会变的 … WIFI,所以 IP 地址也经常变化),会导致 raft 记录的集群地址失效,导致选 Leader 出现问题,只要删除 Nacos 根目录下 data 文件夹下的 protocol 文件夹即可。
注意:该错误的解决方案可能不适用所有人,大家还是找下度娘,尝试多种方案解决吧。
3.2.集群容错
在集群调用失败时,Dubbo 提供了多种容错方案,缺省为 failover 重试。
- FailoverCluster(failover):失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。可通过 retries="2" 来设置重试次数(不含第一次)。
- FailfastCluster(failfast):快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。
- FailsafeCluster(failsafe):失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。
- FailbackCluster(failback):失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。
- ForkingCluster(forking):并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks="2" 来设置最大并行数。
- AvailableCluster(available):调用目前可用的实例(只调用一个),如果当前没有可用的实例,则抛出异常。通常用于不需要负载均衡的场景。
集群容错这样配置:
dubbo:
#服务消费者
consumer:
# 集群容错
cluster: failover
3.3.服务降级
可以通过服务降级功能临时屏蔽某个出错的非关键服务,并定义降级后的返回策略。
dubbo官方文档上使用一个mock配置,实现服务降级。mock只在出现非业务异常(比如超时,网络异常等)时执行。
mock的配置支持两种:
- 配置"return null",可以很简单的忽略掉异常。
- 为boolean值,默认的为false。如果配置为true,则缺省使用mock类名,即类名+Mock后缀;
1、模拟超时场景
1)alibaba-cloud-user配置timeout:
dubbo:
consumer:
timeout: 2000
2)alibaba-cloud-order和alibaba-cloud-order2休眠3s
@Override
public String getInfo() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "alibaba-cloud-order-01";
}
@Override
public String getInfo() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "alibaba-cloud-order-02";
}
3)重启项目后,访问 http://127.0.0.1:9999/user/getInfo
2、直接return null 忽略异常
服务调用方配置
dubbo:
consumer:
mock: "return null"
此时控制台还会报错,但是页面没有显示任何报错信息了。
3、使用mock类名,即类名+Mock后缀
在业务接口所在的包中,定义一个类,该类的命名需要满足以下规则:业务接口简单类名 + Mock同时实现mock接口,类名要注意命名规范:接口名+Mock后缀。此时如果调用失败会调用Mock实现。mock实现需要保证有无参的构造方法。
1)服务调用方配置
dubbo:
consumer:
mock: true
2)服务调用方提供Mock实现(注意规范)
/**
* OrderDubboServiceMock
* 1)实现的接口是服务提供方提供的Dubbo接口OrderDubboService,不是我们的业务接口
* 2)实现类的命名规范:Dubbo接口名 + Mock
* 3)实现类与Dubbo接口必须在同一个包路径下
* 以上条件缺一不可,否则会找不到对应的Mock类
*/
public class OrderDubboServiceMock implements OrderDubboService {
@Override
public List<OrderInfo> findAllOrder() {
List<OrderInfo> orderInfoList = new ArrayList();
orderInfoList.add(new OrderInfo(-1L, -1L));
return orderInfoList;
}
@Override
public OrderInfo findOrderByUserId(Long userId) {
return new OrderInfo(-1L, -1L);
}
@Override
public String getInfo() {
return "getInfo调用失败了";
}
}
3)测试验证,访问 http://127.0.0.1:9999/user/getInfo ,控制报错了,但是浏览器显示如下:
说明我们的服务降级Mock生效了。
3.4.重试机制
当消费者请求⼀个服务时出现错误,会重试连接其他的服务器,但重试会带来更多的延迟。重试次数可以使⽤【retries=重试次数】来设置。
Dubbo的超时重试机制为服务容错、服务稳定提供了⽐较好的框架⽀持 dubbo在调⽤服务不成功时,默认会重试2次。Dubbo的路由机制,会把超时的请求路由到其他机器上,⽽不是本机尝试,所以 dubbo的重试机器也能⼀定程度的保证服务的质量。,但是在⼀些⽐较特殊的⽹络环境下(⽹络传输慢,并发多)可能由于服务响应慢,Dubbo⾃⾝的超时重试机制(服务端的处理时间超过了设定的超时时间时,就会有重复请求)可能会带来⼀些⿇烦。
常见的应⽤场景故障:
- 发送邮件(重复) ;
- 账户注册(重复)
解决方案:
1)对于核⼼的服务中⼼,去除dubbo超时重试机制,并重新评估设置超时时间。
retries设置为0,表示不重试。
2)业务处理代码必须放在服务端,客户端只做参数验证和服务调⽤,不涉及业务流程处理。
3.5.限流
为了防止某个消费者的QPS或是所有消费者的QPS总和突然飙升而导致的重要服务的失效,系统可以对访问流量进行控制,这种对集群的保护措施称为服务限流。
Dubbo中能够实现服务限流的方式较多,可以划分为两类:直接限流与间接限流
- 直接限流:通过对连接数量直接进行限制来达到限流的目的。
- 间接限流:通过一些非连接数量设置来达到限制流量的目的。
1、executes直接限流– 仅提供者端
该属性仅能设置在提供者端。可以设置为接口级别,也可以设置为方法级别。限制的是服务(方法)并发执行数量。
服务提供者:
dubbo:
provider:
executes: 10
接口级别:
@DubboService(executes = 10)
方法级别:
@DubboService(methods = {
@Method(name = "getInfo", executes = 10)
})
2、accepts限流 – 仅提供者端
用于对指定协议的连接数量进行限制。
3、actives限流 – 两端
该限流方式与前两种不同的是,其可以设置在提供者端,也可以设置在消费者端。可以设置为接口级别,也可以设置为方法级别
1)提供者端限流
根据消费者与提供者间建立的连接类型的不同,其意义也不同。
- 长连接:表示当前长连接最多可以处理的请求个数。与长连接的数量没有关系
- 短连接:表示当前服务可以同时处理的短连接数量
2)消费者端限流
根据消费者与提供者间建立的连接类型的不同,其意义也不同:
- 长连接:表示当前消费者所发出的长连接中最多可以提交的请求个数。与长连接的数量没有关系。
- 短连接:表示当前消费者可以提交的短连接数量
4、connections限流
可以设置在提供者端,也可以设置在消费者端。限定连接的个数。对于短连接,该属性效果与actives相同。但对于长连接,其限制的是长连接的个数。
一般情况下,会使connectons与actives联用,让connections限制长连接个数,让actives限制一个长连接中可以处理的请求个数。联用前提:使用默认的Dubbo服务暴露协议。
5、间接限流
1)延迟连接 – 仅消费者端
仅可设置在消费者端,且不能设置为方法级别。仅作用于Dubbo服务暴露协议。
将长连接的建立推迟到消费者真正调用提供者时。
可以减少长连接的数量。
@DubboReference(lazy = true)
2)粘连连接 – 仅消费者
仅能设置在消费者端,其可以设置为接口级别,也可以设置为方法级别。仅作用于Dubbo服务暴露协议。
其会使客户端尽量向同一个提供者发起调用,除非该提供者挂了,其会连接另一台。只要启用了粘连连接,其就会自动启用延迟连接。
其限制的是流向,而非流量。
@DubboReference(sticky = true)
3)负载均衡
可以设置在消费者端,亦可设置在提供者端;可以设置在接口级别,亦可设置在方法级别。其限制的是流向,而非流量。