1. 引子
为什么突然想起来要学Dubbo呢,原因有下:
之前有人问什么是SPI,以前学习嵌入式的时候,听过硬件上有SPI总线协议,不知道Java世界也有一个SPI的概念,全称为:Service Provider Interface。在Dubbo的官网介绍开端就提到了SPI这个概念,为此就想通过Dubbo来多了解一下SPI。
在学习Spring Cloud的时候,服务间访问是通过REST风格的HTTP调用实现的。但是很多书和文章提到在分布式架构中除了基于HTTP的REST调用外,还提到了RPC框架,即远程过程调用来实现服务间调用。至于REST和RPC的区别是什么一直有点模糊,直到在上偶然翻到一篇文章,作者叫柳树之,那篇文章的地址是:https://www.jianshu.com/p/2accc2840a1b。在一堆无病呻吟的软文中找到这种优秀的作者也是比较稀少的。从文中得知,Dubbo就是基于RPC实现的。为此也产生了想深入学习Dubbo的念想。
2. 环境搭建
Dubbo官网文档地址为:http://dubbo.apache.org/zh-cn/docs/source_code_guide/dubbo-spi.html。下面就先照着官网文档内容进行环境搭建。
2.1 zookeeper
dubbo的服务注册依赖于zookeeper,在zookeeper官网下载包,分别执行下面的命令。
-
解压
> tar -zxvf zookeeper-3.4.10.tar.gz //解压 > cd zookeeper-3.4.10/conf //切换到配置目录下 > mv zoo_sample.cfg zoo.cfg //更改默认配置文件名称 > vi zoo.cfg //编辑配置文件,自定义dataDir
-
启动
> cd zookeeper-3.4.10/bin //切换到 bin目录 > ./zkServer.sh start //启动
-
客户端连接
> cd zookeeper-3.4.10/bin //切换到 bin目录 > ./zkCli.sh -server 127.0.0.1:2181
-
停止
> ./zkServer.sh stop //停止后,如果CLi没有关闭,将报错
2.2.1 服务发现组件介绍
对于微服务的治理而言,核心就是服务的注册和发现。所以选择哪个组件,很大程度上要看它对于服务注册与发现的解决方案。在这个领域,开源的框架有很多,最常见的是ZooKeeper,但这并不是一个最佳的选择。
在分布式系统领域有一个著名的CAP定理:C(数据一致性)、A(服务可用性)、P(服务对网络分区故障的容错性)这三个特性在任何分布式系统中不能同时满足,最多同时满足两个。ZooKeeper是Apache下非常著名的一个顶级项目,很多场景下ZooKeeper也作为Service发现服务解决方案。ZooKeeper保证的是CP,即任何时刻对ZooKeeper的访问请求能得到一致的数据结果,同时系统对网络分区具备容错性,但是它不能保证每次服务请求的可用性。从实际情况来分析,在使用ZooKeeper获取服务列表时,如果ZooKeeper正在选主,或者ZooKeeper集群中半数以上机器不可用,那么就无法获取数据。所以说,ZooKeeper不能保证服务可用性。
的确,对于大多数分布式环境,尤其是涉及数据存储的场景,数据一致性是应该首先被保证的,这也是ZooKeeper被设计成CP的主要原因。但是对于服务发现场景来说,具体情况就需要具体分析:针对同一个服务,即使注册中心的不同节点保存的服务提供者信息不一致,也不会造成灾难性的后果。因为对于服务消费者来说,能够消费才是最重要的。所以,对于服务发现而言,可用性比数据一致性更加重要(AP优于CP)。而Spring Cloud Netflix在设计Eureka的时候遵守就是的AP原则。
Eureka是Netflix开源的一款提供服务注册和发现的产品,并且提供了相应的Java SDK封装。在它的实现中,节点之间是相互平等的,部分注册中心的节点“挂掉”也不会对集群造成影响,即使集群只剩一个节点存活,也可以正常提供发现服务。哪怕是所有的服务注册节点都“挂了”, EurekaClients也会缓存服务调用的信息。这就保证了微服务之间的互相调用是足够健壮的。为了适配更多的服务注册发现框架,Spring Cloud针对该方案进行了一层抽象,官方提供了三种实现:Eureka、Consul、ZooKeeper,目前支持得最好的就是Eureka,其次是Consul,最后是ZooKeeper。
2.2 dubbo快速启动
dubbo官网已经提供了一个demo,基于这个demo,我们可以快速创建出服务提供者和消费者。这里直接从码云上将dubbo代码拉取下来:
git clone https://gitee.com/mirrors/dubbo.git
不使用官网写的github,因为下载速度太慢了。
下载后可以看到这个一个整体的大目录,下面有以"dubbo-"开头的各种包。其使用IDEA将dubbo/dubbo-demo/dubbo-demo-xml
这个目录导入成一个maven工程。该maven工程中包含了名为dubbo-demo-xml-consumer
和名为dubbo-demo-xml-provider
的两个模块。
2.2.1 公共模块
在dubbo中,服务提供者提供的方法要以接口的形式单独打成一个jar包,然后分别被服务提供者和服务消费者引入。为此先新建一个只定义接口的maven工程:在dubbo-demo-xml这个大工程下,新建一个module,名为api。该模块只定义一个接口:
package org.apache.dubbo.demo;
public interface DemoService {
String sayHello(String name);
}
然后,pom.xml文件修改如下:
com.dubbo.api
api
1.0-SNAPSHOT
jar
这样,在根目录下执行mvn install
命令,将应用api打包成一个jar包(target目录下)。
生成jar包后,还要将这个jar包发布到本地的maven仓库中,发布命令为:
mvn install:install-file -Dfile=/Users/yubuyun/Documents/workspace/dubbo/dubbo/dubbo-demo/dubbo-demo-xml/api/target/api-1.0-SNAPSHOT.jar -DgroupId=com.dubbo.api -DartifactId=api -Dversion=1.0.0 -Dpackaging=jar -DgeneratePom=true -DcreateChecksum=true
这样本地仓库就有这个api-1.0-SNAPSHOT.jar了。
2.2.2 服务提供者
添加依赖
首先修改pom.xml文件,将公共模块这个jar包引入:
com.dubbo.api
api
1.0-SNAPSHOT
compile
访问接口实现
然后服务提供者应用中就可以直接实现DemoService这个接口了:
public class DemoServiceImpl implements DemoService {
private static final Logger logger = LoggerFactory.getLogger(DemoServiceImpl.class);
@Override
public String sayHello(String name) {
logger.info("Hello " + name + ", request from consumer: " + RpcContext.getContext().getRemoteAddress());
return "Hello " + name + ", response from provider: " + RpcContext.getContext().getLocalAddress();
}
}
这里服务提供者只是打印和返回了字符串。
配置文件
dubbo官方的demo中在resources目录下还提供了dubbo配置文件,名为dubbo-provider.xml
,内容如下:
表示将当前应用起名为demo-provider
,注册到本地的zookeeper,并且指定了接口实现类并注入到Spring容器中。
启动服务提供者
main函数定义如下:
public class Application {
public static void main(String[] args) throws Exception {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/dubbo-provider.xml");
context.start();
System.in.read();
}
}
就是解析dubbo配置到容器中,然后启动。
2.2.3 服务消费者
添加依赖
首先修改pom.xml文件,将公共模块这个jar包引入:
com.dubbo.api
api
1.0-SNAPSHOT
compile
配置文件
dubbo官方的demo中在resources目录下还提供了dubbo配置文件,名为dubbo-consumer.xml
,内容如下:
表示将当前应用起名为demo-consumer
,注册到本地的zookeeper,并且将通过demoService接口访问其他应用。
启动服务消费者
启动类定义如下:
public class Application {
public static void main(String[] args) throws Exception {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/dubbo-consumer.xml");
context.start();
DemoService demoService = context.getBean("demoService", DemoService.class);
String result = demoService.sayHello("hello");
System.out.println("consumer:"+result);
}
}
启动过程中,服务消费者本身并没有实现DemoService接口实现,但是仍然可以直接调用该接口的访问,这时直接就访问到了服务提供者上。这就是RPC调用的优点:像本地调用一样进行远程调用。
2.2.4 调试
首先启动zookeeper,然后启动服务提供者,再启动服务消费者,就会发现,服务提供者打印:
provider.DemoServiceImpl: Hello hello, request from consumer: /99.15.214.152:55576
服务消费者收到返回值后打印:
consumer:Hello hello, response from provider: 99.15.214.152:20880
这就说明两个应用之间RPC调用成功了。
3. 其他
本文示例代码的地址为:https://gitee.com/yubuyun/dubbo-study