使用 Dubbo 搭建一个简单的分布式系统
Posted GitChat精品课
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用 Dubbo 搭建一个简单的分布式系统相关的知识,希望对你有一定的参考价值。
前言
随着阿里巴巴开源的高性能分布式 RPC 框架 Dubbo 正式进入 Apache 孵化器,Dubbo 又火了一把。本文作为 Dubbo 系列开端,先教大家使用 Dubbo 搭建一个简单的分布式系统,因为要研究一个东西的原理,必须先能把环境搭建起来,并且会使用它。
在这个系统里面会包含服务提供者,服务消费者,服务注册中心(本 Chat 使用 ZooKeeper),管理控制台(Dubbo-Admin),监控平台(Dubbo-Monitor),麻雀虽小,却五脏俱全。
通过本 Chat 你将能学到(文章内有 Demo 源码):
五大组件的关系。
如何基于 Spring 配置搭建一个简单的分布式系统。
如何基于 Dubbo API 搭建一个简单的分布式系统。
何为服务端异步调用,如何使用异步调用,使用异步调用好处是什么。
何为泛化调用,如何使用泛化调用,什么时候使用泛化调用。
五大组件关系
服务提供方在启动时候会注册自己提供的服务到服务注册中心。
管理控制台主要提供路由规则,动态配置,服务降级,访问控制,权重调整,负载均衡,等管理功能。管理控制台直接与服务注册中心打交道,从服务注册中心获取所有注册的服务和消费方法;并且可以通过管理台界面设置服务消费端的路由规则,动态配置等信息并注册到服务管理台,这些信息会被通知到服务消费端。管理控制台也不是分布式系统必备的组件,但是有了他我们可以对服务进行很好的治理和监控。
服务注册中心的搭建
本文我们讲解 Apache ZooKeeper 的搭建。
首先你需要到 http://zookeeper.apache.org/releases.html 下载一个 zk 的包,本文作者使用的是 zookeeper-3.4.11 这个版本,如下图:
解压该包后,如下图:
然后修改 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 已经不在线了:
最后在 zookeeper-3.4.11/bin 下运行 sh zkServer.sh start-foreground 就会启动 zk,会有下面输出:
可知 zk 在端口 2181 进行监听,至此服务注册中心搭建完毕。
服务提供方与服务消费方搭建
本文 Demo 结构介绍
首先讲解下本文使用的 Demo 的结构,Demo 使用 Maven 聚合功能,里面有三个模块,目录如下:
其中 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 类,输出如下:
说明当前服务已经注册了 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接口程序
新手都能懂,使用SpringBoot+Dubbo 搭建一个简单的分布式服务
超详细,新手都能看懂 !大牛带你使用SpringBoot+Dubbo 搭建一个简单的分布式服务,还附带坦克大战项目,速看!