Duboo 在 Java 后端分布式领域与 SpringCloud 平分天下,那么就跟随这篇文章来了解下吧!
简介
Apache Dubbo™ 是一款高性能 Java RPC 框架。
Dubbo 提供了三大核心能力:
- 面向接口的远程方法调用
- 智能容错和负载均衡
- 服务自动注册和发现
架构
Dubbo 架构如下图所示:

节点角色说明
在 Dubbo 架构图中,存在了五个角色:
| 角色 | 说明 |
|---|---|
Provider |
暴露服务的服务提供方 |
Consumer |
调用远程服务的服务消费方 |
Registry |
服务注册与发现的注册中心 |
Container |
承载服务的运行容器 |
Monitor |
统计服务的调用次数和调用时间的监控中心 |
角色调用关系
在 Dubbo 架构中,各角色的调用关系是怎样的呢?
调用流程步骤如下:
- ① 服务容器
Container负责启动,加载,运行服务提供者Provider - ② 服务提供者
Provider在启动时,向注册中心Registry注册自己提供的服务 - ③ 服务消费者
Consumer在启动时,向注册中心Registry订阅自己所需的服务 - ④ 注册中心
Registry返回服务提供者地址列表给消费者,若有变更,注册中心将基于长连接推送变更数据给消费者 - ⑤ 服务消费者
Consumer从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,若调用失败,再选另一台调用 - ⑥ 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心
Monitor
快速入门
分析
从前文可知,Dubbo 包括以下角色:
Container:服务运行容器Registry:注册中心Provider:服务提供者Consumer:服务消费者Monitor:监控中心
下面简单分析一下:
- 对于
Container而言,默认将使用 Spring 上下文来加载 Dubbo - 对于
Registry而言,可以使用 ZooKeeper 作为注册中心 - 对于
Provider及Consumer,需要自己提供 - 对于
Monitor而言,可以使用 Dubbo 官方的 dubbo-admin 来进行监控
总的来说,要想创建一个 Dubbo 项目,我们:
- (必要条件)需要借助 Spring 框架来搭建 Dubbo 服务
- (必要条件)需要使用 ZooKeeper 作为注册中心(因为 Dubbo 本身并不提供注册中心)
- (必要条件)自身实现
Provider及Consumer - (可选)若需要监控 Dubbo 的话,可以借助 Dubbo 官方提供的 dubbo-admin
下面思考一个问题:Provider及Consumer到底是什么?
其实呀,它们就是将曾经单体项目的Service层和Web层单独抽离出来的部分。
因此,一个微服务应包括:
Api:对应Service,即定义接口,需要被Provider及Consumer依赖Provider:对应ServiceImpl,即实现接口,需要依赖定义的接口(Api)Consumer:对应Controller,即调用接口,需要依赖定义的接口(Api)
前置条件
对于后续 demo ,其前置条件为已配置并启动了 ZooKeeper(此处其端口为 2181)
创建方式
下面将分别通过 Maven 创建普通的 Spring 项目及 SpringBoot 项目。
前文分析过,一个微服务应该创建 3 个项目:
ApiProviderConsumer
其他角色,要么在配置文件中配置(Registry),要么另起一个项目运行(Monitor)
以下项目目录构造均为:1
2
3
4
5.
├── dubbo-demo-api
├── dubbo-demo-consumer
├── dubbo-demo-provider
└── pom.xml
Spring XML 方式
API
在dubbo-demo-api项目创建包及一个接口即可:1
2
3
4
5package cn.lovike.dubbo.demo.service;
public interface IUserService {
String saySomething(String s);
}
注意哦:其pom.xml文件需要配置打成jar包。
Provider
① 创建dubbo-demo-provider项目后,在pom.xml文件加入以下依赖:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33<dependencies>
<dependency>
<groupId>cn.lovike</groupId>
<artifactId>dubbo-demo-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.5.8</version>
</dependency>
<!-- zkclient -->
<dependency>
<groupId>com.github.sgroschupf</groupId>
<artifactId>zkclient</artifactId>
<version>0.1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.30</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>
② 前面依赖了Api项目,现在编写其实现类:1
2
3
4
5
6
7
8
9
10
11package cn.lovike.dubbo.demo.service.impl;
import cn.lovike.dubbo.demo.service.IUserService;
public class UserServiceImpl implements IUserService {
public String saySomething(String s) {
return "hello " + s + " by dubbo!!!";
}
}
③ 下面进行相关配置:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<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">
<!-- 1.定义 dubbo 应用名称 -->
<dubbo:application name="dubbo-provider"/>
<!-- 2.定义 dubbo 协议端口 -->
<dubbo:protocol port="28800"/>
<!-- 3.定义 duboo 使用的注册中心及其地址 -->
<dubbo:registry protocol="zookeeper" address="localhost:2181"/>
<!-- 4.定义服务提供者 -->
<bean id="userService" class="cn.lovike.dubbo.demo.service.impl.UserServiceImpl"/>
<!-- 5.声明暴露的接口 -->
<dubbo:service interface="cn.lovike.dubbo.demo.service.IUserService" ref="userService"/>
</beans>
Consumer
① 创建dubbo-demo-consumer项目后,在pom.xml文件加入以下依赖:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33<dependencies>
<dependency>
<groupId>cn.lovike</groupId>
<artifactId>dubbo-demo-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.5.8</version>
</dependency>
<!-- zkclient -->
<dependency>
<groupId>com.github.sgroschupf</groupId>
<artifactId>zkclient</artifactId>
<version>0.1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.30</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>
② 编写配置文件(重点在dubbo:reference):1
2
3
4
5
6
7
8
9
10
11
<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">
<!-- 1.定义 dubbo 应用名称 -->
<dubbo:application name="dubbo-consumer"/>
<!-- 2.定义 duboo 使用的注册中心及其地址 -->
<dubbo:registry protocol="zookeeper" address="localhost:2181"/>
<!-- 3.生成远程服务代理,可以和本地 bean 一样使用 IUserService -->
<dubbo:reference interface="cn.lovike.dubbo.demo.service.IUserService" id="userService"/>
</beans>
以上只需要三个标签,比提供者配置少了dubbo protocol标签的配置。
③ 编写消费者代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22package cn.lovike.dubbo.demo;
import cn.lovike.dubbo.demo.service.IUserService;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.io.IOException;
/**
* @author lovike
* @since 2020-06-07
*/
public class Consumer {
public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
new String[] {"classpath:dubbo-consumer.xml"});
context.start();
IUserService userService =(IUserService) context.getBean("userService");
String something = userService.saySomething(" read msg ");
System.out.println(something);
System.in.read(); // 按任意键退出
}
}
测试
测试时,首先运行提供者,控制台将打印:1
Dubbo 服务发布成功!
其次,运行消费者,控制台将打印:1
hello read msg by dubbo!!!
SpringBoot 方式
SpringBoot 项目与 Spring XML 类似,只是都将相关配置统一在application.yml文件定义或者使用注解注入相关实现。
Api
与前文类似,略。
Provider
Consumer
负载均衡
什么是负载均衡?
LoadBalance ,即负载均衡,它的职责是将网络请求或者其他形式的负载“均摊”到不同的机器上,避免集群中部分服务器压力过大,而另一些服务器比较空闲的情况。
通过负载均衡,可以让每台服务器获取到适合自己处理能力的负载。在为高负载服务器分流的同时,还可以避免资源浪费,一举两得。
负载均衡可分为软件负载均衡和硬件负载均衡。
在我们日常开发中,一般很难接触到硬件负载均衡。但软件负载均衡还是可以接触到的,比如 Nginx。
Dubbo 中的负载均衡
在 Dubbo 中,也有负载均衡的概念和相应的实现。
Dubbo 需要对服务消费者的调用请求进行分配,避免少数服务提供者负载过大。服务提供者负载过大,会导致部分请求超时。因此将负载均衡到每个服务提供者上,是非常必要的。
Dubbo 提供了 4 种负载均衡实现:
RandomLoadBalance:基于权重随机算法LeastActiveLoadBalance:基于最少活跃调用数算法ConsistentHashLoadBalance:基于 hash 一致性RoundRobinLoadBalance:基于加权轮询算法
以上的具体介绍请参考 Dubbo 官网负载均衡
负载均衡配置
若不指定负载均衡,默认使用随机负载均衡。
当然,我们也可以根据自己的需要,显式指定一个负载均衡。
Dubbo 中可以在多个地方类来配置负载均衡,比如 Provider 端,Consumer端,服务级别,方法级别等。
服务端服务级别
以下配置使得服务端对应服务的所有方法都使用 roundrobin 负载均衡。
XML 配置方式
1 | <dubbo:service interface="..." loadbalance="roundrobin" /> |
YML 配置(SpringBoot)
1 | dubbo: |
注解配置(SpringBoot)
1 | (version = "${user.service.version}",loadbalance="roundrobin") |
客户端服务级别
以下配置使得客户端对应服务的所有方法都使用 roundrobin 负载均衡。
XML 配置方式
1 | <dubbo:reference interface="..." loadbalance="roundrobin" /> |
YML 配置(SpringBoot)
1 | dubbo: |
注解配置(SpringBoot)
1 |
|
服务端方法级别
1 | <dubbo:service interface="..."> |
只有该服务的 hello 方法使用 roundrobin 负载均衡。
客户端方法级别
1 | <dubbo:reference interface="..."> |
只有该服务的 hello 方法使用 roundrobin 负载均衡。
注意点
和 Dubbo 其他的配置类似,多个配置是有覆盖关系的:
- 方法级优先,接口级次之,全局配置再次之。
- 如果级别一样,则消费方优先,提供方次之。
所以,上面 4 种配置的优先级是:
- 客户端方法级别配置
- 客户端接口级别配置
- 服务端方法级别配置
- 服务端接口级别配置
高速序列化
Dubbo RPC 是 Dubbo 体系中最核心的一种高性能、高吞吐量的远程调用方式,可以称之为多路复用的 TCP 长连接调用:
- 长连接:避免了每次调用新建 TCP 连接,提高了调用的响应速度
- 多路复用:单个 TCP 连接可交替传输多个请求和响应的消息,降低了连接的等待闲置时间,从而减少了同样并发数下的网络连接数,提高了系统吞吐量
Dubbo RPC 主要用于两个 Dubbo 系统之间的远程调用,特别适合高并发、小数据的互联网场景。而序列化对于远程调用的响应速度、吞吐量、网络带宽消耗等同样也起着至关重要的作用,是我们提升分布式系统性能的最关键因素之一。
Dubbo 中支持的序列化方式:
- Dubbo 序列化:阿里尚未开发成熟的高效 java 序列化实现,阿里不建议在生产环境使用它
- Hessian2 序列化:hessian 是一种跨语言的高效二进制序列化方式。但这里实际不是原生的 hessian2 序列化,而是阿里修改过的 hessian lite,它是 dubbo RPC 默认启用的序列化方式
- Json 序列化:目前有两种实现,一种是采用的阿里的 fastjson 库,另一种是采用 dubbo 中自己实现的简单 json 库,但其实现都不是特别成熟,而且 json 这种文本序列化性能一般不如上面两种二进制序列化
- Java 序列化:主要是采用 JDK 自带的 Java 序列化实现,性能很不理想。
在通常情况下,这四种主要序列化方式的性能从上到下依次递减。
对于 dubbo RPC 这种追求高性能的远程调用方式来说,实际上只有 1、2 两种高效序列化方式比较般配,而第 1 个 dubbo 序列化由于还不成熟,所以实际只剩下 2 可用,因此 dubbo RPC 默认采用 hessian2 序列化。
由于 hessian 是一个比较老的序列化实现了,而且它是跨语言的,所以不是单独针对 Java 进行优化的。而 dubbo RPC 实际上完全是一种 Java to Java 的远程调用,其实没有必要采用跨语言的序列化方式(当然肯定也不排斥跨语言的序列化)。
最近几年,各种新的高效序列化方式层出不穷,不断刷新序列化性能的上限,最典型的包括:
- 专门针对 Java 语言的:Kryo,FST 等等
- 跨语言的:Protostuff,ProtoBuf,Thrift,Avro,MsgPack 等等
这些序列化方式的性能多数都显著优于 hessian2(甚至包括尚未成熟的 dubbo 序列化)
有鉴于此,我们为 dubbo 引入 Kryo 和 FST 这两种高效 Java 序列化实现,来逐步取代 hessian2。
其中,Kryo 是一种非常成熟的序列化实现,已经在 Twitter、Groupon、Yahoo 以及多个著名开源项目(如 Hive、Storm)中广泛的使用。而 FST 是一种较新的序列化实现,目前还缺乏足够多的成熟使用案例。
在面向生产环境的应用中,目前更优先选择 Kryo。
在 SpringBoot Dubbo 项目中集成 Kryo
在 Provider 和 Consumer 项目启用 Kryo 高速序列化功能,两个项目的配置方式相同。
首先,在pom.xml文件增加 Kryo 依赖:1
2
3
4
5<dependency>
<groupId>de.javakaffee</groupId>
<artifactId>kryo-serializers</artifactId>
<version>0.42</version>
</dependency>
其次,在application.yml进行相应配置:1
2
3
4
5
6dubbo:
protocol:
id: dubbo
name: dubbo
port: 20880
serialization: kryo
最后可以去注册被序列化类。
要让 Kryo 和 FST 完全发挥出高性能,最好将那些需要被序列化的类注册到 dubbo 系统中,例如,我们可以实现如下回调接口:1
2
3
4
5
6
7
8
9
10
11
12public class SerializationOptimizerImpl implements SerializationOptimizer {
public Collection<Class> getSerializableClasses() {
List<Class> classes = new LinkedList<Class>();
classes.add(BidRequest.class);
classes.add(BidResponse.class);
classes.add(Device.class);
classes.add(Geo.class);
classes.add(Impression.class);
classes.add(SeatBid.class);
return classes;
}
}
在注册这些类后,序列化的性能可能被大大提升,特别针对小数量的嵌套对象的时候。
当然,在对一个类做序列化的时候,可能还级联引用到很多类,比如 Java 集合类。 针对这种情况,Dubbo 已经自动将 JDK 中的常用类进行了注册,所以不需要重复注册它们(当然你重复注册了也没有任何影响),包括:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30GregorianCalendar
InvocationHandler
BigDecimal
BigInteger
Pattern
BitSet
URI
UUID
HashMap
ArrayList
LinkedList
HashSet
TreeSet
Hashtable
Date
Calendar
ConcurrentHashMap
SimpleDateFormat
Vector
BitSet
StringBuffer
StringBuilder
Object
Object[]
String[]
byte[]
char[]
int[]
float[]
double[]
由于注册被序列化的类仅仅是出于性能优化的目的,所以即使忘记注册某些类也没有关系。
事实上,即使不注册任何类,Kryo 和 FST 的性能依然普遍优于 hessian 和 dubbo 序列化。
Hystrix
详细介绍见 SpringCloud 一文,与 Dubbo 整合暂略。