Dubbo 入门应用程序

Dubbo 是一款微服务框架,提供高性能 RPC 通信,服务发现,流量管理等服务治理能力,提供构建大规模微服务集群所需的整套解决方案。

本文讲述如何利用 Dubbo 快速构建一个完整的服务端 - 客户端程序,包括基于 XML,注解和 API 的方式实现一个 Dubbo 的 demo。

配置开发环境

本文使用 IntelliJ IDEA 作为 Dubbo 应用程序开发的 IDE。

安装 ZooKeeper

Dubbo 推荐使用 ZooKeeper (下文简称为 zk)作用注册中心,为简单起见,本文搭建一个单机版的 zk 以供 Dubbo 应用程序使用。有关 zk 集群的搭建步骤,可参考 ZooKeeper的安装与部署

在 zk 官网下载最新版本的 zk 二进制安装包,解压,将 conf 目录下 zoo_sample.cfg 文件复制为 zoo.cfg,然后在 zk 主目录执行命令,将 zk 启动起来。此时 zk 会监听默认的 2181 端口。

1
bin/zkServer.sh start

下载源代码

本文的 Dubbo 入门应用程序很简单,服务端接收到客户端请求,然后直接将消息返回给客户端。应用程序可以通过 XML、注解和 API 三种方式编写。

本文示例应用程序在 https://github.com/zonghaishang/dubbo-samples 可以下载。

下载后,使用 IDEA 打开,导入 pom.xml 所需的依赖后显示如下:

基于 XML 实现

Echo 服务端

定义服务端接口 EchoService ,暴露出来以供客户端使用:

1
2
3
4
5
6
7
package com.alibaba.dubbo.samples.echo.api;

public interface EchoService {

String echo(String message);

}

接下来是接口的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.alibaba.dubbo.samples.echo.impl;

import com.alibaba.dubbo.rpc.RpcContext;
import com.alibaba.dubbo.samples.echo.api.EchoService;

import java.text.SimpleDateFormat;
import java.util.Date;

public class EchoServiceImpl implements EchoService {

public String echo(String message) {
String now = new SimpleDateFormat("HH:mm:ss").format(new Date());
System.out.println("[" + now + "] Hello " + message
+ ", request from consumer: " + RpcContext.getContext().getRemoteAddress());
return message;
}
}

为了让 EchoService 对外正常提供服务,还需要一些额外的配置,通过 spring 配置声明以暴露服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">

<!-- 服务提供方应用名称, 方便用于依赖跟踪 -->
<dubbo:application name="echo-provider"/>

<!-- 使用本地zookeeper作为注册中心 -->
<dubbo:registry address="zookeeper://127.0.0.1:2181"/>

<!-- 只用Dubbo协议并且指定监听端口 20880 -->
<dubbo:protocol name="dubbo" port="20880"/>

<!-- 通过xml方式配置为bean, 让spring托管和实例化 -->
<bean id="echoService" class="com.alibaba.dubbo.samples.echo.impl.EchoServiceImpl"/>

<!-- 声明服务暴露的接口,并暴露服务 -->
<dubbo:service interface="com.alibaba.dubbo.samples.echo.api.EchoService" ref="echoService"/>

</beans>

最后是编写代码加载 spring 配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.alibaba.dubbo.samples.echo;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class EchoProvider {

public static void main(String[] args) throws Exception {
// #1 指定服务暴露配置文件
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"spring/echo-provider.xml"});
// #2 启动spring容器并暴露服务
context.start();

System.in.read();
}
}

Echo 服务端示例代码,以及各代码所在目录位置如下图所示:

启动 EchoProvider ,控制台输出:

[08/11/20 09:50:38:038 CST] main INFO zookeeper.ZookeeperRegistry: [DUBBO] Notify urls for subscribe url provider://192.168.3.3:20880/com.alibaba.dubbo.samples.echo.api.EchoService

打开一个新的终端,输入命令连接服务端:

1
telnet localhost 20880

然后输入执行接口调用命令:

1
dubbo>invoke com.alibaba.dubbo.samples.echo.api.EchoService.echo("leehao.me")

输出:

“leehao.me”
elapsed: 0 ms.

说明已正常调用了服务端口的接口。

Echo 客户端

上面我们使用 telnet 命令调用服务端接口,现在我们编写 Dubbo 客户端来调用服务端接口。

客户端通过 spring 配置引用服务端接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">

<!-- 服务消费方应用名称, 方便用于依赖跟踪 -->
<dubbo:application name="echo-consumer"/>

<!-- 使用本地zookeeper作为注册中心 -->
<dubbo:registry address="zookeeper://127.0.0.1:2181"/>

<!-- 指定要消费的服务 -->
<dubbo:reference id="echoService" check="false" interface="com.alibaba.dubbo.samples.echo.api.EchoService"/>

</beans>

主要配置包括,使用 zk 作为注册中心,需要消费的服务接口,等。

然后,编写代码,加载 spring 配置,调用远程接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.alibaba.dubbo.samples.echo;

import com.alibaba.dubbo.samples.echo.api.EchoService;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class EchoConsumer {

public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"spring/echo-consumer.xml"});
context.start();
EchoService echoService = (EchoService) context.getBean("echoService"); // get remote service proxy

String status = echoService.echo("Hello leehao!");
System.out.println("echo result: " + status);
}
}

启动 EchoConsumer,控制台输出:

echo result: Hello leehao!

基于注解实现

Dubbo 支持使用注解的方式来暴露服务接口。

Echo 服务端

在服务接口添加 @Service 注解,即可以暴露服务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.alibaba.dubbo.samples.echo.impl;

import com.alibaba.dubbo.config.annotation.Service;
import com.alibaba.dubbo.rpc.RpcContext;
import com.alibaba.dubbo.samples.echo.api.EchoService;

import java.text.SimpleDateFormat;
import java.util.Date;


@Service
public class EchoServiceImpl implements EchoService {

public String echo(String message) {
String now = new SimpleDateFormat("HH:mm:ss").format(new Date());
System.out.println("[" + now + "] Hello " + message
+ ", request from consumer: " + RpcContext.getContext().getRemoteAddress());
return message;
}
}

然后,由 dubbo 将这个服务接口提升为 spring 容器的 bean,并负责配置的初始化和服务暴露:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import com.alibaba.dubbo.config.ProtocolConfig;
import com.alibaba.dubbo.config.ApplicationConfig;
import com.alibaba.dubbo.config.ProviderConfig;
import com.alibaba.dubbo.config.RegistryConfig;
import com.alibaba.dubbo.config.spring.context.annotation.EnableDubbo;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

public class AnnotationProvider {
public static void main(String[] args) throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ProviderConfiguration.class);
context.start();
System.in.read();
}

@Configuration
// #1 指定扫描服务的位置
@EnableDubbo(scanBasePackages = "com.alibaba.dubbo.samples.echo")
static class ProviderConfiguration {
@Bean
public ProviderConfig providerConfig() {
return new ProviderConfig();
}

@Bean
public ApplicationConfig applicationConfig() {
ApplicationConfig applicationConfig = new ApplicationConfig();
applicationConfig.setName("echo-annotation-provider");
return applicationConfig;
}

@Bean
public RegistryConfig registryConfig() {
RegistryConfig registryConfig = new RegistryConfig();
// #2 使用zookeeper作为注册中心,同时给出注册中心ip和端口
registryConfig.setProtocol("zookeeper");
registryConfig.setAddress("localhost");
registryConfig.setPort(2181);
return registryConfig;
}

@Bean
public ProtocolConfig protocolConfig() {
ProtocolConfig protocolConfig = new ProtocolConfig();
// #3 默认服务使用dubbo协议,在20880监听服务
protocolConfig.setName("dubbo");
protocolConfig.setPort(20880);
return protocolConfig;
}
}
}

Echo 客户端

使用 @Reference 注解来标注需要消费的服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.alibaba.dubbo.samples.echo.refer;

import com.alibaba.dubbo.config.annotation.Reference;
import com.alibaba.dubbo.samples.echo.api.EchoService;

import org.springframework.stereotype.Component;

@Component
public class EchoConsumer {

@Reference
private EchoService echoService;

public String echo(String name) {
return echoService.echo(name);
}
}

然后定义基于注解的消费者的启动代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package com.alibaba.dubbo.samples.echo;

import com.alibaba.dubbo.config.ApplicationConfig;
import com.alibaba.dubbo.config.ConsumerConfig;
import com.alibaba.dubbo.config.RegistryConfig;
import com.alibaba.dubbo.config.spring.context.annotation.EnableDubbo;
import com.alibaba.dubbo.samples.echo.refer.EchoConsumer;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

public class AnnotationConsumer {

public static void main(String[] args) {
// #1 基于注解配置初始化spring上下文
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConsumerConfiguration.class);
context.start();
// #2 发起服务调用
EchoConsumer echoService = context.getBean(EchoConsumer.class);
String hello = echoService.echo("Hello world!");
System.out.println("result: " + hello);
}

@Configuration
// #3 指定要扫描的消费注解,会触发注入
@EnableDubbo(scanBasePackages = "com.alibaba.dubbo.samples.echo")
@ComponentScan(value = {"com.alibaba.dubbo.samples.echo"})
static class ConsumerConfiguration {
@Bean
public ApplicationConfig applicationConfig() {
ApplicationConfig applicationConfig = new ApplicationConfig();
applicationConfig.setName("echo-annotation-consumer");
return applicationConfig;
}

@Bean
public ConsumerConfig consumerConfig() {
return new ConsumerConfig();
}

@Bean
public RegistryConfig registryConfig() {
RegistryConfig registryConfig = new RegistryConfig();
// #4 使用zookeeper作为注册中心,同时给出注册中心ip和端口
registryConfig.setProtocol("zookeeper");
registryConfig.setAddress("localhost");
registryConfig.setPort(2181);
return registryConfig;
}
}
}

输出:

result: Hello world!

基于 API 实现

Dubbo 框架支持 API 的方式实现暴露服务和消费服务。

Echo 服务端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.alibaba.dubbo.samples.echo;

import com.alibaba.dubbo.config.ApplicationConfig;
import com.alibaba.dubbo.config.RegistryConfig;
import com.alibaba.dubbo.config.ServiceConfig;
import com.alibaba.dubbo.samples.echo.api.EchoService;
import com.alibaba.dubbo.samples.echo.impl.EchoServiceImpl;

import java.io.IOException;

public class EchoProvider {
public static void main(String[] args) throws IOException {
ServiceConfig<EchoService> service = new ServiceConfig<>();
service.setApplication(new ApplicationConfig("java-echo-provider"));
service.setRegistry(new RegistryConfig("zookeeper://127.0.0.1:2181"));
service.setInterface(EchoService.class);
service.setRef(new EchoServiceImpl());
service.export();
System.out.println("java-echo-provider is running.");
System.in.read();
}
}

Echo 客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.alibaba.dubbo.samples.echo;

import com.alibaba.dubbo.config.ApplicationConfig;
import com.alibaba.dubbo.config.ReferenceConfig;
import com.alibaba.dubbo.config.RegistryConfig;
import com.alibaba.dubbo.samples.echo.api.EchoService;

public class EchoConsumer {
public static void main(String[] args) {
ReferenceConfig<EchoService> reference = new ReferenceConfig<>();
// #1 设置消费方应用名称
reference.setApplication(new ApplicationConfig("java-echo-consumer"));
// #2 设置注册中心地址和协议
reference.setRegistry(new RegistryConfig("zookeeper://127.0.0.1:2181"));
// #3 指定要消费的服务接口
reference.setInterface(EchoService.class);
// #4 创建远程连接并做动态代理转换
EchoService greetingsService = reference.get();
String message = greetingsService.echo("Hello world!");
System.out.println(message);
}
}

输出:

Hello world!

参考资料