Dubbo2.7 纯注解使用+ Nacos+Springboot
- 环境准备篇
- 相关依赖
- nacos准备
- 代码编写
- 服务提供者
- 服务使用者
- 整体结构图
- 结果
- 常规操作篇
- 服务分组
- 服务版本
- 参数传递
- 泛化调用
- 参数校验
- 只订阅
- 延迟暴露
- 服务端异步回调
- 多协议复用
- 多注册中心
- 本地存根
- 服务治理篇
- 超时时间
- 重试
- 并发控制
- 权限控制
- 服务降级
- 负载均衡
- 集群容错
- 拓展应用篇
- 自定义事件机制
- 自定义拓展类
- 生效方法
- 自定义过滤器
- 自定义拓展类
- 生效方式
- 自定义负载均衡
- 自定义拓展类
- 生效方式
- 自定义服务引用监听
- 自定义拓展类
- 生效方式
- 自定义服务暴露监听
- 自定义拓展类
- 生效方式
- 自定义路由
- 自定义拓展类
- 生效方式
- 自定义集群策略
- 自定义拓展类
- 生效方式
- 注解说明篇
- DubboReference
- DubboService
- Activate
- Method
- Argument
环境准备篇
本文目的是关于Dubbo纯注解版的使用以及注册中心采用nacos,所以在工程结构以及命名方面可能不是那么规范
首先我们看一下整体的工程结构,你可以简单的理解成有两个springboot工程,都引入了一个公共的模块或者公共包,而这个公共模块就是专门来放暴露的接口的
相关依赖
对应springboot工程使用dubbo非常简单,只需要引入dubbo依赖即可,但是最好注意一下版本(两个工程依赖都一致的)
<!-- SpringBoot 依赖配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.7.12-SNAPSHOT</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--dubbo相关-->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.12</version>
</dependency>
<!--因为要采用nacos注册中心-->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-nacos</artifactId>
<version>2.7.12</version>
</dependency>
nacos准备
学习使用的目的,所以nacos只需要准备个单机即可,也无需考虑数据持久化问题,直接去nacos地址下载一个部署即可,我是liunx环境,下载是2.0.3版本的(版本无所谓)
liunx中解压后直接去bin目录下 运行命令启动即可
bash startup.sh -m standalone
默认端口是8848 ,这时候输入网址http://xxxxxx:8848/nacos 打开成功就OK了,默认账号密码
nacos nacos
代码编写
首先在公共模块中新建一个接口 TestService
public interface TestService {
String test(String message);
}
服务提供者
dubbo-annotation-producer工程:就是把自己的一些接口服务暴露出去可以让别的服务调用
配置文件
server:
port: 8081
dubbo:
registry:
# 注册中心地址 采用nacos
address: nacos://IP:PORT
# 分组
group: DUBBO_TEST
protocol:
# 协议选择 这里选择dubbo
name: dubbo
# 通信端口
port: 22223
application:
# 应用名称 服务名即可
name: dubbo-producer
scan:
# 需要暴露接口的包名(可以理解为mybatis中扫描mapper)
base-packages: cn.colins.api
接口实现:添加@DubboService注解
@Component
// 此注解代表将该接口服务暴露出去
@DubboService
public class ProducerTestService implements TestService {
public String test(String message) {
return message;
}
}
启动类:添加@EnableDubbo注解
@EnableDubbo
@SpringBootApplication
public class DubboAnnotationProducerApplication {
public static void main(String[] args) {
SpringApplication.run(DubboAnnotationProducerApplication.class, args);
}
}
服务使用者
dubbo-annotation-consumer工程:调用别的服务暴露出去的接口服务
配置文件
server:
port: 9000
dubbo:
registry:
# 注册中心地址 采用nacos
address: nacos://IP:PORT
# 分组
group: DUBBO_TEST
protocol:
# 协议选择 这里选择dubbo
name: dubbo
# 通信端口
port: 33333
application:
# 应用名称 服务名即可
name: dubbo-consumer
scan:
# 需要暴露接口的包名(可以理解为mybatis中扫描mapper)
base-packages: cn.colins.api
接口调用:就是常规的controller层写法,但是服务不在是从spring容器中取了,而是采用@DubboReference注解从dubbo中获取
@RestController
public class ConsumerTestController {
// check代表启动不检查
@DubboReference(check = false)
private TestService testService;
@GetMapping("/test")
public String test(String message){
return testService.test(message);
}
}
启动类:添加@EnableDubbo注解
@EnableDubbo
@SpringBootApplication
public class DubboAnnotationConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(DubboAnnotationConsumerApplication.class, args);
}
}
整体结构图
结果
两个工程启动后,可以在nacos控制台服务中看到
浏览器请求接口
这样简单的环境就搞定了,只需要注意producer和consumer这样命名只是为了更好的理解,服务提供者和服务调用者的概念,事实上每个微服务都可以即是提供者,也是调用者,可以看到用注解比用xml要简单方便的多,以前的容器是spring提供的,现在可以理解为有个dubbo的容器,可以往里面塞服务和取服务
常规操作篇
上文我们主要用了三个注解@EnableDubbo、@DubboService、@DubboReference
@EnableDubbo可以基本不用理,就是个启动注解
主要用另外两个:
@DubboService:提供服务的注解
@DubboReference:调用服务的注解
如果把dubbo看做是一个容器,那前者就是存入设置好的服务,后者就是要根据配置取出对应的服务
服务分组
使用场景
同一个接口针对不同的业务场景、不同的使用需求或者不同的功能模块等场景,可使用服务分组来区分不同的实现方式。同时,这些不同实现所提供的服务是可并存的,也支持互相调用。
大白话:同一个接口可以有不同的实现,类似RocketMq消息中的tag,可以通过分组来确定调用的具体某种实现
实现
// 提供者注解 加上group参数
@DubboService(group = "test1")
// 调用者注解 就需要加上group参数来选择调用具体的哪个服务
@DubboReference(check = false,group = "test1")
看一下服务名就知道了,暴露的服务也会打上对应的参数,如果不带条件获取是找不到服务的哦
服务版本
使用场景
同一个接口可能存在多个版本,每个版本都有不同的逻辑,比如:接口升级从v1升级到了v2,那v1任然有服务在使用不可能直接下架,所以这时候需要v1和v2两个版本共存
实现
// 提供者注解 加上version 参数
@DubboService(group = "test1", version = "v1")
// 调用者注解 就需要加上version参数来选择调用具体的哪个服务
@DubboReference(check = false,group = "test1", version = "v1")
通常情况下需要group和version同时使用才能确定唯一的服务
参数传递
使用场景
在系统间调用时,想传递接口定义之外的一些参数怎么办?就比如一些上下文信息、用户凭证等等
实现
主要是利用RpcContext(完成下面一次远程调用会被清空,即多次远程调用要多次设置)
RpcContext分为(ServerContext、ClientAttachment、ServerAttachment 、ServiceContext)
它们分别承担了不同的职责:
- ServiceContext:在 Dubbo 内部使用,用于传递调用链路上的参数信息,如 invoker 对象等
- ClientAttachment:在 Client 端使用,往 ClientAttachment 中写入的参数将被传递到 Server 端
- ServerAttachment:在 Server 端使用,从 ServerAttachment 中读取的参数是从 Client 中传递过来的
- ServerContext:在 Client 端和 Server 端使用,用于从 Server 端回传 Client 端使用,Server 端写入到 ServerContext 的参数在调用结束后可以在 Client 端的 ServerContext 获取到
调用者: 修改原来的ConsumerTestController
@RestController
@Slf4j
public class ConsumerTestController {
@DubboReference(check = false,group = "test1", version = "v1")
private TestService testService;
@GetMapping("/test")
public String test(String message){
// 调用前给提供者传递参数
RpcContext.getContext().setObjectAttachment("clientKey","server 你好!");
String test = testService.test(message);
// 调用后获取提供者给我的参数
Map<String, Object> serverAttachment = RpcContext.getServerContext().getObjectAttachments();
log.info("ContextTask clientAttachment: " + JSON.toJSON(serverAttachment));
return test;
}
}
提供者: 修改原来的ProducerTestService
@Component
@DubboService(group = "test1", version = "v1")
@Slf4j
public class ProducerTestService implements TestService {
public String test(String message) {
// 接收调用者传递过来的参数
Map<String, Object> clientAttachments = RpcContext.getContext().getObjectAttachments();
log.info("ContextService clientAttachments:" + JSON.toJSONString(clientAttachments));
// 给调用者传递参数
RpcContext.getServerContext().setAttachment("serverKey","client 你好!");
// 返回
return message;
}
}
泛化调用
使用场景
泛化调用(客户端泛化调用)是指在调用方没有服务方提供的 API(SDK),只知道服务的接口的全限定类名和方法名的情况下,对服务方进行调用,并且可以正常拿到调用结果。
实现
调用者: 修改原来的ConsumerTestController ,新增一个泛化调用接口
@GetMapping("/special")
public String special(String message){
// 调用前给提供者传递参数
RpcContext.getContext().setObjectAttachment("clientKey","server 你好!");
// 泛化调用 注意没有用上面引入的 testService
GenericService genericService = buildGenericService("cn.colins.api.TestService","test1","v1");
//传入需要调用的方法名、参数类型列表、参数值列表
Object result = genericService.$invoke("test", new String[]{"java.lang.String"}, new Object[]{message});
System.out.println("GenericTask Response: " + JSON.toJSONString(result));
// 调用后获取提供者给我的参数
Map<String, Object> serverAttachment = RpcContext.getServerContext().getObjectAttachments();
log.info("ContextTask clientAttachment: " + JSON.toJSON(serverAttachment));
return result.toString();
}
private GenericService buildGenericService(String interfaceClass, String group, String version) {
ReferenceConfig<GenericService> reference = new ReferenceConfig<GenericService>();
reference.setInterface(interfaceClass);
reference.setVersion(version);
//开启泛化调用
reference.setGeneric("true");
reference.setTimeout(30000);
reference.setGroup(group);
ReferenceConfigCache cache = ReferenceConfigCache.getCache();
try {
return cache.get(reference);
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
}
}
参数校验
使用场景
这个就不用多说吧,就是用来校验参数的
实现
参数验证功能是基于 JSR303 实现的,用户只需标识 JSR303 标准的验证 annotation ,我们在API模块加入校验依赖 (TestService接口模块)
<!-- Java Validation API -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<!-- Hibernate Validator -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.17.Final</version>
</dependency>
在接口方法上加入校验注解
public interface TestService {
String test(@NotNull(message = "消息不能为空") String message);
}
然后在提供者和调用者注解上加入 validation="true"即可
// 提供者注解 加上validation 参数
@DubboService(group = "test1", version = "v1",validation = "true")
// 调用者注解 就需要加上validation 参数
@DubboReference(check = false,group = "test1", version = "v1",validation = "true")
实际测试两个注解其中一个加了就能生效,在调用方报错
异常处理,还有这个校验更多的使用不用介绍了吧,平时用的也挺多的
只订阅
使用场景
为方便开发测试,经常会在线下共用一个所有服务可用的注册中心,这时,如果一个正在开发中的服务提供者注册,可能会影响调用者不能正常运行。可以让服务提供者开发方,只订阅服务(开发的服务可能依赖其它服务),而不注册正在开发的服务,通过直连测试正在开发的服务。
实现
// 提供者注解 register=false 代表不注册
@DubboService(group = "test1", version = "v1",validation = "true",register = false)
服务提供者就找不到了
延迟暴露
这个效果就是等到spring容器初始化完后,延迟多久才将服务注册到注册中心
// 提供者注解 delay延迟时间 单位:毫秒
@DubboService(group = "test1", version = "v1",validation = "true",delay = 10000)
服务端异步回调
使用场景
回调函数通知客户端执行结果,或发送通知,在方法执行时间比较长时,类似异步调用,审批工作流中回调客户端审批结果。
先看一下效果吧,可能这样说不太理解,其实和平时的本地回调差不多
实现
先在API模块新增两个接口CallbackService 和 CallbackListener
public interface CallbackListener {
void changed(String msg);
}
public interface CallbackService {
String addListener(String key, CallbackListener listener);
}
服务提供者
// 代表给addListener 方法添加一个类型为cn.colins.api.CallbackListener 的回调
@DubboService(methods = {
@Method(name = "addListener", arguments = { @Argument(callback = true, type = "cn.colins.api.CallbackListener") })
})
public class CallbackTestServiceImpl implements CallbackService {
@Override
public String addListener(String key, CallbackListener listener) {
// 异步处理
new Thread(()->{
listener.changed(getChanged(key)); // 异步处理
}).start();
// 直接返回
return key;
}
private String getChanged(String key) {
// 逻辑处理。。。。。完后返回
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "处理了5s返回结果";
}
}
服务调用者
// 新增一个接口正常调用就好了
@GetMapping("/callbackTest")
public String callbackTest(){
String test = callbackService.addListener("test", new CallbackListener() {
@Override
public void changed(String msg) {
log.info("callback msg : {}", msg);
}
});
log.info("直接调用返回的 msg : {}", test);
return test;
}
效果就是先直接返回了,然后5s后拿到服务端处理后的结果
整体结构现如下:
多协议复用
使用场景
可以与不同的系统兼容,试想同一个接口你在内部采用dubbo协议通信,这时候又需要暴露出去与其他不同协议的系统对接,难不成每种协议都写一个接口?而且除此之外它还有其他好处
- 改进的性能:不同的协议可能具有不同的性能特征,这取决于传输的数据量和网络条件等因素。通过使用多种协议,可以根据您的性能要求选择最适合给定情况的协议。
- 安全性:一些协议可能提供比其他协议更好的安全特性。HTTPS 协议通过加密传输的数据来提供安全通信,这对于保护敏感数据非常有用。
- 易用性:某些协议在某些情况下可能更易于使用。如果正在构建 Web 应用程序并希望与远程服务集成,使用 HTTP 协议可能比使用需要更复杂设置的协议更方便
实现
这里以同时配置dubbo和rest协议为例,修改dubbo-producer项目
配置文件
dubbo:
registry:
address: nacos://returnac.cn:8848
group: DUBBO_TEST
username:
password:
application:
name: dubbo-producer
scan:
base-packages: cn.colins.api
protocols:
dubbo:
name: dubbo
port: 22223
rest:
name: rest
port: 22224
# 使用内置服务器 还可以用其他类型的容器
server: tomcat
依赖添加
因为rest协议是基于标准的 Java REST API——JAX-RS 2.0(Java API for RESTful Web Services 的简写)实现的 REST 调用支持,所以要引入相关依赖
<!--dubbo支持rest-->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-rpc-rest</artifactId>
<version>2.7.12</version>
</dependency>
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>javax.ws.rs-api</artifactId>
<version>2.1.1</version>
</dependency>
提供者服务修改
protocol: 代表同时支持的协议
@Path : 代表路径
@Produces: 代表返回设置
关于JAX-RS相关的配置就不做多的说明了
@Component
@DubboService(group = "test1", version = "v1",validation = "true",protocol = {"dubbo","rest"})
@Path("rest")
@Produces({"application/json; charset=UTF-8", "text/xml; charset=UTF-8"})
public class ProducerTestService implements TestService {
private final static Logger log= LoggerFactory.getLogger(ProducerTestService.class);
@GET()
@Path("test")
@Override
public String test(@QueryParam("message") String message) {
// 接收调用者传递过来的参数
Map<String, Object> clientAttachments = RpcContext.getContext().getObjectAttachments();
log.info("ContextService clientAttachments:" + JSON.toJSONString(clientAttachments));
// 给调用者传递参数
RpcContext.getServerContext().setAttachment("serverKey","client 你好!");
// 返回
return message;
}
}
调用者修改
// 因为提供者采用了多协议,所以这里也需要配置protocol指定具体的协议
@DubboReference(check = false,group = "test1", version = "v1",validation = "true",protocol = "dubbo")
测试结果
内部的dubbo协议远程调用依旧正常
而我们直接http请求也ok了,注意rest配置的是22224端口
再看看nacos控制台,你会发现实例数变成了两个,区别就是端口和协议不同
多注册中心
使用场景
Dubbo 支持同一服务向多注册中心同时注册,或者不同服务分别注册到不同的注册中心上去,甚至可以同时引用注册在不同注册中心上的同名服务。另外,注册中心是支持自定义扩展的,有以下好处:
- 高可用:多个注册服务器确保即使其中一个注册服务器出现故障,服务仍然可用。
- 负载:同时访问大量服务,使用多个注册服务器帮助在多个服务器之间分配负载提高系统的整体性能和可扩展性。
- 区域:不同地理位置的服务,使用多个注册服务器来确保根据其位置注册和发现服务减少请求需要传输的距离来帮助提高系统的性能和可靠性。
- 安全:使用多个注册服务器。期望将一台注册服务器用于内部服务,另一台用于外部服务,以确保只有授权的客户端才能访问您的内部服务
实现
在配置文件中配置:
dubbo:
# 单个注册中心
# registry:
# address: nacos://IP:PORT
# group: DUBBO_TEST
registries:
# 注册中心ID 别名 不能重复
center1:
address: nacos://IP:PORT
group: DUBBO_TEST
center2:
address: nacos://IP:PORT
group: DUBBO_TEST
然后再注解中选择需要注册到哪个注册中心
// 提供者注解 加上registry参数 选择需要注册到哪个注册中心
@DubboService(registry= "center1")
// 调用者注解 加上registry参数 选择需要注册到哪个注册中心
@DubboReference(check = false,registry = "center1")
// 也可以同时注册到多个注册中心,用,隔开: registry = "center1,center2"
本地存根
使用场景
远程服务后,客户端通常只剩下接口,而实现全在服务器端,但提供方有些时候想在客户端也执行部分逻辑,说白了就是在调用方调用方法的进行了一层包装
实现
我们在调用方,也就是consumer下新建一个文件夹service
新建MyTestService实现TestService接口(就是我们需要远程调用的接口)
注意: 构造方法一定要有,而且参数就是接口对象,下面实现的方法则是最终会调用的方法
public class MyTestService implements TestService {
private final static Logger log= LoggerFactory.getLogger(MyTestService.class);
private TestService testService;
public MyTestService(TestService testService){
this.testService=testService;
}
@Override
public String test(String message) {
// 此代码在客户端执行, 你可以在客户端做ThreadLocal本地缓存,或预先验证参数是否合法,等等
log.info("存根测试...");
try {
return testService.test(message);
} catch (Exception e) {
// 你可以容错,可以做任何AOP拦截事项
return "容错数据";
}
}
}
然后再调用者注解上加上stub参数,配置实现类为全路径类名
@DubboReference(check = false,stub = "cn.colins.consumer.service.MyTestService")
结果: 最终是走的我们实现类中的方法
服务治理篇
超时时间
场景就不用说了吧,懂得都懂
实现
无论是服务提供者还是调用者都可以配置,如果都配了,基本上结果是取最小的那个
// 调用方注解 配置
// 控制一个服务下所有方法
@DubboReference(check = false,timeout = 1000)
// 控制一个服务某些方法(方法可以指定控制多个)
@DubboReference(check = false,methods = {@Method(name = "方法名",timeout = 1000)})
// 提供方注解 配置
// 控制一个服务下所有方法
@DubboService(timeout = 2000)
// 控制一个服务某些方法(方法可以指定控制多个)
@DubboService(methods = {@Method(name = "方法名",timeout = 2000)})
效果
重试
实现
无论是服务提供者还是调用者都可以配置,如果都配了,基本上结果是取最大的那个(默认都是2)
// 调用方注解 配置
// 控制一个服务下所有方法
@DubboReference(check = false,retries = 4)
// 控制一个服务某些方法(方法可以指定控制多个)
@DubboReference(check = false,methods = {@Method(name = "方法名",retries = 4)})
// 提供方注解 配置
// 控制一个服务下所有方法
@DubboService(retries = 3)
// 控制一个服务某些方法(方法可以指定控制多个)
@DubboService(methods = {@Method(name = "方法名",retries = 3)})
并发控制
使用场景
控制某些服务的最大并发请求数,确保其他服务的资源可用性。系统过载和确保系统稳定性。
允许在需求增加时更平滑地扩展服务。
确保服务在高峰使用时间保持可靠和稳定。
实现
有两种语义的控制方法:服务端并发执行数、客户端并发执行数
服务端并发执行数:不区分客户端,总体并发数(占用线程池线程数)
// 提供方注解 配置
// 控制一个服务下所有方法
@DubboService(executes= 10)
// 控制一个服务某些方法(方法可以指定控制多个)
@DubboService(methods = {@Method(name = "方法名",executes= 10)})
客户端并发执行数:区分客户端,就是每个客户端占用连接的请求数
// 提供方注解 配置
// 控制一个服务下所有方法
@DubboService(actives= 10)
// 控制一个服务某些方法(方法可以指定控制多个)
@DubboService(methods = {@Method(name = "方法名",actives= 10)})
// 调用方注解 配置
// 控制一个服务下所有方法
@DubboReference(check = false,actives= 10)
// 控制一个服务某些方法(方法可以指定控制多个)
@DubboReference(check = false,methods = {@Method(name = "方法名",actives= 10)})
权限控制
使用场景
通过令牌验证在注册中心控制权限,以决定要不要下发令牌给消费者, 可以防止消费者绕过注册中心访问提供者, 另外通过注册中心可灵活改变授权方式,而不需修改或升级提供者
实现
// 提供方注解 配置 true代表:随机token令牌,使用UUID生成
@DubboService(token="true")
or
// 固定token令牌,相当于密码
@DubboService(token="132456")
效果
正常通过注册中心远程调用的不受影响
加粗样式
而直接走本机的rest调用则被拦截
服务降级
使用场景
- 某服务或接口负荷超出最大承载能力范围,需要进行降级应急处理,避免系统崩溃
- 调用的某非关键服务或接口暂时不可用时,返回模拟数据或空,业务还能继续可用
- 降级非核心业务的服务或接口,腾出系统资源,尽量保证核心业务的正常运行
- 某上游基础服务超时或不可用时,执行能快速响应的降级预案,避免服务整体雪崩
实现
在调用者配置文件中加入配置
dubbo:
consumer:
filter: mock
在注解上加入mock参数,有几种配置方法:
- 全路径名
mock="[fail|force]return|throw xxx"
- fail 或 force 关键字可选,表示调用失败或不调用强制执行 mock 方法,如果不指定关键字默认为 fail
- return 表示指定返回结果,throw 表示抛出指定异常
- xxx 根据接口的返回类型解析,可以指定返回值或抛出自定义的异常
如:
return null:代表失败返回null
throw java.lang.NullPointException : 失败抛出指定异常
我这里采用全路径名
@DubboReference(check = false,mock = "cn.colins.api.TestServiceMock")
在API模块下新建一个实现类:
public class TestServiceMock implements TestService {
private final static Logger log= LoggerFactory.getLogger(TestServiceMock.class);
@Override
public String test(String message) {
// 返回
return "我被降级了";
}
}
效果:当调用发生异常
负载均衡
实现
具体实现上,Dubbo 提供的是客户端负载均衡,即由 Consumer 通过负载均衡算法得出需要将请求提交到哪个 Provider 实例
负载均衡策略
目前 Dubbo 内置了如下负载均衡算法,用户可直接配置使用:
算法 | 特性 | 备注 |
random | 加权随机 | 默认算法,默认权重相同 |
roundrobin | 加权轮询 | 借鉴于 Nginx 的平滑加权轮询算法,默认权重相同 |
leastactive | 最少活跃优先 + 加权随机 | 背后是能者多劳的思想 |
shortestresponse | 最短响应优先 + 加权随机 | 更加关注响应速度 |
consistenthash | 一致性 Hash | 确定的入参,确定的提供者,适用于有状态请求 |
注解
// 调用者通过 loadbalance 选取策略
@DubboReference(check = false,loadbalance = "consistenthash")
// 也可以控制在方法级别
@DubboReference(check = false,methods = {@Method(name = "方法名",loadbalance = "consistenthash")})
// 提供者通过 weight 设置权重
// 这个也有loadbalance 参数,但是我具体没测
@DubboService(loadbalance = "roundrobin",weight = 5)
集群容错
使用场景
多个服务器部署同一集群中,运行同一应用程序,如果一台服务器出现故障,其他服务器将接管负载,确保应用程序对用户仍然可用。
实现
集群容错常见策略有以下几种:
- failover:失败自动切换,默认值;当调用失败时,自动切换到另一个可用的服务提供者进行重试,重试次数由 **retries **参数指定
- failfast:快速失败;当调用失败时,立即报错,不进行任何重试操作;
- failsafe:失败安全;当调用失败时,直接忽略错误,不进行任何处理;
- failback:失败自动恢复。当调用失败时,记录失败的请求并异步重试,重试次数由 **retries **参数指定。
- forking:并行调用多个服务器,只要有一个成功即返回,
- broadcast:广播调用所有提供者,任意一个报错则报错
// 指定消费者端的集群容错策略
@DubboReference(check = false,cluster = "failover")
// 指定服务提供者端的集群容错策略。
@DubboService(cluster = "roundrobin")
拓展应用篇
所谓拓展都是利用Dubbo自身的SPI机制来实现的
自定义事件机制
这个不是SPI机制,但我觉得也算是一种拓展,就放过来了,虽然完全可以被下面的过滤器取代
在调用之前、调用之后、出现异常时,会触发 oninvoke
、onreturn
、onthrow
三个事件,可以配置当事件发生时,通知哪个类的哪个方法
自定义拓展类
在consumer中新建一个文件夹event,新建一个接口 TestEvent
public interface TestEvent {
//必须具有与真实的被调用方法相同的入参列表
void oninvoke(String test);
//2.1 至少要有一个入参且第一个入参必须与被调用方法的返回类型相同,接收返回结果
//2.2 可以有多个参数,多个参数的情况下,第一个为返回结果,后面都是入参
String onreturn(String result,String test);
//3.1 至少要有一个入参且第一个入参类型为Throwable或其子类
//3.2 可以有多个参数,多个参数的情况下,第一个为Throwable,后面都是入参
void onthrow(Throwable throwable,String test);
}
新建一个实现类,并注入到spring容器 TestEventImpl
@Slf4j
@Component("testEvent")
public class TestEventImpl implements TestEvent{
@Override
public void oninvoke(String test) {
log.info("test方法调用前");
}
@Override
public String onreturn(String result, String test) {
log.info("test方法调用后");
return null;
}
@Override
public void onthrow(Throwable throwable, String test) {
log.info("test方法异常时",throwable);
}
}
生效方法
我们在调用者注解上配置即可:
// 指定需要监听的方法
// testEvent:是那个实现类注入到spring容器中的bean名称
@DubboReference(check = false,methods = {@Method(name = "test",oninvoke = "testEvent.oninvoke", onreturn = "testEvent.onreturn", onthrow = "testEvent.onthrow")})
效果:
自定义过滤器
面向切面编程都知道了,这同样是如此
自定义拓展类
在consumer中新建一个文件夹filter,专门来放拓展类
新增自定义过滤器类 MyDubboFilter实现Filter 接口
public class MyDubboFilter implements Filter {
private final static Logger log= LoggerFactory.getLogger(MyDubboFilter.class);
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
log.info("dubbo 方法执行前.....");
Result result = invoker.invoke(invocation);
log.info("dubbo 方法执行后.....");
return result;
}
}
在资源文件夹resources中新建META-INF文件夹,然后在其中新建dubbo文件夹
新建一个名为:org.apache.dubbo.rpc.Filter 的文件,文件内容如下:
格式为: 自定义过滤器名称=自定义过滤器类的全路径
myFilter=cn.colins.consumer.filter.MyDubboFilter
现在整体结构如下:
生效方式
此时我们的过滤器已经定义完了,现在需要他生效,通常有两种方式:
服务级别:针对暴露的服务在注解上加上filter参数,如:
// 调用方注解 配置
@DubboReference(check = false,filter = "myFilter")
// 提供方注解 配置
@DubboService(filter = "myFilter")
// filter值 就是自定义过滤器的名称
// 可以配置多个用,隔开 如:"xxx,yyy"
// 先后顺序就是加载的执行顺序 也可以让某个过滤器不执行 如:"xxx,yyy,-ccc" ccc过滤器不生效
以上配置只能针对注解所代表的某个服务接口
全局级别:在实现类上打上激活注解,如:
@Activate(group = {CommonConstants.CONSUMER,CommonConstants.PROVIDER})
@Activate()怎么用的,先别管,先这么加着,后面再说
@Activate(group = {CommonConstants.CONSUMER,CommonConstants.PROVIDER})
public class MyDubboFilter implements Filter {
private final static Logger log= LoggerFactory.getLogger(MyDubboFilter.class);
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
log.info("dubbo 方法执行前.....");
Result result = invoker.invoke(invocation);
log.info("dubbo 方法执行后.....");
return result;
}
}
效果:
自定义负载均衡
虽然已经内置了好几种策略了,但是万一出现特殊场景呢是吧
自定义拓展类
同理新建一个类MyLoadBalance 实现LoadBalance 接口
public class MyLoadBalance implements LoadBalance {
private final static Logger log= LoggerFactory.getLogger(MyLoadBalance.class);
@Override
public <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException {
log.info("自定义负载均衡");
// 为了测试无脑取第一个
return invokers.get(0);
}
}
在META-INF/dubbo下新建一个文本文件 org.apache.dubbo.rpc.cluster.LoadBalance 内容如下:
myLoadBalance=myFilter=cn.colins.consumer.filter.MyLoadBalance
生效方式
全局的方式和上述一样加入激活注解@Activate,这里就不再说明了,服务级别的则是修改一下参数:
// 调用方注解 配置
@DubboReference(check = false,loadbalance= "myLoadBalance")
// 提供方注解 配置
@DubboService(loadbalance= "myLoadBalance")
效果: 注意需要启动多个提供者,只有一个的话不需要负载所以没效果
自定义服务引用监听
当有服务引用时或者服务引用被销毁时,触发该事件。用在调用方
自定义拓展类
在调用者工程下
新建一个MyListener类,实现InvokerListener接口
public class MyListener implements InvokerListener {
private final static Logger log= LoggerFactory.getLogger(MyListener.class);
@Override
public void referred(Invoker<?> invoker) throws RpcException {
log.info("当有服务引用时被调用: {}",invoker.getUrl());
}
@Override
public void destroyed(Invoker<?> invoker) {
log.info("当服务引用被销毁时被调用: {}",invoker.getUrl());
}
}
在META-INF/dubbo下新建一个文本文件org.apache.dubbo.rpc.InvokerListener内容如下:
myListener=cn.colins.consumer.filter.MyListener
生效方式
全局的方式和上述一样加入激活注解@Activate,这里就不再说明了,服务级别的则是修改一下参数:
// 调用方注解 添加listener参数
@DubboReference(check = false,protocol = "dubbo",listener = "myListener")
效果: 重启提供方查看效果
自定义服务暴露监听
这个和上面那个相辅相成,上面那个针对调用方,这个针对提供方,有服务暴露的时候或者有服务销毁的时候触发调用
自定义拓展类
注意:以上都是在调用者工程下新建的拓展类,这里需要在提供者工程下新建
同样是新建一个filter文件夹,然后新建一个MyExporterListener实现ExporterListener接口
@Activate(group = CommonConstants.PROVIDER)
public class MyExporterListener implements ExporterListener {
private final static Logger log= LoggerFactory.getLogger(MyExporterListener.class);
@Override
public void exported(Exporter<?> exporter) throws RpcException {
log.info("有服务暴露了:{} ",exporter.getInvoker().getUrl());
}
@Override
public void unexported(Exporter<?> exporter) {
log.info("有服务取消暴露了:{} ",exporter.getInvoker().getUrl());
}
}
老规矩,在META-INF/dubbo下新建一个文本文件org.apache.dubbo.rpc.ExporterListener内容如下:
myExListener=cn.colins.producer.filter.MyExporterListener
生效方式
一样是有全局加载和服务加载两种,上述已经加上了@Activate注解是全局的,服务加载方式为:
// 提供方注解 添加listener参数
@DubboService(listener = "myExListener")
效果:
自定义路由
为什么有了负载均衡还需要自定义路由?因为默认情况下负载均衡是对所有提供方的均衡调用,假设我现在有个场景需要根据条件调用里面某一部分的提供方,这就不是负载均衡能控制的了
自定义拓展类
在调用方工程实现,都是在filter文件夹下
我们先新建一个类MyRouter继承AbstractRouter
public class MyRouter extends AbstractRouter {
public MyRouter(URL url){
setUrl(url);
}
private final static Logger log= LoggerFactory.getLogger(MyRouter.class);
@Override
public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException {
log.info("url:{}",url);
// 根据已有条件过滤invokers 返回即可
// 返回的invokers 就会走负载策略
invokers.stream().filter(invoker->{
log.info("Provider id: {}",invoker.getUrl().getHost());
log.info("Provider port: {}",invoker.getUrl().getPort());
log.info("Provider protocol: {}",invoker.getUrl().getProtocol());
log.info("Provider methods : {}",invoker.getUrl().getParameter("methods", Collections.emptyList()));
log.info("Provider Parameter: {}",invoker.getUrl().getParameters());
return true;
}).collect(Collectors.toList());
return invokers;
}
}
然后新建一个MyRouterFactory实现RouterFactory
public class MyRouterFactory implements RouterFactory {
@Override
public Router getRouter(URL url) {
return new MyRouter(url);
}
}
主要SPI机制加载的是这个工程
老规矩,在META-INF/dubbo下新建一个文本文件org.apache.dubbo.rpc.cluster.RouterFactory内容如下:
myRouter=cn.colins.consumer.filter.MyRouterFactory
生效方式
这个生效和之前的不太一样,需要在配置文件中加:
consumer:
router: myRouter
整体如下:
效果: 说明走了我们的路由
自定义集群策略
自带的集群策略我们之前说了,有以下几种:
- failover:失败自动切换,默认值;当调用失败时,自动切换到另一个可用的服务提供者进行重试,重试次数由 **retries **参数指定
- failfast:快速失败;当调用失败时,立即报错,不进行任何重试操作;
- failsafe:失败安全;当调用失败时,直接忽略错误,不进行任何处理;
- failback:失败自动恢复。当调用失败时,记录失败的请求并异步重试,重试次数由 **retries **参数指定。
- forking:并行调用多个服务器,只要有一个成功即返回,
- broadcast:广播调用所有提供者,任意一个报错则报错
要是没满意的咋整,自定义呗
自定义拓展类
还是在 调用方 工程下测试
在filter文件夹下新增MyCluster实现Cluster接口
public class MyCluster implements Cluster {
private final static Logger log= LoggerFactory.getLogger(MyCluster.class);
@Override
public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
return new AbstractClusterInvoker<T>(directory) {
// Invocation 调用方法相关信息
// invokers 服务提供者集合
// loadbalance 负载均衡策略
public Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
log.info("这是我自定义的集群策略");
// 只是测试无脑选第一个
return invokers.get(0).invoke(invocation);
}
};
}
}
老规矩,在META-INF/dubbo下新建一个文本文件org.apache.dubbo.rpc.cluster.Cluster内容如下:
myCluster=cn.colins.consumer.filter.MyCluster
生效方式
全局的还是和之前一样加激活注解,服务级别的如下:
@DubboReference(check = false,cluster = "myCluster")
效果: 使用需要注意上下文参数的传递(这里没管就丢失了)
注解说明篇
只列举本系列中使用过得,常用的,并不是全部
DubboReference
调用方注解
- interfaceClass:指定服务接口的 Class 对象,表示该服务实现的接口类型。
- interfaceName:指定服务接口的名称,与
interfaceClass
参数作用相同。 - version: 服务版本,与服务提供者的版本一致
- group: 服务分组,当一个接口有多个实现,可以用分组区分,必需和服务提供方一致
- timeout: 服务方法调用超时时间(毫秒)
- retries: 远程服务调用重试次数,不包括第一次调用,不需要重试请设为0
- loadbalance: 负载均衡策略,可选值: random,roundrobin,leastactive,分别表 示:随机,轮循,最少活跃调用
- async: 是否异步执行,不可靠异步,只是忽略返回值,不阻塞执行线程
- check: 启动时检查提供者是否存在,true 报错, false 忽略
- stub: 服务接口客户端本地代理类名,用于在客 户端执行本地逻辑,如本地缓存等,该本地代理类的构造函数必须允许传入远程代理对象
- mock: 服务接口调用失败 Mock 实现类名
- validation: 是否启用 JSR303 标准注解验证,如果启用,将对方法参数上的注解进行校验
- registry: 从指定注册中心注册获取服务列表,在多 个注册中心时使用,值为 dubbo:registry的 id 属性,多个注册中 心 ID 用逗号分隔
- actives: 每服务消费者每服务每方法最大并发调用数
- cluster: 集群方式,可选: failover/failfast/failsafe/failback/forking (failover)
- filter: 服务消费方远程调用过程拦截器名称,多个名称用逗号分隔
- listener: 服务消费方引用服务监听器名称,多个名称用逗号分隔
- protocol: 只调用指定协议的服务提供方,其它协议忽略。
- methods:指定服务接口中每个方法的详细信息,包括超时时间、重试次数、参数类型、返回值类型等。
DubboService
提供方注解
- interfaceClass:指定服务接口的 Class 对象,表示该服务实现的接口类型。
- interfaceName:指定服务接口的名称,与
interfaceClass
参数作用相同。 - version:指定当前服务接口的版本号,默认为空字符串 “”。
- group:指定当前服务接口的分组,同样默认为空字符串 “”。
- timeout:指定服务调用超时时间,单位为毫秒,默认为 0 表示不设置超时时间。
- loadbalance:指定负载均衡策略,可选值有 “random”、“roundrobin” 和 “leastactive” 等,默认为 “random”。
- retries:指定失败重试次数,即当某个服务调用失败时,是否自动重试,默认为 2。
- actives:指定客户端最大并发请求数,即同时可以处理的请求个数,默认为 0 不限制。
- executes:指定线程池大小,即服务提供者处理请求的线程数,默认为 0 不启用。
- parameters:指定一些额外的参数,如 dubbo 协议的一些配置参数。
- mock:指定当服务调用失败时所返回的默认值,通常用于服务降级处理。
- stub:指定本地存根类,用于将部分调用逻辑在客户端执行,从而可以减轻服务端的压力。
- cluster:指定集群容错模式,可选值有 “failover”、“failfast”、“failsafe”、“failback” 和 “forking” 等,默认为 “failover”。
- validation:指定参数校验逻辑是否开启,可选值有 “true” 或 “false”,默认为 “false”。
- weight:指定服务权重,用于负载均衡,数值越大表示权重越高。
- accesslog:指定 Dubbo 服务访问日志的输出文件路径,支持使用占位符 ${…} 指定日期格式等。
- async:指定是否异步调用接口方法,可选值有 “true” 或 “false”,默认为 “false”。
- protocol:指定服务协议的名称和端口号,默认为 Dubbo 协议,端口号为 20880。
- filter:用于指定过滤器
- listener:用于指定监听器
- methods:指定服务接口中每个方法的详细信息,包括超时时间、重试次数、参数类型、返回值类型等。
- registry:指定注册中心的地址和协议,默认为 ZooKeeper 协议。Dubbo 支持多种注册中心,如 ZooKeeper、Redis、Consul 等。
- monitor:指定监控中心的地址和协议,默认不开启。Dubbo 监控中心支持多种协议,如 Dubbo 协议、JMX 协议等。
- token:权限控制,true是则是随机token令牌,使用UUID生成
Activate
- group():默认为空字符串数组。可指定组名,用于将多个注解进行分组,之前常用CommonConstants.CONSUMER,CommonConstants.PROVIDER 代表在调用方和提供方哪一方生效,也就是DubboReference和DubboService代表的一方
- value():默认为空字符串数组。可用于指定该注解所需激活的扩展点名称。(遇到什么才被激活)
- order():默认为0。可用于指定该注解所对应的扩展点在其它同类扩展点中的执行顺序。值越小,则越先执行。
Method
- name:Dubbo服务方法的名称,默认为空字符串。如果不指定,系统将使用接口方法名作为Dubbo服务方法名。
- retries:Dubbo服务方法的重试次数,默认为0。如果该值大于0,当Dubbo服务调用失败时,Dubbo框架会自动进行重试,最多重试指定次数。
- loadbalance:Dubbo服务方法的负载均衡策略,默认为"random"。Dubbo框架支持多种负载均衡策略,如随机负载均衡、轮询负载均衡等,可以根据需要指定。
- async:是否异步执行该Dubbo服务方法,默认为false。如果该值为true,Dubbo框架会将该Dubbo服务方法调用转化为异步调用。
- timeout:Dubbo服务方法的超时时间(毫秒),默认为0。如果该值为0,则表示不设定超时时间。
- actives:服务消费者端限制该Dubbo服务并发调用数,默认为0(不限制)。
- executes:该Dubbo服务提供者端限制该Dubbo服务并发执行数,默认为0(不限制)。
- oninvoke:方法调用前事件方法
- onreturn:方法调用后事件方法
- onthrow:方法调用异常事件方法
- validation:是否参数校验
Argument
- index:默认值为-1。用于指定该参数在Dubbo服务方法中的索引位置。如果不指定,则系统会自动推断。
- type:默认值为空字符串。用于指定该参数的类型。如果不指定,则系统会自动推断。
- callback:默认值为false。表示该参数是否为回调参数。
\