文章目录
前言
Apache Dubbo 是一款 RPC 服务开发框架,用于解决微服务架构下的服务治理与通信问题,官方提供了 Java、Golang 等多语言 SDK 实现。使用 Dubbo 开发的微服务原生具备相互之间的远程地址发现与通信能力, 利用 Dubbo 提供的丰富服务治理特性,可以实现诸如服务发现、负载均衡、流量调度等服务治理诉求。Dubbo 被设计为高度可扩展,用户可以方便的实现流量拦截、选址的各种定制逻辑。
在云原生时代,Dubbo 相继衍生出了 Dubbo3、Proxyless Mesh 等架构与解决方案,在易用性、超大规模微服务实践、云原生基础设施适配、安全性等几大方向上进行了全面升级。
本文将通过三个方面进行dubbo的应用,包括基础调用、异步调用、泛化调用
zookeeper安装与使用
注册中心我们选择zookeeper,我们下载好zookeeper后,以windows方式启动zookeeper为例
1、下载zookeeper,下载地址,官网,下载带bin的压缩包,如apache-zookeeper-3.6.4-bin.tar.gz
2、下载完后解压,在conf目录下的zoo_sample.cfg文件复制一份为zoo.cfg,修改dataDir配置,例如dataDir=D:\java\apache-zookeeper-3.6.4-bin\data
完整配置如下
# The number of milliseconds of each tick
tickTime=2000
# The number of ticks that the initial
# synchronization phase can take
initLimit=10
# The number of ticks that can pass between
# sending a request and getting an acknowledgement
syncLimit=5
# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just
# example sakes.
dataDir=D:\java\apache-zookeeper-3.6.4-bin\data
# the port at which the clients will connect
clientPort=2181
# the maximum number of client connections.
# increase this if you need to handle more clients
#maxClientCnxns=60
#
# Be sure to read the maintenance section of the
# administrator guide before turning on autopurge.
#
# http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance
#
# The number of snapshots to retain in dataDir
#autopurge.snapRetainCount=3
# Purge task interval in hours
# Set to "0" to disable auto purge feature
#autopurge.purgeInterval=1
## Metrics Providers
#
# https://prometheus.io Metrics Exporter
#metricsProvider.className=org.apache.zookeeper.metrics.prometheus.PrometheusMetricsProvider
#metricsProvider.httpPort=7000
#metricsProvider.exportJvmInfo=true
3、打开bin目录下的zkServer.cmd,启动
启动成功如图
一、基础调用
1、基于dubbo api调用
引入maven依赖
<dubbo.version>3.1.0</dubbo.version>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>${dubbo.version}</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-x-discovery</artifactId>
<version>4.0.1</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.13</version>
</dependency>
定义服务接口
服务接口 Dubbo 中沟通消费端和服务端的桥梁。
public interface GreetingsService {
String sayHi(String name);
}
定义服务端的实现
定义了服务接口之后,可以在服务端这一侧定义对应的实现,这部分的实现相对于消费端来说是远端的实现,本地没有相关的信息。
public class GreetingsServiceImpl implements GreetingsService {
@Override
public String sayHi(String name) {
return "hi, " + name;
}
}
服务端发布服务
在实现了服务之后,本小节将通过 Dubbo 的 API 在网络上发布这个服务。
public class ProviderApplication {
public static void main(String[] args) throws IOException {
// 设置应用名称
ApplicationConfig config = new ApplicationConfig();
config.setName("ProviderApplication");
// 连接注册中心
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setAddress("127.0.0.1:2181");
registryConfig.setProtocol("zookeeper");
// 设置协议
ProtocolConfig protocolConfigDubbo = new ProtocolConfig();
protocolConfigDubbo.setPort(12881);
// 设置dubbo的协议
protocolConfigDubbo.setName("dubbo");
// 服务提供者暴露服务
ServiceConfig<GreetingsService> serviceServiceConfig = new ServiceConfig<>();
serviceServiceConfig.setApplication(config); // 设置应用名称
serviceServiceConfig.setRegistry(registryConfig); // 设置注册中心
serviceServiceConfig.setProtocol(protocolConfigDubbo); // 设置DUBBO协议
serviceServiceConfig.setInterface(GreetingsService.class); // 设置接口
serviceServiceConfig.setRef(new GreetingsServiceImpl()); // 设置具体实现类
serviceServiceConfig.export(); // 暴露和注册服务
System.in.read();
}
}
消费端订阅并调用
对于消费端,可以通过 Dubbo 的 API 可以进行消费端订阅。
public class ConsumerApplication {
public static void main(String[] args) throws IOException {
//设置应用名称
ApplicationConfig config = new ApplicationConfig();
config.setName("ConsumerApplication");
//连接注册中心
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setAddress("127.0.0.1:2181"); //本地虚拟机的地址
registryConfig.setProtocol("zookeeper");
ReferenceConfig<GreetingsService> referenceConfig = new ReferenceConfig<>();
referenceConfig.setApplication(config);
referenceConfig.setRegistry(registryConfig);
referenceConfig.setInterface(GreetingsService.class);
referenceConfig.setProtocol("dubbo");
GreetingsService greetingsService = referenceConfig.get();
String msg = greetingsService.sayHi("dubbo");
System.out.println("Receive result ======> " + msg); //具体的调用
System.in.read();
}
}
启动完成后,打印如下,表示消费成功
2、集成 Springboot 调用
maven 依赖,除了以上依赖,还需引用spring的相关依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.0</version>
</parent>
<nacos-spring-boot.version>0.2.12</nacos-spring-boot.version>
<nacos.client.version>2.1.0</nacos.client.version>
<!-- spring tarter -->
<!--Spring Boot核心启动器,包含了自动配置、日志和YAML-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- 第三方starter -->
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>nacos-config-spring-boot-starter</artifactId>
<version>${nacos-spring-boot.version}</version>
</dependency>
<!-- 第三方依赖包 -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>${dubbo.version}</version>
</dependency>
定义实体类
public class Student implements Serializable {
private static final long serialVersionUID = -1L;
private String name;
private Integer age;
public Student() {
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return this.age;
}
public void setAge(Integer age) {
this.age = age;
}
public String toString() {
return "Student{name='" + this.name + '\'' + ", age=" + this.age + '}';
}
}
定义服务接口
public interface GreetingsProvider {
String sayHi(String name);
String sayHello(Student student);
Student sayHello(String name);
}
定义服务端的实现
@DubboService
public class GreetingsProviderImpl implements GreetingsProvider{
@Override
public String sayHi(String name) {
return "hi, " + name;
}
@Override
public String sayHello(Student student) {
return "hello : student :" + JSON.toJSONString(student);
}
@Override
public Student sayHello(String name) {
Student student = new Student();
student.setName(name);
student.setAge(5);
return student;
}
}
服务端发布服务
- yml配置
dubbo: application: name: dubbo-springboot-start-provider protocol: name: dubbo port: -1 registry: address: zookeeper://127.0.0.1:2181 provider: timeout: 30000
- 服务端启动类
@SpringBootApplication @EnableDubbo public class ProviderApplication { public static void main(String[] args) { SpringApplication.run(ProviderApplication.class, args); } }
- 启动ProviderApplication
消费者订阅并调用
- yml配置
dubbo: application: name: dubbo-springboot-start-consumer protocol: name: dubbo port: -1 registry: address: zookeeper://127.0.0.1:2181 scan: basePackages: org.sjl consumer: timeout: 30000
- 消费者消费任务
@Component public class ConsumerTask implements CommandLineRunner { @DubboReference private GreetingsProvider greetingsProvider; @Override public void run(String... args) throws Exception { String result = greetingsProvider.sayHi("dubbo"); System.out.println("Receive result ======> " + result); } }
- 消费端启动类
@SpringBootApplication @EnableDubbo public class ConsumerApplication { public static void main(String[] args) { SpringApplication.run(ConsumerApplication.class, args); } }
- 消费端启动服务,消费成功
二、异步调用
maven依赖和yml配置保持和集成 Springboot 调用一致
1、使用 CompletableFuture 签名的接口
定义服务接口
public interface AsyncProvider {
CompletableFuture<String> sayHelloAsync(String name);
}
服务端的实现
通过 return CompletableFuture.supplyAsync() ,业务执行已从 Dubbo 线程切换到业务线程,避免了对 Dubbo 线程池的阻塞。
注意接口的返回类型是 CompletableFuture
@DubboService
public class AsyncProviderImpl implements AsyncProvider {
@Override
public CompletableFuture<String> sayHelloAsync(String name) {
return CompletableFuture.supplyAsync(() -> {
System.out.println(name);
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
}
return "async response from provider.";
});
}
}
消费者消费任务
输出顺序为1、2、3
@Component
public class AsyncTask implements CommandLineRunner {
@DubboReference
private AsyncProvider asyncProvider;
@Override
public void run(String... args) throws Exception {
// 调用直接返回CompletableFuture
CompletableFuture<String> future = asyncProvider.sayHelloAsync("async call request");
// 增加回调
future.whenComplete((v, t) -> {
if (t != null) {
// 异常处理
t.printStackTrace();
} else {
// 2 任务完成前置处理
System.out.println("Response: " + v);
}
});
// 1
System.out.println("Executed before response return.");
// 3 任务完成结果
System.out.println("future get " + future.get());
}
}
服务端和消费者启动类启动
消费结果如图
2、使用 AsyncContext
定义服务接口
public interface AsyncProvider {
String sayHiAsync(String name);
}
服务端的实现
Dubbo 提供了一个类似 Servlet 3.0 的异步接口AsyncContext,在没有 CompletableFuture 签名接口的情况下,也可以实现 Provider 端的异步执行
@DubboService
public class AsyncProviderImpl implements AsyncProvider {
@Override
public String sayHiAsync(String name) {
final AsyncContext asyncContext = RpcContext.startAsync();
new Thread(() -> {
// 如果要使用上下文,则必须要放在第一句执行
asyncContext.signalContextSwitch();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 写回响应
asyncContext.write("Hello " + name + ", response from provider.");
}).start();
return null;
}
}
消费者消费任务
@Component
public class AsyncTask2 implements CommandLineRunner {
@DubboReference
private AsyncProvider asyncProvider;
@Override
public void run(String... args) throws Exception {
// 调用返回结果
String asyncResult = asyncProvider.sayHiAsync("async call request");
System.out.println("asyncResult = " + asyncResult);
}
}
服务端和消费者启动类启动
消费结果如图
3、使用 RpcContext 实现消费端异步调用
定义服务接口
public interface AsyncProvider {
String sayHello(String name);
}
服务端的实现
@DubboService
public class AsyncProviderImpl implements AsyncProvider {
@Override
public String sayHello(String name) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
}
return "sync response from provider";
}
}
消费者消费任务
@Component
public class AsyncTask3 implements CommandLineRunner {
@DubboReference
private AsyncProvider asyncProvider;
@Override
public void run(String... args) throws Exception {
// 拿到调用的Future引用,当结果返回后,会被通知和设置到此Future
CompletableFuture<String> future = RpcContext.getServiceContext().asyncCall(
() -> asyncProvider.sayHello("consumer async")
);
// 期间可以处理其它业务
System.out.println("consumer async:" + future.get());
}
}
服务端和消费者启动类启动
消费结果如图
异步的作用能让资源更充分利用,在对接口返回速度没有要求的情况下可以尝试使用异步处理
三、泛化调用
1、基于springboot集成的泛化调用
定义服务接口
public interface GreetingsProvider {
String sayHi(String name);
String sayHello(Student student);
Student sayHello(String name);
}
服务端的实现
@DubboService
public class GreetingsProviderImpl implements GreetingsProvider{
@Override
public String sayHi(String name) {
return "hi, " + name;
}
@Override
public String sayHello(Student student) {
return "hello : student :" + JSON.toJSONString(student);
}
@Override
public Student sayHello(String name) {
Student student = new Student();
student.setName(name);
student.setAge(5);
return student;
}
}
消费者消费任务
- 在设置 ReferenceConfig 时,使用 setGeneric(“true”) 来开启泛化调用
- 配置完 ReferenceConfig 后,使用 referenceConfig.get() 获取到 GenericService 类的实例
- 使用其 $invoke 方法获取结果
@Component
public class GeneralTask implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
// 创建注册中心配置
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setAddress("zookeeper://127.0.0.1:2181");
// 创建服务引用配置
ReferenceConfig<GenericService> referenceConfig = new ReferenceConfig<>();
// 设置接口
referenceConfig.setInterface("org.sjl.dubbo.GreetingsProvider");
referenceConfig.setRegistry(registryConfig);
// 重点:设置为泛化调用
// 注:不再推荐使用参数为布尔值的setGeneric函数
// 应该使用referenceConfig.setGeneric("true")代替
referenceConfig.setGeneric("true");
// 设置超时时间
referenceConfig.setTimeout(7000);
// 获取服务,由于是泛化调用,所以获取的一定是GenericService类型
GenericService genericService = referenceConfig.get();
// 使用GenericService类对象的$invoke方法可以代替原方法使用
// 第一个参数是需要调用的方法名
// 第二个参数是需要调用的方法的参数类型数组,为String数组,里面存入参数的全类名。
// 第三个参数是需要调用的方法的参数数组,为Object数组,里面存入需要的参数。
Object result = genericService.$invoke("sayHello", new String[]{"java.lang.String"}, new Object[]{"hello world"});
// 打印结果
Student student = JSONObject.parseObject(JSON.toJSONString(result), Student.class);
System.err.println("invokeSayHello1(return): " + student);
Student student1 = new Student();
student1.setName("hello");
student1.setAge(5);
Object result2 = genericService.$invoke("sayHello", new String[]{"org.sjl.model.Student"}, new Object[]{student1});
// 打印结果
System.err.println("invokeSayHello2(return): " + result2);
Object result3 = genericService.$invoke("sayHi", new String[]{"java.lang.String"}, new Object[]{"su"});
// 打印结果
System.err.println("invokeSayHello3(return): " + result3);
}
}
服务端和消费者启动类启动
消费结果如图
2、基于API的泛化调用
- 服务端不变
- 消费者消费任务
- 和springboot的区别是需要多注册ApplicationConfig
public class GenericConsumer {
public static void main(String[] args){
// 创建ApplicationConfig 比springboot需要多注册ApplicationConfig
ApplicationConfig applicationConfig = new ApplicationConfig();
applicationConfig.setName("generic-call-consumer");
// 创建注册中心配置
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setAddress("zookeeper://127.0.0.1:2181");
applicationConfig.setRegistry(registryConfig);
// 创建服务引用配置
ReferenceConfig<GenericService> referenceConfig = new ReferenceConfig<>();
// 设置接口
referenceConfig.setInterface("org.sjl.dubbo.GreetingsProvider");
referenceConfig.setApplication(applicationConfig);
// 重点:设置为泛化调用
// 注:不再推荐使用参数为布尔值的setGeneric函数
// 应该使用referenceConfig.setGeneric("true")代替
referenceConfig.setGeneric("true");
// 设置超时时间
referenceConfig.setTimeout(7000);
// 获取服务,由于是泛化调用,所以获取的一定是GenericService类型
GenericService genericService = referenceConfig.get();
// 使用GenericService类对象的$invoke方法可以代替原方法使用
// 第一个参数是需要调用的方法名
// 第二个参数是需要调用的方法的参数类型数组,为String数组,里面存入参数的全类名。
// 第三个参数是需要调用的方法的参数数组,为Object数组,里面存入需要的参数。
Object result = genericService.$invoke("sayHello", new String[]{"java.lang.String"}, new Object[]{"hello world"});
// 打印结果
Student student = JSONObject.parseObject(JSON.toJSONString(result), Student.class);
System.err.println("invokeSayHello1(return): " + student);
Student student1 = new Student();
student1.setName("hello");
student1.setAge(5);
Object result2 = genericService.$invoke("sayHello", new String[]{"org.sjl.model.Student"}, new Object[]{student1});
// 打印结果
System.err.println("invokeSayHello2(return): " + result2);
Object result3 = genericService.$invoke("sayHi", new String[]{"java.lang.String"}, new Object[]{"su"});
// 打印结果
System.err.println("invokeSayHello3(return): " + result3);
}
}
启动消费者,消费结果如图
总结
以上能满足日常开发的大部分场景,基础调用可以快捷进入开发、异步调用可以释放与有效利用资源、泛化调用可以不引入服务接口,同时可以进行动态注册,适合一些需要动态扩展的场景。利用好相关的应用,能够让系统更加健壮、有伸缩性。