📋 Dubbo 知识整理
在微服务知识整理的过程中,断断续续看了好些文档和视频教程,写下的笔记也很杂乱,现决定稍微专注一点,一鼓作气将这块知识整理完整。
基础知识
RPC(Remote Procedure Call)意为[远程过程调用]{.blue}。
RPC 是用来干什么的?
在微服务架构中,一个完整的项目,通常会被拆分成多个微型服务,但在业务过程中,这些服务彼此之间是存在调用关系的,对于这种跨服务之间的调用,通常就需要通过 RPC 来完成。简单来说,RPC 的目的,就是为了让程序能够像调用本地方法一样调用远程方法。
既然有 HTTP 请求,为什么还要用 RPC 调用?
- RPC 的出现是早于 HTTP 的,RPC 主要是基于 TCP/IP 协议,而 HTTP 是基于 HTTP 协议的。
- HTTP 和 RPC 不是两个可以对等的比较概念。因为 HTTP 是一个通信协议,而 RPC 是一个完整的远程调用方案。
- 可以更简单地理解,HTTP 其实就是一种 RPC 的一种,它拥有更加具体的实现,但对于微服务之间的远程调用,则不太适用。
- 知乎问答上有更加详细的讨论,可点击此处前往查看。
RPC 的原理是什么?
要明白 RPC 的原理,首先应当了解如下几个概念:
- 客户端:远程方法的调用者。
- 客户端 Stub:它主要负责将调用的方法、类、方法参数等信息传递到服务端。
- 网络传输:网络传输就是客户端将要调用的信息输到服务端,然后服务端执行完之后再把返回结果通过网络传输给你传输回来。网络传输即是指这种双向的数据传输过程。
- 服务端 Stub:它的主要作用是接收客户端的请求,并指定对应的方法进行处理,最终将处理结果返回给客户端。
- 服务端:远程方法的提供者。
RPC 原理示意图:
- 客户端以本地调用方式调用远程服务。
- 客户端 stub 接收到调用后,将方法、参数等组装成能够进行网络传输的消息体(序列号):
RpcRequest
。 - 客户端 stub 找到远程服务地址,并将消息发送到服务端。
- 服务端 stub 接收到消息并反序列化为 Java 对象:RpcRequest。
- 服务端 stub 根据 RpcRequest 中的类、方法、方法参数等信息调用本地方法。
- 服务端 stub 得到方法执行结果,并将其序列号为
RpcResponse
对象,并通过网络传输发送到客户端。 - 客户端 stub 接收到服务端的响应数据,并反序列化为 RpcResponse 对象。
Dubbo 概述
Dubbo 是什么?
Dubbo 是阿里巴巴开源的一款高性能、轻量级的 RPC 框架,它提供服务自动注册、自动发现等高效服务治理方案,可以和 Spring 框架无缝集成。
Dubbo 提供了六大核心能力:
为什么要用 Dubbo?
随着服务化的进一步发展,服务越来越多,服务之间的调用和依赖关系也越来越复杂,诞生了面向服务的架构体系(SOA)。同时,也由此衍生出了一系列相应的技术,如对服务提供、服务调用、连接处理、通信协议、序列化方式、服务发现、服务路由、日志输出等行为进行封装的服务框架。就这样为分布式系统的服务治理框架就出现了,这也是 Dubbo 最初的产生。
Dubbo 的核心架构
下图是 Dubbo 官网描述的基本原理图:
其中,Dubbo 默认使用 zookeeper 作为其注册中心,监控中心在 Dubbo 的架构中并非必须。
快速入门
如前文所述,Dubbo 默认使用 zookeeper 作为其注册中心,因此,在正式入门前,需要了解如何安装 zookeeper,zookeeper 安装及相关基础知识,可参考文章《Zookeeper 基础教程》。
集成实例代码已上传到 Github 仓库,可自行查看,自出不进行详述:
高级特性
Dubbo Admin
-
介绍
Dubbo Admin 是一个通过 vue + springboot 实现的 Dubbo 可视化管理界面。
-
下载
-
后端部署
git clone https://github.com/apache/dubbo-admin.git cd dubbo-admin-server mvn clean package cd target java -jar dubbo-admin-server-0.1.jar
-
前端部署
cd dubbo-admin-ui npm install npm run dev
-
注意事项
- Dubbo Admin 前端依赖于 node.js 环境。
- Dubbo Admin 默认账号密码均为 root。
- Dubbo 默认使用 20880 端口连接注册中心,同一主机多个 Dubbo 客户端时应注意避免端口冲突。
- 此部分尽量简化,如有疑问,可参考 Dubbo 官方提供的 Dubbo Admin 运维指南。
协议与序列化
我们把对象转换为字节序列的过程称为对象的序列化,把字节序列恢复为对象的过程称为对象的反序列化。而 Dubbo 需要进行网络通信,就必然需要实现序列化和反序列化的过程。其实现序列化的方式也很简单,只需要使传输的对象类实现 Serializable
接口即可。
public class User implements Serializable {
private Integer id;
}
🎈 Dubbo 支持的协议
Dobbo 支持的协议包括:dubbo、rmi、hessian、http、webservice、thrift、memcached、redis、rest。其中,dubbo://
是 Dobbo 默认支持的协议。
Dubbo | RMI | Hessian | Http | WebService | |
---|---|---|---|---|---|
连接数 | 单个 | 多个 | 多个 | 多个 | 多个 |
连接类型 | 长连接 | 短连接 | 短连接 | 短连接 | 短连接 |
通信协议 | TCP | TCP | HTTP | HTTP | HTTP |
传输方式 | NIO | 同步 | 同步 | 同步 | 同步 |
序列化 | Hessian | Java 标准二进制 | Hessian | 表单序列化 | SOAP 文本序列化 |
使用场景 | 大并发,小数据 | 常规 | 文件、页面 | 支持客户端或js调用 | 系统集成,跨语言 |
Dubbo 针对不同服务,可以选择使用不同的协议:
<?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://dubbo.apache.org/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<dubbo:application name="world" />
<dubbo:registry id="registry" address="10.20.141.150:9090" username="admin" password="hello1234" />
<!-- 多协议配置 -->
<dubbo:protocol name="dubbo" port="20880" />
<dubbo:protocol name="rmi" port="1099" />
<!-- 使用dubbo协议暴露服务 -->
<dubbo:service interface="com.alibaba.hello.api.HelloService" version="1.0.0" ref="helloService" protocol="dubbo" />
<!-- 使用rmi协议暴露服务 -->
<dubbo:service interface="com.alibaba.hello.api.DemoService" version="1.0.0" ref="demoService" protocol="rmi" />
</beans>
此外,Dubbo 也可以同时使用多个协议暴露服务:
<?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://dubbo.apache.org/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<dubbo:application name="world" />
<dubbo:registry id="registry" address="10.20.141.150:9090" username="admin" password="hello1234" />
<!-- 多协议配置 -->
<dubbo:protocol name="dubbo" port="20880" />
<dubbo:protocol name="hessian" port="8080" />
<!-- 使用多个协议暴露服务 -->
<dubbo:service id="helloService" interface="com.alibaba.hello.api.HelloService" version="1.0.0" protocol="dubbo,hessian" />
</beans>
超时与重试机制
通过进行如下配置,即可实现 Dubbo 服务调用的超时与重试:
<!-- 服务调用超时设置为5秒,重试3次-->
<dubbo:service interface="com.provider.service.DemoService" ref="demoService" retries="3" timeout="5000"/>
<!-- <dubbo:consumer timeout="1000"></dubbo:consumer>-->
Dubbo 超时设置可以有两种方式:
- 服务提供者端设置超时(推荐)。
- 服务消费者端设置超时,其优先级更高。
服务调用超时,Dubbo 会自动进行重试,当未指定重试次数时,默认为重试两次。
除了通过 xml 的方式指定 Dubbo 超时重试等相关参数外,也可以通过指定注解参数的方式实现:
import org.apache.dubbo.config.annotation.Service; @Service(timeout = 3000, retries = 3)
注:Dubbo 新版本中
@Service
和@Reference
已被标记为过时,并使用DubboService
和DubboReference
进行替代。
多版本
在 Dubbo 中可以为同一个服务配置多个版本,当一个接口实现,出现不兼容升级时,可以用版本号过渡,版本号不同的服务相互间不引用。
<!-- 服务提供者端 -->
<dubbo:service interface="com.foo.BarService" version="1.0.0" />
<!-- 服务消费者端 -->
<dubbo:reference id="barService" interface="com.foo.BarService" version="*" />
同样,除了使用 xml 进行配置外,多版本参数也可以通过注解的方式进行指定:
// 服务提供者端
@Service(version = "1.0.0")
// 服务消费者端
@Reference(version = "1.0.0")
负载均衡
在集群负载均衡时,Dubbo 提供了多种均衡策略,缺省为 random
随机调用。具体实现上,Dubbo 提供的是客户端负载均衡,即由 Consumer 通过负载均衡算法得出需要将请求提交到哪个 Provider 实例。
目前 Dubbo 内置了如下负载均衡算法,用户可直接配置使用:
算法 | 特性 | 备注 |
---|---|---|
RandomLoadBalance | 加权随机 | 默认算法,默认权重相同 |
RoundRobinLoadBalance | 加权轮询 | 借鉴于 Nginx 的平滑加权轮询算法,默认权重相同, |
LeastActiveLoadBalance | 最少活跃优先 + 加权随机 | 背后是能者多劳的思想 |
ShortestResponseLoadBalance | 最短响应优先 + 加权随机 | 更加关注响应速度 |
ConsistentHashLoadBalance | 一致性 Hash | 确定的入参,确定的提供者,适用于有状态请求 |
xml 配置方式:
<!-- 服务端级别 -->
<dubbo:service interface="..." loadbalance="roundrobin" />
<!-- 服务端方法级别 -->
<dubbo:service interface="...">
<dubbo:method name="..." loadbalance="roundrobin"/>
</dubbo:service>
<!-- 消费端级别 -->
<dubbo:reference interface="..." loadbalance="roundrobin" />
<!-- 消费端方法级别 -->
<dubbo:reference interface="...">
<dubbo:method name="..." loadbalance="roundrobin"/>
</dubbo:reference>
注解配置方式:
@Reference(loadbalance = "random")
@Service(loadbalance = "random")
集群容错
在集群调用失败时,Dubbo 提供了多种容错方案,缺省为 failover 重试。Dubbo 主要有如下几种集群容错模式:
- Failover:失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。可通过
retries="2"
来设置重试次数(不含第一次)。 - Failfast:快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。
- Failsafe:失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。
- Failback:失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。
- Forking:并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过
forks="2"
来设置最大并行数。 - Broadcast:广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息。
- Available:调用目前可用的实例(只调用一个),如果当前没有可用的实例,则抛出异常。通常用于不需要负载均衡的场景。
- Mergeable:将集群中的调用结果聚合起来返回结果,通常和group一起配合使用。通过分组对结果进行聚合并返回聚合后的结果,比如菜单服务,用group区分同一接口的多种实现,现在消费方需从每种group中调用一次并返回结果,对结果进行合并之后返回,这样就可以实现聚合菜单项。
- ZoneAware:多注册中心订阅的场景,注册中心集群间的负载均衡。
可以通过如下方式指定集群容错模式:
<!-- 在服务端设置 -->
<dubbo:service cluster="failsafe" />
<!-- 或在消费端设置 -->
<dubbo:reference cluster="failsafe" />
服务降级
Dubbo 可以通过服务降级功能临时屏蔽某个出错的非关键服务,并定义降级后的返回策略。
@Reference(mock = "forse:return null")
mock=force:return null
表示消费方对该服务的方法调用都直接返回 null 值,不发起远程调用。用来屏蔽不重要服务不可用时对调用方的影响。- 还可以改为
mock=fail:return+null
表示消费方对该服务的方法调用在失败后,再返回 null 值,不抛异常。用来容忍不重要服务不稳定时对调用方的影响。
面试题
碍于精力,这一部分的内容并暂时做的比较简单,部分问题也暂未给出参考答案和相关提示。作答者可根据前文中的知识内容自行总结。
后续看情况会进行更新。
-
Dubbo 是什么?{.quiz .fill}
参考 Dubbo 概述部分作答即可。
[这道题往往是作为 Dubbo 面试的预热题目,简单回答即可,等待面试官的追问。但其本质需要明确,Dubbo 本质上就是一款 RPC 框架。]{.mistake}
-
试举几个 Dubbo 支持的协议。{.quiz .fill}
Dobbo 支持的协议包括:dubbo、rmi、hessian、http、webservice、thrift、memcached、redis、rest。其中,
dubbo://
是 Dobbo 默认支持的协议。[选择几个熟知的协议作答即可。]{.mistake}
-
Dubbo 内置了哪几种服务容器?{.quiz .fill}
-
Dubbo 中有哪几种主要角色?{.quiz .fill}
-
Dubbo 服务注册与发现流程。{.quiz .fill}
-
Dubbo 的注册中心如何实现,实现方案和推荐方案。{.quiz .fill}
-
Dubbo 默认采用的通信框架是[]{.gap}。{.quiz}
- Mina
- Netty{.correct}
- Grizzly
- xSocket
{.options}
[除 xSocket 外,其他三个选项均是 Dubbo 支持的通信框架。]{.mistake}
-
Dubbo 是如何进行序列号与反序列化的?{.quiz .fill}
参考
著作権声明
本記事のリンク:https://www.chinmoku.cc/dev/java/advanced/dubbo-tutorial/
本博客中的所有内容,包括但不限于文字、图片、音频、视频、图表和其他可视化材料,均受版权法保护。未经本博客所有者书面授权许可,禁止在任何媒体、网站、社交平台或其他渠道上复制、传播、修改、发布、展示或以任何其他方式使用此博客中的任何内容。