本文目的是关于Dubbo纯注解版的使用以及注册中心采用nacos,所以在工程结构以及命名方面可能不是那么规范
首先我们看一下整体的工程结构,你可以简单的理解成有两个springboot工程,都引入了一个公共的模块或者公共包,而这个公共模块就是专门来放暴露的接口的
对应springboot工程使用dubbo非常简单,只需要引入dubbo依赖即可,但是最好注意一下版本(两个工程依赖都一致的)
org.springframework.boot
spring-boot-dependencies
2.7.12-SNAPSHOT
pom
import
org.apache.dubbo
dubbo-spring-boot-starter
2.7.12
org.apache.dubbo
dubbo-registry-nacos
2.7.12
学习使用的目的,所以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);
}
}
浏览器请求接口
这样简单的环境就搞定了,只需要注意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)
它们分别承担了不同的职责:
调用者: 修改原来的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 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 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 serverAttachment = RpcContext.getServerContext().getObjectAttachments();
log.info("ContextTask clientAttachment: " + JSON.toJSON(serverAttachment));
return result.toString();
}
private GenericService buildGenericService(String interfaceClass, String group, String version) {
ReferenceConfig reference = new ReferenceConfig();
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接口模块)
javax.validation
validation-api
2.0.1.Final
org.hibernate.validator
hibernate-validator
6.0.17.Final
在接口方法上加入校验注解
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协议通信,这时候又需要暴露出去与其他不同协议的系统对接,难不成每种协议都写一个接口?而且除此之外它还有其他好处
实现
这里以同时配置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 调用支持,所以要引入相关依赖
org.apache.dubbo
dubbo-rpc-rest
2.7.12
javax.ws.rs
javax.ws.rs-api
2.1.1
提供者服务修改
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 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"
如:
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)
使用场景
多个服务器部署同一集群中,运行同一应用程序,如果一台服务器出现故障,其他服务器将接管负载,确保应用程序对用户仍然可用。
实现
集群容错常见策略有以下几种:
// 指定消费者端的集群容错策略
@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 Invoker select(List> 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 List> route(List> 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
整体如下:
效果: 说明走了我们的路由
自带的集群策略我们之前说了,有以下几种:
要是没满意的咋整,自定义呗
还是在 调用方 工程下测试
在filter文件夹下新增MyCluster实现Cluster接口
public class MyCluster implements Cluster {
private final static Logger log= LoggerFactory.getLogger(MyCluster.class);
@Override
public Invoker join(Directory directory) throws RpcException {
return new AbstractClusterInvoker(directory) {
// Invocation 调用方法相关信息
// invokers 服务提供者集合
// loadbalance 负载均衡策略
public Result doInvoke(Invocation invocation, List> 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")
效果: 使用需要注意上下文参数的传递(这里没管就丢失了)
只列举本系列中使用过得,常用的,并不是全部
调用方注解
interfaceClass
参数作用相同。提供方注解
interfaceClass
参数作用相同。个人博客: 全是干货,相信不会让你失望