第一个dubbo项目当然是 Hello World。
本文示例代码就是为一个含有sayHi函数的接口实现服务。参考了 一个简单的案例带你入门Dubbo分布式框架。
测试代码过程中遇到了注册zookeeper慢的问题,解决方案:
安装和启动 zookeeper
需要安装 zookeeper 用于服务注册和发现。下载安装包解压,Linux 下执行zkServer.sh start
启动即可。
项目结构和依赖配置
项目结构如下:
.
├── README.md
├── build.gradle
├── settings.gradle
├── consumer
│ ├── src
│ └── main
│ ├── java
│ │ └── demo
│ │ └── consumer
│ │ └── ConsumerMain.java
│ └── resources
│ ├── dubbo-consumer.xml
│ ├── dubbo.properties
│ └── log4j.properties
├── contract
│ ├── src
│ └── main
│ └── java
│ └── demo
│ └── contract
│ └── DemoService.java
├── provider
├── src
└── main
├── java
│ └── demo
│ └── provider
│ ├── DemoServiceImpl.java
│ └── ProviderMain.java
└── resources
├── dubbo-provider.xml
├── dubbo.properties
└── log4j.properties
项目包含3个module,contract
、provider
、consumer
。 contract 提供接口。provider 基于接口实现服务,consumer 则是消费者,去调用服务。
setting.gradle 内容如下:
rootProject.name = 'dubbo-demo-0002'
include 'contract'
include 'provider'
include 'consumer'
build.gradle 内容如下:
group 'com.example'
version '1.0-SNAPSHOT'
apply plugin: 'java'
sourceCompatibility = 1.8
ext {
dubboVersion = '2.7.1'
zookeeperVersion = '3.4.10'
junitVersion = '4.12'
}
allprojects {
apply plugin: 'java'
repositories {
maven { url 'https://maven.aliyun.com/repository/public/' }
mavenCentral()
}
}
// 配置子项目 contract
project(":contract") {
dependencies {
testCompile "junit:junit:$junitVersion"
}
}
// 配置子项目 consumer
project(":consumer") {
dependencies {
compile project(":contract") // 依赖子项目 contract
compile "org.apache.dubbo:dubbo:$dubboVersion"
compile "org.apache.dubbo:dubbo-dependencies-zookeeper:$dubboVersion"
testCompile "junit:junit:$junitVersion"
}
}
project(":provider") { // 配置子项目 provider
dependencies {
compile project(":contract") // 依赖子项目 contract
compile "org.apache.dubbo:dubbo:$dubboVersion"
compile "org.apache.dubbo:dubbo-dependencies-zookeeper:$dubboVersion"
testCompile "junit:junit:$junitVersion"
}
}
代码实现
接口实现
在 contract 模块中,添加以下接口:
package demo.contract;
public interface DemoService {
String sayHello(String name);
}
服务实现
在 provider 模块中,增加 DemoService 接口的实现类:
package demo.provider;
import demo.contract.DemoService;
public class DemoServiceImpl implements DemoService {
@Override
public String sayHello(String name) {
return "Hello, " + name;
}
}
在src/main/resources
目录增加 dubbo-provider.xml
:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<dubbo:application name="hello-world-app"></dubbo:application>
<dubbo:registry address="zookeeper://127.0.0.1:2181"/>
<dubbo:protocol name="dubbo" port="20881"/>
<dubbo:service interface="demo.contract.DemoService" ref="demoServiceImpl"/>
<bean id="demoServiceImpl" class="demo.provider.DemoServiceImpl"/>
</beans>
zookeeper 服务注册地址是127.0.0.1:2181
,本服务端口是20880
。demo.contract.DemoServiceImpl
类作为 demo.contract.DemoService
接口的实现,暴露出去。
编写主函数:
package demo.provider;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.io.IOException;
public class ProviderMain {
public static void main(String[] args) throws IOException {
System.out.println("start");
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("dubbo-provider.xml");
ctx.start();
System.in.read();
}
}
消费者实现
在 consumer 模块的 src/main/resources
目录增加 dubbo-consumer.xml
:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<dubbo:application name="consumer-of-helloworld-app"/>
<dubbo:consumer timeout="5000" />
<dubbo:registry address="zookeeper://127.0.0.1:2181" check="false"/>
<dubbo:reference id="demoService" interface="demo.contract.DemoService" check="false"/>
</beans>
<dubbo:reference id="sayHello" interface="demo.contract.DemoService" check="false"/>
声明了要用到demo.contract.DemoService
对应的服务。
编写消费逻辑:
package demo.consumer;
import demo.contract.DemoService;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class ConsumerMain {
public static void main(String[] args) {
System.out.println("start");
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("dubbo-consumer.xml");
DemoService demoService = (DemoService) ctx.getBean(DemoService.class);
String s = demoService.sayHello("张三");
System.out.println(s);
// 结果是输出 "Hello, 张三"
}
}
运行效果
先执行 provider 中的ProviderMain
类,它会告诉zookeeper它可以提供demo.contract.DemoService
这个接口对应的服务,服务IP是多少,端口是多少。欢迎别的服务过来调用。
再执行 consumer 中的 ConsumerMain
类,它会去问zookeeper有没有服务提供了demo.contract.DemoService
这个接口的实现,有的话,告诉我位置(可能1个或者多个),我去调用其中一个。
然后 ConsumerMain 运行后会输出一堆结果(大部分是 dubbo 产生的日志),其中我们能看到:
start
Hello, 张三
重点在Hello, 张三
。这是我们想要的结果,符合预期。
值得思考的地方
- 实现
demo.contract.DemoService
接口的服务对应的项目,应该只一个,否则多项目实现的服务,内容可能不一样。这样的服务实现是错乱的。 - 服务方和调用方,不应该在一个程序中运行,最好也别在一个项目中。一个好的项目结构应该是将 contract 和 provider 放到一个项目中,并将 contract 打包成 jar 包发布到组织内部的maven仓库中;consumer 单独是一个项目,并声明对 contract 的依赖。
当provider服务启动时,zookeeeper里面存了什么?
我们使用 zhCli.sh
查看zookeeper存储的内容。
$ zkCli.sh -server 127.0.0.1:2181
> ls /
[dubbo, zookeeper]
> ls /dubbo
[demo.contract.DemoService]
> ls /dubbo/demo.contract.DemoService
[consumers, configurators, routers, providers]
如果只启动一个 provider 服务:
> ls /dubbo/demo.contract.DemoService/providers
[dubbo%3A%2F%2F192.168.0.101%3A20880%2Fdemo.contract.DemoService%3Fanyhost%3Dtrue%26application%3Dhello-world-app%26dubbo%3D2.5.3%26interface%3Ddemo.contract.DemoService%26methods%3DsayHello%26pid%3D10676%26side%3Dprovider%26timestamp%3D1531561272410]
只有一个元素,进行urldecode解码:
dubbo://192.168.0.101:20880/demo.contract.DemoService?anyhost=true&application=hello-world-app&dubbo=2.5.3&interface=demo.contract.DemoService&methods=sayHello&pid=10676&side=provider×tamp=1531561272410
我们再启动一个 provider 服务。 先改下端口<dubbo:protocol name="dubbo" port="20880"/>
改成<dubbo:protocol name="dubbo" port="20881"/>
,然后运行 ProviderMain 。
继续在zkCli.sh
中查看:
> ls /dubbo/demo.contract.DemoService/providers
[dubbo%3A%2F%2F192.168.0.101%3A20880%2Fdemo.contract.DemoService%3Fanyhost%3Dtrue%26application%3Dhello-world-app%26dubbo%3D2.5.3%26interface%3Ddemo.contract.DemoService%26methods%3DsayHello%26pid%3D10676%26side%3Dprovider%26timestamp%3D1531561272410, dubbo%3A%2F%2F192.168.0.101%3A20881%2Fdemo.contract.DemoService%3Fanyhost%3Dtrue%26application%3Dhello-world-app%26dubbo%3D2.5.3%26interface%3Ddemo.contract.DemoService%26methods%3DsayHello%26pid%3D13664%26side%3Dprovider%26timestamp%3D1531576586309]
可以看到多了一个服务,urldecode解码结果:
dubbo://192.168.0.101:20881/demo.contract.DemoService?anyhost=true&application=hello-world-app&dubbo=2.5.3&interface=demo.contract.DemoService&methods=sayHello&pid=13664&side=provider×tamp=1531576586309