大家好,我是老田
答应了小伙伴的Dubbo
源码分析系列,今天终于来了,希望不是很晚。
主要也是现在Spring CLoud Alibaba
实在是太火了,很多小伙伴都想好好搞搞。
既然如此,我们就选择从Dubbo
开始吧。
如果你已经对Dubbo
熟练使用了,那这篇文章不太适合你,但如果想了解Dubbo
,想学习Dubbo
,那就非常适合你。
什么是Dubbo?
Dubbo
一开始是由阿里巴巴开发,后面贡献给了Apache
,所以后面我们称之为Apache Dubbo
或者直接叫Dubbo
。
Dubbo
是一款高性能、轻量级的开源服务框架 。
先纠正读法: 错误读法:diubo、dubo 正确读法:|ˈdʌbəʊ|
Dubbo的六大核心能力
- 面向接口代理的高性能
RPC
调用 - 智能容错和负载均衡
- 服务自动注册和发现
- 高度可扩展能力
- 运行期流量调度
- 可视化的服务治理与运维。
开发中,我们都喜欢把Dubbo
简称为RPC
开源框架。
什么是RPC?
RPC
是Remote Procedure Call
的简称,翻译过来就是:远程过程调用
。
简单的理解是一个节点请求另一个节点提供的服务 。
通俗点讲:
两台服务器A和B,在服务器A上部署一个应用程序serverA
,在服务器B上部署一个应用程序serverB
。此时,serverA想调用serverB
里的某个方法,由于不在同一服务器内,不能直接调用,需要通过网络来表达调用的语义和传导调用的数据。
调用远程的方法就像调用本地的方法一样。
我们开发中,通常两个服务(不同服务器上的服务)之间的调用,通常都是用HTTP REST。
RPC框架
其实关于RPC框架,市面上有很多了,Dubbo只是其中之一。比如说还有:
- gRPC 是 Google 开发的高性能、通用的开源 RPC 框架,gRPC 使用 ProtoBuf 来定义服务,ProtoBuf 是 Google 开发的一种数据序列化协议,性能比较高,压缩和传输效率高,语法也比较简单。另外,gRPC 支持多种语言,并能够基于语言自动生成客户端和服务端功能库。
- Thrift 起源于 Facebook,和 Dubbo 一样,后来被提交 Apache 基金会将 Thrift 作为一个开源项目。Facebook 创造 Thrift 的目的是为了解决 Facebook 各系统间大数据量的传输通信,以及系统间语言环境不同需要跨平台的问题。
- Motan 是新浪微博开源的一个 Java RPC 框架,官方文档对外宣传在微博平台已经广泛应用,每天为数百个服务完成近千亿次的调用。
- ...
Dubbo核心角色
我们来看看Dubbo
架构中的核心角色:
该图来自于官网,下面我们对图做一个简单介绍:
Registry
注册中心。负责服务地址的注册与查找,服务的 Provider
和 Consumer
只在启动时与注册中心交互。注册中心通过长连接感知 Provider
的存在,在 Provider
出现宕机的时候,注册中心会立即推送相关事件通知 Consumer
。
Provider
服务提供者。在它启动的时候,会向 Registry 进行注册操作,将自己服务的地址和相关配置信息封装成 URL 添加到 ZooKeeper 中。
Consumer
服务消费者。在它启动的时候,会向 Registry 进行订阅操作。订阅操作会从 ZooKeeper
中获取 Provider 注册的 URL,并在 ZooKeeper
中添加相应的监听器。获取到 Provider URL 之后,Consumer
会根据负载均衡算法从多个 Provider
中选择一个 Provider
并与其建立连接,最后发起对 Provider
的 RPC
调用。如果 Provider
URL 发生变更,Consumer
将会通过之前订阅过程中在注册中心添加的监听器,获取到最新的 Provider URL 信息,进行相应的调整,比如断开与宕机 Provider
的连接,并与新的 Provider
建立连接。Consumer 与 Provider
建立的是长连接,且 Consumer
会缓存 Provider
信息,所以一旦连接建立,即使注册中心宕机,也不会影响已运行的 Provider
和 Consumer
。
Monitor
监控中心。用于统计服务的调用次数和调用时间。Provider
和 Consumer
在运行过程中,会在内存中统计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。监控中心在上面的架构图中并不是必要角色,监控中心宕机不会影响 Provider
、Consumer
以及 Registry
的功能,只会丢失监控数据而已。
猥琐发育,后期爆发,(前期可能关注的不多,但是后期它特别香)
Container:
服务运行容器。是一个独立的容器,因为服务通常不需要Tomcat
、JBoss
等Web容器的特性,没必要用Web容器去加载服务。服务容器只是一个简单的main方法,并加载一个简单的Spring容器,用于暴露服务。
流程说明
在上面这张图中,有几个角色,并且还画了很多线条,下面我们对此做一个简单说明。
- 服务容器负责启动,加载,运行服务提供者。
- 服务提供者在启动时,向注册中心注册自己提供的服务。
- 服务消费者在启动时,向注册中心订阅自己所需的服务。
- 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
- 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
- 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
Dubbo官网
Dubbo
的官网:https://dubbo.apache.org/
由于Dubbo
是由阿里巴巴技术团队开发的,所以,文档方面对于咱们中国人来说那是相当的友好,一个字:爽!
另外,Dubbo
官网上很多东西,我们就不在这里一一介绍了。
建议大家都去官网逛逛。
话不多说,咱们先来嗨一把!
demo案例1
我们先来搞一个没有注册中心的案例。
我们搭建一个项目,并创建三个module
:
dubbo-demo
dubbo-demo-api
dubbo-demo-provider
dubbo-demo-consumer
项目整体结构如下:
下面,我们来代码做一个简单说明。
首先是pom
依赖:
<!--dubbo的依赖-->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>3.0.4</version>
</dependency>
<!-- provider和consumer共用类-->
<dependency>
<groupId>com.tian.dubbo</groupId>
<artifactId>dubbo-demo-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
consumer和provider项目都需要添加这两个依赖。
api
api主要是定义服务接口以及一些工具类,主要是供consumer和provider共用。
在api中我们只定义了一个服务接口:DemoService
package com.tian.dubbo.service;
public interface DemoService {
String sayHello(String msg);
}
然后打成jar,在consumer和provider项目中添加到pom.xml
依赖里,最后两遍都可以使用了。
provider
在resources目录下创建一个目录META-INF.spring
,然后在目录下创建一个application.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服务名称-->
<dubbo:application name="dubbo-provider"/>
<!--不需要注册到服务注册中心-->
<dubbo:registry address="N/A"/>
<!--端口和协议-->
<dubbo:protocol name="dubbo" port="20880"/>
<!--我们的服务-->
<dubbo:service interface="com.tian.dubbo.service.DemoService" ref="demoService"/>
<bean id="demoService" class="com.tian.dubbo.service.DemoServiceImpl"/>
</beans>
再在resources目录下创建一个日志打印的配置文件:log4j.properties
###set log levels###
log4j.rootLogger=debug, stdout
###output to the console###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=[%d{dd/MM/yy HH:mm:ss:SSS z}] %t %5p %c{2}: %m%n
在定义一个业务实现类:DemoServiceImpl
package com.tian.dubbo.service;
public class DemoServiceImpl implements DemoService {
public String sayHello(String msg) {
System.out.println("msg= " msg);
return "SUCCESS";
}
}
再就是定义一个provider的启动类:ProviderMain
package com.tian.dubbo;
import org.apache.dubbo.container.Main;
public class ProviderMain {
public static void main(String[] args) {
Main.main(args);
}
}
注意:这里的Main类是Dubbo
最后,我们启动ProviderMain
类,日志输出:
好了,已经启动成功了。
我们继续来看看consumer项目,在项目中,也就是调用我们服务的项目。
consumer
在consumer项目中application.xml
配置文件和provider有所区别。
<?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="dubbo-consumer"/>
<!--不需要注册到服务注册中心-->
<!-- 通过url直接调用-->
<dubbo:reference id="demoService"
interface="com.tian.dubbo.service.DemoService"
url="dubbo://127.0.0.1:20880/com.tian.dubbo.service.DemoService"/>
</beans>
这个url地址,我们在provider启动的时候,可以从日志中找到。
日志文件和provider一样,然后就是ConsumerMain
启动类了。
package com.tian.dubbo;
import com.tian.dubbo.service.DemoService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class ConsumerMain {
public static void main(String[] args) {
DemoService demoService = null;
ApplicationContext context = new ClassPathXmlApplicationContext
("classpath:META-INF/spring/application.xml");
demoService = context.getBean(DemoService.class);
//调用服务
System.out.println(demoService.sayHello("tian"));
}
}
前面,我们已经把provider成功启动了,下面我们就来启动ConsumerMain
。
从日志可以看出我们已经成功调用了provider,我们再来看看provider的日志输出:
也成功的输出了我们想要的。
到此,一个简单的入门无注册中心(通过url直接调用)的方式就完成了。
url在开发联调的时候还是很拥有的哦,因为它摆脱了对注册中心的依赖。
demo案例2
前面我们已经演示完了无注册中心,下面我们来演示有注册中心。
Dubbo
目前差不多能支持市面上所有的注册中心:
- consul
- zookeeper
- eureka
- redis
- etcd
- nacos
- ....
我们在实际开发中,Dubbo
注册中心大部分都是使用Zookeeper
和Nacos
。
下面们基于Zookeeper
来演示(nacos类似,下面会说到)。
代码层面
我基于前面的案例进行改造。改造只需要调整两个地方:
- consumer和provider中添加
pom
依赖 application.xml
中添加注册中心
pom
依赖
我们需要在前面demo中consumer和provider的pom.xml
中添加Zookeeper
的依赖:
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-dependencies-zookeeper</artifactId>
<version>3.0.4</version>
<type>pom</type>
</dependency>
provider端
在provider项目中我们需要调整:
代码语言:javascript复制<dubbo:registry address="N/A"/>
改成:
代码语言:javascript复制<dubbo:registry address="zookeeper://127.0.0.1:2181" timeout="10000"/>
这个timeout建议配上,我这里其实没必要配,因为
dubbo
服务和Zookeeper
都在我本地。
然后我们启动provider项目:
看到我们的项目已经启动成功,并且已经注册到Zookeeper
上了。
我们可以使用Zookeeper
的可视化工具,看看注册上去的信息。
我们再看看consumer端的调整。
consumer端
我们需要在application.xml
中添加
<dubbo:registry address="zookeeper://127.0.0.1:2181" timeout="10000"/>
同时,去掉reference
中的url:
<dubbo:reference id="demoService"
interface="com.tian.dubbo.service.DemoService"
url="dubbo://127.0.0.1:20880/com.tian.dubbo.service.DemoService"/>
因为是通过Zookeeper
注册中心拿到地址,所以这里的url就可以去掉了。
最后,启动ConsumerMain
类:
可以看到我们也成功调用服务,另外也有大量的Zookeeper
日志。
到此,说明,我们的Zookeeper
为注册中心的demo案例也成功了。
注意:provider和consumer项目都需要依赖相关jar包(api、zookeeper、dubbo)
其他
关于Nacos
,我们这里就不演示了,因为太简单了,如果你把Nacos
搭建好了后,直接配置就好了。
<dubbo:registry address="nacos://127.0.0.1:8848" timeout="10000"/>
就是把address地址改一下就可以了。
Nacos 的演示,我们下一篇文章中见。
总结
本文分享了Dubbo
入门案例的两个版本:无注册中心和Zookeeper
注册中心。
也啰嗦了一些Dubbo
废话(其实官网都有的)。
好了,今天咱们就分享到这里!
参考:Dubbo官网和杨正四《Dubbo源码解读与实战》