是五月呀!

第一个DUBBO例子

最近dubbo项目重新维护,让人忍不住想了解一下。

本文参考dubbo官方实例实现了一个简单的demo。使用Spring配置文件,zookeeper作为注册器。

1. 项目结构

  • hello-dubbo-api: 公共api接口,provider暴露此接口,consumer调用此接口
  • hello-dubbo-provider: 实现api中的接口,对外提供服务
  • hello-dubbo-consumer: 调用api中的接口

2.父pom依赖管理

首先在父pom中引入dependencyManagement和公共依赖:

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.damon4u.demo</groupId>
<artifactId>hello-dubbo</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>hello-dubbo-api</module>
<module>hello-dubbo-provider</module>
<module>hello-dubbo-consumer</module>
</modules>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java-version>1.7</java-version>
<junit.version>4.12</junit.version>
<spring.version>4.3.9.RELEASE</spring.version>
<org.slf4j.version>1.7.5</org.slf4j.version>
<logback.version>1.0.13</logback.version>
</properties>
<dependencyManagement>
<dependencies>
<!-- spring start -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId>
<version>${spring.version}</version>
<type>pom</type>
<!-- 注意:import scope只能用在dependencyManagement里面 -->
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.6.9</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
<!-- spring end -->
<!-- log start -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${org.slf4j.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>${logback.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${org.slf4j.version}</version>
<scope>runtime</scope>
</dependency>
<!-- log end -->
<!-- test start -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<!-- test end -->
</dependencies>
</dependencyManagement>
<dependencies>
<!-- log start -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<scope>runtime</scope>
</dependency>
<!-- log end -->
<!-- test start -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<!-- test end -->
</dependencies>
<build>
<finalName>hello-dubbo</finalName>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
<encoding>UTF-8</encoding>
<debug>true</debug>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.4.2</version>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
</plugins>
</build>
</project>

3. dubbo-api

新建子模块,作为api,不需要特别的pom依赖引入,只是定义一个简单的接口:

1
2
3
4
5
6
public interface DemoService {
/**
* sayHello
*/
String sayHello(String name);
}

4. dubbo-provider

新建子模块,作为服务提供者。
首先引入api,dubbo和zookeeper依赖,注意解决日志冲突:

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
<dependencies>
<!-- api start -->
<dependency>
<groupId>com.damon4u.demo</groupId>
<artifactId>hello-dubbo-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- api end -->
<!-- dubbo start -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.5.8</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.9</version>
<exclusions>
<exclusion>
<artifactId>slf4j-log4j12</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version>0.2</version>
</dependency>
<!-- dubbo end -->
</dependencies>

其中dubbo的依赖中包含了相关的Spring依赖,如果项目本身使用Spring容器,推荐单独指定Spring依赖。

provider需要实现api接口,对外提供服务:

1
2
3
4
5
6
7
8
9
10
public class DemoServiceImpl implements DemoService {
private static final Logger logger = LoggerFactory.getLogger(DemoServiceImpl.class);
@Override
public String sayHello(String name) {
System.out.println("[" + new SimpleDateFormat("HH:mm:ss").format(new Date()) + "] Hello " + name + ", request from consumer: " + RpcContext.getContext().getRemoteAddress());
return "Hello " + name + ", response form provider: " + RpcContext.getContext().getLocalAddress();
}
}

接下来是dubbo相关的Spring配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://code.alibabatech.com/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-2.5.xsd
http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<!-- 提供方应用信息,用于计算依赖关系 -->
<dubbo:application name="hello-dubbo-provider"/>
<!-- 使用zookeeper注册中心暴露发现服务地址 -->
<dubbo:registry address="zookeeper://10.235.132.202:2181"/>
<!-- 用dubbo协议在20880端口暴露服务 -->
<dubbo:protocol name="dubbo" port="20880"/>
<!-- 和本地bean一样实现服务 -->
<bean id="demoService" class="com.damon4u.demo.dubbo.provider.DemoServiceImpl"/>
<!-- 声明需要暴露的服务接口 -->
<dubbo:service interface="com.damon4u.demo.dubbo.api.DemoService" ref="demoService"/>
</beans>

dubbo默认使用log4j作为日志输出,此处我们想使用slf4j代替,需要在dubbo.properties中指定日志类型:

1
dubbo.application.logger=slf4j

然后添加logback.xml:

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
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="Console" class="ch.qos.logback.core.ConsoleAppender">
<Target>System.out</Target>
<encoder>
<pattern>%date [%level] [%thread] %logger{80} [%file : %line] %msg%n</pattern>
</encoder>
</appender>
<logger name="org.springframework" level="info"/>
<logger name="jdbc.connection" level="OFF"/>
<logger name="org.apache" level="error"/>
<logger name="jdbc.resultset" level="OFF"/>
<logger name="org.apache.ibatis" level="info"/>
<logger name="jdbc.audit" level="OFF"/>
<logger name="jdbc.sqlonly" level="INFO"/>
<logger name="jdbc.sqltiming" level="DEBUG"/>
<logger name="org.quartz.simpl" level="error"/>
<logger name="com.alibaba.dubbo" level="INFO"/>
<root level="debug">
<appender-ref ref="Console"/>
</root>
</configuration>

最后是一个启动类:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Provider {
private static final Logger logger = LoggerFactory.getLogger(Provider.class);
public static void main(String[] args) throws Exception {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
new String[] {"dubbo-provider.xml"});
context.start();
logger.info("Provider Running...");
System.in.read(); // press any key to exit
}
}

5. dubbo-consumer

新建子模块,作为api接口调用者。同样导入dubbo-provider中的pom依赖,dubbo.properties和logback.xml。
然后是dubbo相关的Spring配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://code.alibabatech.com/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-2.5.xsd
http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<!-- 消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样 -->
<dubbo:application name="hello-dubbo-consumer"/>
<!-- 使用zookeeper注册中心暴露发现服务地址 -->
<dubbo:registry address="zookeeper://10.235.132.202:2181"/>
<!-- 生成远程服务代理,可以和本地bean一样使用demoService -->
<dubbo:reference id="demoService" check="false" interface="com.damon4u.demo.dubbo.api.DemoService"/>
</beans>

最后是启动类,像使用本地service服务一样,调用api接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Consumer {
private static final Logger logger = LoggerFactory.getLogger(Consumer.class);
public static void main(String args[]) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"dubbo-consumer.xml"});
context.start();
DemoService demoService = (DemoService) context.getBean("demoService"); // get remote service proxy
while (true) {
try {
Thread.sleep(1000);
String hello = demoService.sayHello("world"); // call remote method
System.out.println(hello); // get result
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
}
}

6. 测试

我们首先启动一台provider和一台consumer,可以看到两者正常调用。

这时,我们更换provider的端口到20881再启动一台provider,可以看到,consumer收到一个notify,zookeeper中注册的地址多了,部分请求被分发到新的节点。

然后我们再启动两台consumer,模拟多个调用者,可以看到请求被分发到两个provider上。

负载均衡的策略还没有研究。

本项目使用zookeeper作为注册中心,此时zookeeper注册节点情况如下(图中使用的工具是ZooInspector.jar):

当增删节点时,都会有notify通知,提供服务发现。

完整的项目代码在github上:hello-dubbo