使用 Dubbo 搭建一个简单的分布式系统

Posted GitChat精品课

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用 Dubbo 搭建一个简单的分布式系统相关的知识,希望对你有一定的参考价值。


前言


随着阿里巴巴开源的高性能分布式 RPC 框架 Dubbo 正式进入 Apache 孵化器,Dubbo 又火了一把。本文作为 Dubbo 系列开端,先教大家使用 Dubbo 搭建一个简单的分布式系统,因为要研究一个东西的原理,必须先能把环境搭建起来,并且会使用它。


在这个系统里面会包含服务提供者,服务消费者,服务注册中心(本 Chat 使用 ZooKeeper),管理控制台(Dubbo-Admin),监控平台(Dubbo-Monitor),麻雀虽小,却五脏俱全。


通过本 Chat 你将能学到(文章内有 Demo 源码):

  • 五大组件的关系。

  • 如何基于 Spring 配置搭建一个简单的分布式系统。

  • 如何基于 Dubbo API 搭建一个简单的分布式系统。

  • 何为服务端异步调用,如何使用异步调用,使用异步调用好处是什么。

  • 何为泛化调用,如何使用泛化调用,什么时候使用泛化调用。


五大组件关系


使用 Dubbo 搭建一个简单的分布式系统

  • 服务提供方在启动时候会注册自己提供的服务到服务注册中心。

  • 管理控制台主要提供路由规则,动态配置,服务降级,访问控制,权重调整,负载均衡,等管理功能。管理控制台直接与服务注册中心打交道,从服务注册中心获取所有注册的服务和消费方法;并且可以通过管理台界面设置服务消费端的路由规则,动态配置等信息并注册到服务管理台,这些信息会被通知到服务消费端。管理控制台也不是分布式系统必备的组件,但是有了他我们可以对服务进行很好的治理和监控。

服务注册中心的搭建


本文我们讲解 Apache ZooKeeper 的搭建。

  • 首先你需要到 http://zookeeper.apache.org/releases.html 下载一个 zk 的包,本文作者使用的是 zookeeper-3.4.11 这个版本,如下图:

使用 Dubbo 搭建一个简单的分布式系统

解压该包后,如下图:

使用 Dubbo 搭建一个简单的分布式系统

  • 然后修改 zookeeper-3.4.11/conf 文件夹里面的 zoo.cfg 文件。

    • 设置配置项 dataDir 为一个存在的以 data 结尾的目录;

    • 设置 zk 的监听端口 clientPort=2181;

    • 设置 zk 心跳检查间隔 tickTime = 2000;

    • 设置 Follower 服务器启动时候从 Leader 同步完毕数据能忍受多少个心跳时间间隔数 initLimit=5。

    • 设置运行过程中 Leader 同步数据到 Follower 后,Follower 回复信息到 Leader 的超时时间 syncLimit=2,如果 Leader 超过 syncLimit 个 tickTime 的时间长度,还没有收到 Follower 响应,那么就认为这个 Follower 已经不在线了:

使用 Dubbo 搭建一个简单的分布式系统

  • 最后在 zookeeper-3.4.11/bin 下运行 sh zkServer.sh start-foreground 就会启动 zk,会有下面输出:

使用 Dubbo 搭建一个简单的分布式系统


可知 zk 在端口 2181 进行监听,至此服务注册中心搭建完毕。


服务提供方与服务消费方搭建


本文 Demo 结构介绍


首先讲解下本文使用的 Demo 的结构,Demo 使用 Maven 聚合功能,里面有三个模块,目录如下:

使用 Dubbo 搭建一个简单的分布式系统


  • 其中 Consumer 模块为服务消费者,里面 TestConsumer 和 consumer.xml 组成了基于 Spring 配置方式的服务调用,TestConsumerApi 是基于 Dubbo API 方式的服务调用,TestConsumerApiGeneric 是泛化方式的服务调用,TestConsumerAsync 是异步调用的方式。

  • 其中 Provider 模块为服务提供者,里面 TestProvider 和 provider.xml 组成了基于 Spring 配置方式的服务提供,TestProviderApi 是基于 Dubbo API 的服务提供,UserServiceImpl 为服务实现类。

  • 其中 SDK 模块是一个二方包,用来存放服务提供者所有的接口,是为了代码复用使用,在服务提供者和消费者的模块里面都需要引入这个二方包。


其中 SDK 里面的接口定义源码如下:

public interface UserServiceBo {    String sayHello(String name);  
    String sayHello2(String name); 
    String testPojo(Person person);

}

在 SDK 模块执行 mvn clean install 命令会安装该模块的 Jar 到本地仓库,要想在其他模块引入该 Jar,必须要先执行这个安装步骤。


基于 Spring 配置的服务提供方与消费方搭建


基于 Spring 配置的服务提供方搭建


Provider 模块为服务提供者,作用是注册提供的服务到 zk,并使用 Netty 服务监听服务消费端的链接。里面 TestProvider 和 provider.xml 组成了基于 XML 方式的服务提供,UserServiceImpl 为服务实现类。


  • 首先需要在 Provider 模块里面引入 SDK 模块,因为 Provider 模块需要用到 UserServiceBo 接口(需要在 SDK 模块执行 mvn clean install 命令会安装该模块的 Jar 到本地仓库)。

  • 然后实现 UserServiceBo 接口为 UserServiceImpl,代码如下:

public class UserServiceImpl implements UserServiceBo{    @Override
    public String sayHello(String name) {        //让当前当前线程休眠2s
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }        return name; 
    }    @Override
    public String sayHello2(String name) {        //让当前当前线程休眠2s
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }        return name;
    }        @Override
    public String testPojo(Person person) {        return JSON.toJSONString(person);
    }  
}
  • 然后 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:context="http://www.springframework.org/schema/context"
    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://www.springframework.org/schema/context    http://www.springframework.org/schema/context/spring-context-4.0.xsd    http://code.alibabatech.com/schema/dubbo    http://code.alibabatech.com/schema/dubbo/dubbo.xsd">

    <!-- 提供方应用信息,用于计算依赖关系 -->
    <dubbo:application name="dubboProvider" />

    <!-- 使用zookeeper注册中心暴露服务地址 -->
    <dubbo:registry address="zookeeper://127.0.0.1:2181" />

    <!-- 用dubbo协议在20880端口暴露服务 -->
    <dubbo:protocol name="dubbo" port="20880" />
    <!-- 启用monitor模块 -->
    <dubbo:monitor protocol="registry" />

    <bean id="userService" class="com.test.UserServiceImpl" />

    <!-- 声明需要暴露的服务接口 -->
    <dubbo:service interface="com.test.UserServiceBo" ref="userService"
        group="dubbo"  version="1.0.0" timeout="3000"/>

</beans>
  • 然后日志文件 log4j.properties 内容如下:

log4j.rootLogger=INFO,A1
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
  • 然后,编写服务发布测试类 TestProvider,代码如下:

public class TestProvider {   

    public static void main(String[] arg) throws InterruptedException {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:provider.xml");        //挂起当前线程,如果没有改行代码,服务提供者进程会消亡,服务消费者就发现不了提供者了
        Thread.currentThread().join();
    }
}
  • 最后,运行 TestProvider 类,输出如下:

使用 Dubbo 搭建一个简单的分布式系统

说明当前服务已经注册了 ZooKeeper 了。


4.3 基于 Dubbo API 方式的服务提供方与消费方搭建



 Consumer 模块为服务消费方,服务消费端主要是从 zk 获取自己需要的服务提供者的 ip 列表,然后根据路由规则选择一个 ip 进行远程调用。里面 TestConsumer 和 consumer.xml 组成了基于 XML 方式的服务调用。

  • 首先需要在 Consumer 模块里面引入 SDK 模块,因为 Consumer 模块需要用到 UserServiceBo 接口(泛化调用时候不需要这个步骤)。

  • 然后 consumer.xml 内容如下:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"    
     xmlns:context="http://www.springframework.org/schema/context"
     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://www.springframework.org/schema/context    http://www.springframework.org/schema/context/spring-context-4.0.xsd    http://code.alibabatech.com/schema/dubbo    http://code.alibabatech.com/schema/dubbo/dubbo.xsd">

  <!-- 消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样 -->    
    <dubbo:application name="dubboConsumer" />  

      <!-- 使用multicast广播注册中心暴露发现服务地址 -->    
    <dubbo:registry  protocol="zookeeper" address="zookeeper://127.0.0.1:2181" />  
       <!-- 启动monitor-->
    <dubbo:monitor protocol="registry" />        
      <!-- 生成远程服务代理,可以和本地bean一样使用demoService -->    
    <dubbo:reference id="userService" interface="com.test.UserServiceBo" group="dubbo" version="1.0.0" timeout="3000"/>    

</beans>
  • 然后测试服务消费类 TestConsumer 代码如下:

public class TestConsumer {    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(                new String[] { "classpath:consumer.xml" });        final UserServiceBo demoService = (UserServiceBo) context.getBean("userService");

        System.out.println(demoService.sayHello("哈哈哈"));
    }
}
  • 最后运行 TestConsumer,会输出:

说明服务消费端已经正常调用了服务提供方的服务了。

注:至此一个经典的含有服务提供者,服务消费者,服务注册中心的简单分布式系统搭建完毕了。


基于 Dubbo API 方式的服务提供方与消费方搭建



基于Dubbo API 方式的服务提供方搭建

其中 Provider 模块为服务提供者,里面 TestProviderApi 是基于 Dubbo API 的服务提供,UserServiceImpl 为服务实现类。

  • 首先需要在 Provider 模块里面引入 SDK 模块,这个不变。

  • 然后实现 UserServiceBo 接口为 UserServiceImpl,这个也不变。

  • 然后编写 Dubbo API 服务提供测试代码 TestProviderApi 代码如下:

public class TestProviderApi {    public static void main(String[] arg) throws InterruptedException {        //(4.3.1-1)等价于  <bean id="userService" class="com.test.UserServiceImpl" />
        UserServiceBo userService = new UserServiceImpl();        //(4.3.1-2)等价于  <dubbo:application name="dubboProvider" />
        ApplicationConfig application = new ApplicationConfig();
        application.setName("dubboProvider");        //(4.3.1-3)等价于  <dubbo:registry address="zookeeper://127.0.0.1:2181" />
        RegistryConfig registry = new RegistryConfig();
        registry.setAddress("127.0.0.1:2181");
        registry.setProtocol("zookeeper");        // (4.3.1-4)等价于 <dubbo:protocol name="dubbo" port="20880" />
        ProtocolConfig protocol = new ProtocolConfig();
        protocol.setName("dubbo");
        protocol.setPort(20880);        //4.3.1-5)等价于     <dubbo:monitor protocol="registry" />
        MonitorConfig monitorConfig = new MonitorConfig();
        monitorConfig.setProtocol("registry");        //4.3.1-6)等价于 <dubbo:service interface="com.test.UserServiceBo" ref="userService"
        //group="dubbo"  version="1.0.0" timeout="3000"/>
        ServiceConfig<UserServiceBo> service = new ServiceConfig<UserServiceBo>(); // 此实例很重,封装了与注册中心的连接,请自行缓存,否则可能造成内存和连接泄漏
        service.setApplication(application);
        service.setMonitor(monitorConfig);
        service.setRegistry(registry); // 多个注册中心可以用setRegistries()
        service.setProtocol(protocol); // 多个协议可以用setProtocols()
        service.setInterface(UserServiceBo.class);
        service.setRef(userService);
        service.setVersion("1.0.0");
        service.setGroup("dubbo");
        service.setTimeout(3000);
        service.export();       //4.3.1-8) 挂起当前线程
        Thread.currentThread().join();
    }
}

基于Dubbo API 方式的服务消费方搭建

其中 Consumer 模块为服务消费者,里面 TestConsumerApi 是基于 Dubbo API 方式的服务调用。

  • 首先需要在 Consumer 模块里面引入 SDK 模块,这个不变。

  • 编写基于 Dubbo API 消费服务的测试类 TestConsumerApi 代码如下:

public class TestConsumerApi {    public static void main(String[] args) throws InterruptedException {        // 等价于  <dubbo:application name="dubboConsumer" />  
        ApplicationConfig application = new ApplicationConfig();
        application.setName("dubboConsumer");        // 等价于     <dubbo:registry  protocol="zookeeper" address="zookeeper://127.0.0.1:2181" />  
        RegistryConfig registry = new RegistryConfig();
        registry.setAddress("127.0.0.1:2181");
        registry.setProtocol("zookeeper");        //等价于   <dubbo:monitor protocol="registry" />
        MonitorConfig monitorConfig = new MonitorConfig();
        monitorConfig.setProtocol("registry");        //等价于<dubbo:reference id="userService" interface="com.test.UserServiceBo"
        //group="dubbo" version="1.0.0" timeout="3000" />
        ReferenceConfig<UserServiceBo> reference = new ReferenceConfig<UserServiceBo>(); // 此实例很重,封装了与注册中心的连接以及与提供者的连接,请自行缓存,否则可能造成内存和连接泄漏
        reference.setApplication(application);
        reference.setRegistry(registry); // 多个注册中心可以用setRegistries()
        reference.setInterface(UserServiceBo.class);
        reference.setVersion("1.0.0");
        reference.setGroup("dubbo");
        reference.setTimeout(3000);
        reference.setInjvm(false);
        reference.setMonitor(monitorConfig);

        UserServiceBo userService = reference.get(); 
        System.out.println(userService.sayHello("哈哈哈"));
        Thread.currentThread().join();

    }
}


服务消费端泛化调用



前面我们讲解基于 Spring 和基于 Dubbo API 方式搭建一个简单的分布式系统时候服务消费端是引入了一个 SDK 二方包的,里面存放了服务提供端提供的所有接口类,之所以需要引入接口类是因为服务消费端一般是基于接口使用 JDK 代理实现远程调用的。

泛化接口调用方式主要用于服务消费端没有 API 接口类及模型类元(比如入参和出参的 POJO 类)的情况下使用;这时候参数及返回值中由于没有对应的 POJO 类,所以所有 POJO 均转换为 Map 表示。使用泛化调用时候服务消费模块不在需要引入 SDK 二方包。

下面基于 Dubbo API 来实现异步调用,在 Consumer 模块里面 TestConsumerApiGeneric 是泛化调用的方式,代码如下:

public class TestConsumerApiGeneric {    public static void main(String[] args) throws IOException {        // 当前应用配置
        ApplicationConfig application = new ApplicationConfig();
        application.setName("dubboConsumer");        // 连接注册中心配置
        RegistryConfig registry = new RegistryConfig();
        registry.setAddress("127.0.0.1:2181");
        registry.setProtocol("zookeeper");        // 泛型参数设置为GenericService
        ReferenceConfig<GenericService> reference = new ReferenceConfig<GenericService>(); 
        reference.setApplication(application);
        reference.setRegistry(registry); 
        reference.setVersion("1.0.0");
        reference.setGroup("dubbo");
        reference.setTimeout(3000);        //设置为泛化
        reference.setInterface("com.test.UserServiceBo");
        reference.setGeneric(true);        //用com.alibaba.dubbo.rpc.service.GenericService替代所有接口引用
        GenericService userService = reference.get(); // 

        // 基本类型以及Date,List,Map等不需要转换,直接调用,如果返回值为POJO也将自动转成Map
        Object result = userService.$invoke("sayHello", new String[] {"java.lang.String"},        new Object[] {"哈哈哈"});

        System.out.println(JSON.json(result));//POJO参数转换为mapMap<String, Object> map = new HashMap<String, Object>();
        map.put("class", "com.test.PersonImpl");
        map.put("name", "jiaduo");
        map.put("password", "password");

        result = userService.$invoke("testPojo", new String[] { "com.test.Person" }, new Object[] { map });
        System.out.println((result));
    }
}

这里由于 sayHello 的参数是 String,没有很好的体现参数转换为 Map,下面我们具体来说下 POJO 参数转换 Map 的含义。

比如服务提供者提供的一个接口的 testPojo(Person person) 方法的参数为如下 POJO:

package com.test;public class PersonImpl implements Person {private String name;private String password;public String getName() {return name;
}public void setName(String name) {this.name = name;
}public String getPassword() {return password;
}public void setPassword(String password) {this.password = password;
}
}

则 POJO 数据:

Person person = new PersonImpl();
person.setName("jiaduo");
person.setPassword("password");

正常情况下调用接口是使用:

servicePerson.testPojo(person);

泛化调用下需要首先转换 person 为 Map 后如下表示:

Map<String, Object> map = new HashMap<String, Object>();// 注意:如果参数类型是接口,或者List等丢失泛型,可通过class属性指定类型。map.put("class", "com.test.PersonImpl");
map.put("name", "jiaduo");
map.put("password", "password");

然后使用下面方法进行泛化调用:

 servicePerson.$invoke("testPojo", new String[]
{"com.test.Person"}, new Object[]{map});

泛化调用通常用于框架集成,比如:实现一个通用的服务测试框架,可通过 GenericService 调用所有服务实现,而不需要依赖服务实现方提供的接口类以及接口的入参和出参的 POJO 类。


 服务消费端异步调用



无论前面我们讲解的正常调用还是泛化调用也好,都是进行同步调用的,也就是服务消费方发起一个远程调用后,调用线程要被阻塞挂起,直到服务提供方返回。

本节来讲解下服务消费端异步调用,异步调用是指服务消费方发起一个远程调用后,不等服务提供方返回结果,调用方法就返回了,也就是当前线程不会被阻塞,这就允许调用方同时调用多个远程方法。


阅读全文请扫描

下方二维码,

还可以加入读者圈与作者聊天~:






以上是关于使用 Dubbo 搭建一个简单的分布式系统的主要内容,如果未能解决你的问题,请参考以下文章

用dubbo+zookeeper+spring搭建一个简单的http接口程序

dubbo分布式系统---环境搭建

新手都能懂,使用SpringBoot+Dubbo 搭建一个简单的分布式服务

Spring+Dubbo搭建一个简单的分布式

超详细,新手都能看懂 !大牛带你使用SpringBoot+Dubbo 搭建一个简单的分布式服务,还附带坦克大战项目,速看!

Dubbo剖析-整体架构分析