Dubbo:第一个 Dubbo 项目


#Dubbo#


第一个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,contractproviderconsumer。 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,本服务端口是20880demo.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, 张三。这是我们想要的结果,符合预期。

值得思考的地方

  1. 实现demo.contract.DemoService接口的服务对应的项目,应该只一个,否则多项目实现的服务,内容可能不一样。这样的服务实现是错乱的。
  2. 服务方和调用方,不应该在一个程序中运行,最好也别在一个项目中。一个好的项目结构应该是将 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&timestamp=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&timestamp=1531576586309

( 本文完 )