dubbo学习

目标

  • 能够掌握Dubbo的架构体系
  • 能够掌握Dubbo的源码环境搭建及控制台使用
  • 能够掌握Dubbo与SpringBoot的集成整合
  • 能够掌握Dubbo的高阶配置运用
  • 能够掌握Dubbo的SPI机制及其实现原理
  • 能够掌握Dubbo的暴露机制, 并熟悉源码实现
  • 能够掌握Dubbo的服务发现机制, 并熟悉源码实现
  • 能够掌握Dubbo的高可用集群功能(容错、负载、治理)与实现原理
  • 能够全面掌握网络通信协议
  • 能够掌握Dubbo网络通信的底层实现

架构

运行架构:在这里插入图片描述

在这里插入图片描述
各层说明:

  • config 配置层:对外配置接口,以 ServiceConfig , ReferenceConfig 为中心,可以直接初始化配置类,也可以通过
    spring 解析配置生成配置类
  • proxy 服务代理层:服务接口透明代理,生成服务的客户端 Stub 和服
    务器端 Skeleton, 以 ServiceProxy 为中心,扩展接口为
    ProxyFactory
  • registry 注册中心层:封装服务地址的注册与发现,以服务 URL 为中
    心,扩展接口为 RegistryFactory , Registry , RegistryService
  • cluster 路由层:封装多个提供者的路由及负载均衡,并桥接注册中
    心,以 Invoker 为中心,扩展接口为 Cluster , Directory , Router , LoadBalance
  • monitor 监控层:RPC 调用次数和调用时间监控,以 Statistics 为
    中心,扩展接口为 MonitorFactory , Monitor , MonitorService
  • protocol 远程调用层:封装 RPC 调用,以 Invocation , Result 为
    中心,扩展接口为 Protocol , Invoker , Exporter
  • exchange 信息交换层:封装请求响应模式,同步转异步,以
    Request , Response 为中心,扩展接口为 Exchanger , ExchangeChannel , ExchangeClient , ExchangeServer
  • transport 网络传输层:抽象 mina 和 netty 为统一接口,以
    Message 为中心,扩展接口为 Channel , Transporter , Client , Server , Codec
  • serialize 数据序列化层:可复用的一些工具,扩展接口为
    Serialization , ObjectInput , ObjectOutput , ThreadPool

和springboot的集成

消费端,添加@DubboReference
服务端,添加@DubboService

剩余的就是pom文件和application.yml配置文件

dubbo高阶

1. 不同配置覆盖规则

  • 方法级别优先,接口级次之,全局配置再次之;
  • 级别一样,则消费方优先,提供方次之

2. 属性配置优先级

优先级规则从高到低:

  • JVM -D参数 -Ddubbo.protocol.port=20881
  • xml配置(application.yml/application.properties)重写dubbo.properties中参数
dubbo:
 protocol:
   name: dubbo # 通讯协议
   port: 20882  # dubbo服务提供方的端口,该默认值20880  -1代表随机分配
  • properties默认配置(dubbo.properties),仅仅作用于两者都没配置,一般配置全局公共配置 dubbo.protocol.port=20883

3. 重试与容错机制

3.1 容错机制
  • failfast cluster 快速失败,用于非幂等的写操作,如新增记录
  • failsafe cluster 失败安全,出现异常,直接忽略,用于审计日志
  • failback cluster 失败自动恢复,后台记录失败请求,定时重发,通常用于消息通知操作
  • forking cluster 并行调用,实时性要求较高的读操作,耗性能
  • broadcast cluster 广播调用所有提供者,逐个调用,用于通知所有提供者更新缓存或日志等本地资源信息
3.2 调整客户端重试次数
@DubboReference(
          version = "${dubbo.spring.provider.version}",
          retries = 3
    )
    private OrderService orderService;

这个版本好像没有这个dubbo.spring.provider.version参数,稍后看下

3.3 修改服务端超时时间
@Configuration
public class DubboConfig {


    @Bean
    public ProviderConfig providerConfig() {
        ProviderConfig providerConfig = new ProviderConfig();
        providerConfig.setTimeout(1000);
        return  providerConfig;
    }
}
3.4 多版本控制

通过-Ddubbo.spring.provider.version = 2.0.0 -Dserver.port=18082 -Ddubbo.protocol.port=20881 修改配置

3.5 本地存根调用

在这里插入图片描述
把 Stub 暴露给用户,Stub 可以决定要不要去调 Proxy

在api中增加对应Stub类实现同样的接口,进行参数校验

3.6 负载均衡机制

默认随机
支持的负载均衡策略,实现AbstractLoadBalance接口,包括:

  • Random LoadBalance
  • RoundRobin LoadBalance 轮询
  • LeastActive LoadBalance 最少活跃调用数
  • ConsistentHash LoadBalance 一致性哈希
  • ShortestResponseLoadBalance 最短响应时间负载均衡(2.7.7以后增加)
3.7 服务降级

看不懂,之后再填坑

3.8 并发与连接控制

可在服务级别和方法级别控制并发

  • 服务端
    <dubbo:service interface="com.foo.BarService" executes="10" />
  • 客户端
    <dubbo:reference interface="com.foo.BarService" actives="10" />

连接控制

  • 服务端
    <dubbo:provider protocol="dubbo" accepts="10" />
  • 客户端
    <dubbo:reference interface="com.foo.BarService" connections="10" />

3. dubbo的SPI机制

3.1 spi介绍

SPI 全称为 Service Provider Interface,是一种服务发现机制。本质
是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加
载实现类。这样可以在运行时,动态为接口替换实现类。

spi默认是在ClassPath路径下的META-INF/services文件夹查找接口的实现类。Java SPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制。

调用过程

  • 应用程序调用ServiceLoader.load方法,创建一个新的ServiceLoader,并实例化该类中的成员变量
  • 应用程序通过迭代器接口获取对象实例,ServiceLoader先判断成员变量providers对象中(LinkedHashMap<String,S>类型)是否有缓存实例对象,如果有缓存,直接返回。如果没有缓存,执行类的装载

缺点

  • 不能按需加载。虽然 ServiceLoader 做了延迟载入,但是基本只能通过
    遍历全部获取
  • 加载不到实现类时抛出并不是真正原因的异常,错误很难定位

3.2 dubbo的spi

dubbo重新实现了一套功能更强的SPI机制。相关逻辑封装在ExtensionLoader中。通过键值对的方式配置在如META-INF/dubbo/com.itheima.spi.dubbo.Robot的配置文件中,其中key自己指定,value为实现类的全路径,每一对称为dubbo的扩展点。

dubbo的SPI优点:

  1. 可实现按需加载
  2. 支持AOP,将实现类包装在Wrapper中,wrapper中实现公共增强逻辑
  3. 支持IOC,能通过set方法注入其他扩展点

ExtensionLoader的整个执行逻辑:

getExtension(String name) #根据key获取拓展对象 
	-->createExtension(String name) #创建拓展实例
		 -->getExtensionClasses #根据路径获取所有的拓展类 
			 -->loadExtensionClasses #加载拓展类
			 	 -->cacheDefaultExtensionName #解析@SPI注解
			 -->loadDirectory #方法加载指定文件夹配置文件
			 	 -->loadResource #加载资源
			 	 	-->loadClass #加载类,并通过 loadClass 方法对类进行缓存

其中createExtension方法逻辑较为复杂,包含

  1. 通过getExtensionClasses获取所有的拓展类
  2. 通过反射创建拓展对象
  3. 向拓展对象注入依赖
  4. 将拓展对象包裹在相应的wrapper对象中

Dubbo IOC 是通过 setter 方法注入依赖。Dubbo 首先会通过反射获取到实例的所有方法,然后再遍历方法列表,检测方法名是否具有 setter 方法特征。若有,则通过 ObjectFactory 获取依赖对象,最后通过反射调用 setter 方法将依赖设置到目标对象中。

objectFactory 变量的类型为 AdaptiveExtensionFactory,AdaptiveExtensionFactory 内部维护了一个 ExtensionFactory 列表,用于存储其他类型的 ExtensionFactory。Dubbo 目前提供了两种 ExtensionFactory,分别是 SpiExtensionFactory 和 SpringExtensionFactory。前者用于创建自适应的拓展,后者是用于从 Spring 的 IOC 容器中获取所需的拓展。

dubbo的IOC目前只支持setter方式注入。

3.3 动态增强

Dubbo中,有一种特殊的类,被称为Wrapper类。通过装饰者模式,使用包装类包装原始的扩展点实例。在原始扩展点实现前后插入其他逻辑,实现AOP功能。

3.3.1 装饰者模式

装饰者模式:在不改变原类文件以及不使用继承的情况下,动态地将责任附加到对象上,从而实现动态拓展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。

参与者:

  • Component:装饰者和被装饰者共同的父类,是一个接口或者抽象类,用来定义基本行为
  • ConcreteComponent:定义具体对象,即被装饰者
  • Decorator:抽象装饰者,继承自Component,从外类来扩展ConcreteComponent。对于ConcreteComponent来说,不需要知道Decorator的存在,Decorator是一个接口或抽象类
  • ConcreteDecorator:具体装饰者,用于扩展ConcreteComponent
3.3.2. dubbo的aop

有对应的装饰类,并添加添加拓展点配置文件METAINF/dubbo/com.itheima.dubbo.Robot,执行发现会先调用装饰者增强,再调用目标方法完成业务逻辑。

源码部分为IOC的injection

Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (CollectionUtils.isNotEmpty(wrapperClasses)) {
     //遍历Wrapper类型的Class
     for (Class<?> wrapperClass : wrapperClasses) {
         /**
          * 将当前实例包装到Wrappe中,通过构造注入,往Wrapper中注入依赖,通过Wrapper包装实例,从而在Wrapper的方法中进行方法增强;是实现AOP的关键
          */
         instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
     }
 }

3.4 动态编译

3.4.1 SPI的自适应

有些拓展不希望在框架启动阶段被加载,而是希望在拓展方法被调用时,根据运行时参数进行加载,即根据参数动态加载实现类。
这种在运行时,根据方法参数才动态决定使用具体的拓展,在dubbo中就叫做扩展点自适应实例。其实是一个扩展点的代理,将扩展的选择从Dubbo启动时,延迟到RPC调用时。Dubbo中每一个扩展点都有一个自适应类,如果没有显式提供,Dubbo会自动为我们创建一个,默认使用Javaassist。

实现逻辑:

  1. 首先 Dubbo 会为拓展接口生成具有代理功能的代码;
  2. 通过 javassist 或 jdk 编译这段代码,得到 Class 类;
  3. 通过反射创建代理类;
  4. 在代理类中,通过URL对象的参数来确定到底调用哪个实现类;
3.4.2 javaassist

是一个开源的分析、编辑和创建Java字节码的类库。是jboss的一个子项目,直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。
使用javassist可以方便的在运行时,按需动态的创建java对象,并执行内部方法。而这也是dubbo中动态编译的核心

3.4.3 源码

Adaptive注解

可注解在类或方法上。

  • 类: 在dubbo上,只有两个类被Adaptive注解,分别是AdaptiveCompiler和AdaptiveExtensionFactory。表示拓展的加载逻辑由人工编码完成。
  • 方法:一般都在方法上,表示拓展的加载逻辑由框架自动生成,根据参数URL调用对应扩展点实现。如Protocol的SPI类有很多扩展位置类,设计Protocol$Adaptive的类,通过ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(
    spi类);来提取对象

获取自适应拓展类

每个扩展点都有个自适应类,如果没有显式提供,dubbo会自动创建一个,默认使用Javaassist。

getAdaptiveExtension
	-->createAdaptiveExtension
	 -->getAdaptiveExtensionClass
	  -->getExtensionClasses
	   -->createAdaptiveExtensionClass 先生成自适应的java源码,再编译为字节码
	    -->createAdaptiveExtensionClassCode 生成自适应的java源码
	    -->compile  默认实现为Javaassist

createAdaptiveExtensionClassCode方法使用一个StringBuilder来构建自适应的java源码。

4. 服务暴露和发现

dubbo如何将provider的服务暴露出去,consumer如何获取provider的相关信息?

4.1 spring的自定义schema

dubbo Provider 在容器启动后开始暴露服务,并准备接受请求处理。所以
可以理解为容器的启动带动dubbo Provider开始工作。

Dubbo 如何实现自定义 XML 被 Spring 加载读取呢?

Spring 2.0 开始,Spring 开始提供了一种基于 XML Schema 格式扩展机
制,用于自定义和配置 bean,从而被spring框架所解析。

使用Spring XML Schema 扩展机制的步骤:

  1. 创建配置属性的JavaBean对象
  2. 创建一个 XML Schema 文件,描述自定义的合法构建模块,也就是xsd
    文件。
  3. 自定义处理器类,并实现 NamespaceHandler 接口。
  4. 自定义解析器,实现 BeanDefinitionParser 接口(最关键的部分)。
  5. 编写Spring.handlers和spring.schemas文件配置所有部件

4.2 dubbo的解析

Dubbo所有的组件都是由 DubboBeanDefinitionParser 解析,
并通过registerBeanDefinitionParser方法来注册到spring中最后解析对应的对
象。这些对象中我们重点关注的有以下三个:

  • ServiceBean:服务提供者暴露服务的核心对象
  • ReferenceBean:服务消费者发现服务的核心对象
  • RegistryConfig:定义注册中心的核心配置对象

4.3 服务暴露机制

4.3.1 术语
  • invoker实体域, Dubbo 的核心模型,其它模型都向它靠扰,
    或转换成它,它代表一个可执行体,可向它发起 invoke 调用,它有可
    能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实
    现。在服务提供方,Invoker用于调用服务提供类。在服务消费方,
    Invoker用于执行远程调用
  • Protocol 服务域,是 Invoker 暴露和引用的主功能入口,它负责
    Invoker 的生命周期管理
    • export:暴露远程服务
    • refer:引用远程服务
  • proxyFactory:获取一个接口的代理类
    • getInvoker:针对server端,将服务对象,如DemoServiceImpl包装成一个Invoker对象
    • getProxy:针对client端,创建接口的代理对象,例如DemoService接口的代理实现。
  • Invocation 是会话域,它持有调用过程中的变量,比如方法名,参数等
4.3.2 流程机制

在这里插入图片描述
服务暴露分为两大部分 ,
1、第一步将持有的服务实例通过代理转换成 Invoker,
2、第二步会把 Invoker 通过具体的协议 ( 比如 Dubbo ) 转换成Exporter

4.3.3 源码分析

导出入口

服务导出的入口方法是 ServiceBean 的 onApplicationEvent。
onApplicationEvent 是一个事件响应方法,该方法会在收到 Spring 上下文刷新
事件后执行服务导出操作。

onApplicationEvent方法调用export,最终进入到ServiceConfig.doExportUrls 导出服务方法
URL 是 Dubbo 配置的载体,通过 URL 可让 Dubbo 的各种配置在各个模块之间传递。

服务导出分为导出到本地 (JVM),和导出到远程。
scope = none,不导出服务
scope != remote,导出到本地
scope != local,导出到远程

服务导出之前,均需要先创建Invoker,这是一个很重要的步骤。Invoker 是由 ProxyFactory 创建而来,Dubbo 默认的 ProxyFactory 实现类是JavassistProxyFactory。JavassistProxyFactory 创建了一个继承自 AbstractProxyInvoker 类的匿名对象,并覆写了抽象方法 doInvoke。

导出到远程

主要看导出到远程:
导出服务到远程包含了服务导出与服务注册两个过程。先来分析服务导出逻辑。

主要做如下一些操作:

  1. 调用 doLocalExport 导出服务
  2. 向注册中心注册服务
  3. 向注册中心进行订阅 override 数据
  4. 创建并返回 DestroyableExporter

其中doLocalExport 调用 Protocol 的 export 方法,假设运行时协议为
dubbo,此处的 protocol 变量会在运行时加载 DubboProtocol,并调用
DubboProtocol 的 export 方法。

开启netty服务

DubboProtocol 的 export 方法中,会调用openServer方法,该方法会调用createServer创建服务器实例。

createServer 包含三个核心的逻辑。第一是检测是否存在 server 参数所代表的 Transporter 拓展,不存在则抛出异常。第二是创建服务器实例。第三是检测是否支持 client 参数所表示的 Transporter 拓展,不存在也是抛出异常。

创建服务器实例的操作,主要是调用 HeaderExchanger 的 bind 方法创建 ExchangeServer 实例。该方法调用Transporters 的 bind 方法。调用getTransporter() 方法获取的 Transporter 是在运行时动态创建
的,类名为 TransporterAdaptive,默认为 NettyTransporter。会调用 NettyTransporter.bind(URL, ChannelHandler) 方法。创建一个
NettyServer 实例。调用 NettyServer.doOPen() 方法,服务器被开启,服务
也被暴露出来了。

4.3.4 服务注册

RegistryProtocol 的 export 方法,其中服务注册为register方法。包含两步操作,第一步是获取注册中心实例,第二步是向注册中心注册服务。

createRegistry 创 建 Registry。在此方法中就是通过 new ZookeeperRegistry(url, zookeeperTransporter) 实例化一个注册中心,会创建CuratorZookeeperClient。

4.3.5 总结
  1. 在有注册中心,需要注册提供者地址的情况下,ServiceConfig 解析出的 URL 格式为: registry:// registry- host/org.apache.dubbo.registry.RegistryService? export=URL.encode(“dubbo://service-host/{服务名}/{版本号}”)
  2. 基于 Dubbo SPI 的自适应机制,通过 URL registry:// 协议头识别,就调用 RegistryProtocol#export() 方法
    • 将具体的服务类名,比如 DubboServiceRegistryImpl ,通过 ProxyFactory 包装成 Invoker 实例
    • 调用 doLocalExport 方法,使用 DubboProtocol 将 Invoker转化为 Exporter 实例,并打开 Netty 服务端监听客户请求
    • 创建 Registry 实例,连接 Zookeeper,并在服务节点下写入提供者的 URL 地址,注册服务
    • 向注册中心订阅 override 数据,并返回一个 Exporter 实例
  3. 根据 URL 格式中的 “dubbo://service-host/{服务名}/{版本号}” 中协议头 dubbo:// 识别,调用 DubboProtocol#export() 方法,开发服务端口
  4. RegistryProtocol#export() 返回的 Exporter 实例存放到ServiceConfig 的 List exporters 中

4.4 服务发现

consumer的消费

4.4.1 服务发现流程

在这里插入图片描述
服务消费也分为两大部分 , 第一步通过持有远程服务实例生成Invoker, 这个 Invoker 在客户端是核心的远程代理对象 。 第二步会把 Invoker 通过动态代理转换成实现用户接口的动态代理引用 。

4.4.2 源码分析

引用入口

入口方法为 ReferenceBean 的 getObject 方法,该方法定义在Spring 的 FactoryBean 接口中,ReferenceBean 实现了这个方法

init方法调用createProxy方法,除了床架代理对象外,还会调用其他方法构建和合并Invoker实例。

1、如果是本地调用,直接jvm 协议从内存中获取实例
2、如果只有一个注册中心,直接通过 Protocol 自适应拓展类构建 Invoker
实例接口
3、如果有多个注册中心,此时先根据 url 构建 Invoker。然后再通过
Cluster 合并多个 Invoker,最后调用 ProxyFactory 生成代理类

创建客户端

Invoker 是由 Protocol 实现类构建而来,DubboProtocol的refer方法创建一个DubboInvoker。通过构造方法传入远程调用的client对象。默认情况下,Dubbo 使用 NettyClient 进行通信

注册

RegistryProtocol 的 refer 方法,首先为 url 设置协议头,然后根据 url 参数加载注册中心实例。然后获取 group 配置,根据 group 配置决定 doRefer 第一个参数的类型。

doRefer 方法创建一个 RegistryDirectory 实例,然后生成服务者消费者链接,并向注册中心进行注册。注册完毕后,紧接着订阅 providers、configurators、routers 等节点下的数据。完成订阅后,RegistryDirectory 会收到这几个节点下的子节点信息。由于一个服务可能部署在多台服务器上,这样就会在 providers 产生多个节点,这个时候就需要 Cluster 将多个服务节点合并为一个,并生成一个 Invoker。

创建代理对象

为服务接口生成代理对象。有了代理对象,即可进行远程调用。代理对象生成的入口方法为 ProxyFactory 的getProxy

4.4.3 总结
  1. 从注册中心发现引用服务:在有注册中心,通过注册中心发现提供者地址的情况下,ReferenceConfig 解析出的 URL 格式为:registry://registry- host:/org.apache.registry.RegistryService? refer=URL.encode(“conumer-host/com.foo.FooService? version=1.0.0”) 。
  2. 通过 URL 的registry://协议头识别,就会调用RegistryProtocol#refer()
    方法
    1. 查询提供者 URL,如 dubbo://service- host/com.foo.FooService?version=1.0.0 ,来获取注册中心
    2. 创建一个 RegistryDirectory 实例并设置注册中心和协议
    3. 生成 conusmer 连接,在 consumer 目录下创建节点,向注册中心注册
    4. 注册完毕后,订阅 providers,configurators,routers 等节点的数据
    5. 通过 URL 的 dubbo:// 协议头识别,调用DubboProtocol#refer() 方法,创建一个 ExchangeClient客户端并返回 DubboInvoker 实例
  3. 由于一个服务可能会部署在多台服务器上,这样就会在 providers 产生多个节点,这样也就会得到多个 DubboInvoker 实例,就需要RegistryProtocol 调用 Cluster 将多个服务提供者节点伪装成一个节点,并返回一个 Invoker
  4. Invoker 创建完毕后,调用 ProxyFactory 为服务接口生成代理对象,
    返回提供者引用

5. 高可用集群

5.1 服务集群概述

5.1.1 组件介绍
  • Directory:它代表多个Invoker,从methodInvokerMap提取,但是他的值是动态,例如注册中心的变更。
  • Router:负责从多个Invoker中按路由规则选出子集,例如应用隔离或读写分离或灰度发布等等
  • Cluster:将Directory中的多个Invoker伪装成一个Invoker,来容错,调用失败重试。
  • LoadBalance:从多个Invoker选取一个做本次调用,具体包含很多种负载均衡算法。
  • Invoker:Provider中的一个可调用接口。例如DemoService
5.1.1 调用过程

在这里插入图片描述

两个阶段:
第一个阶段是在服务消费者初始化期间,集群 Cluster 实现类为服务消费者创建 Cluster Invoker 实例,即上图中的merge 操作。
第二个阶段是在服务消费者进行远程调用时。以FailoverClusterInvoker 为例,该类型 Cluster Invoker 首先会调用 Directory的 list 方法列举 Invoker 列表(可将 Invoker 简单理解为服务提供者)。Directory 的用途是保存 Invoker,可简单类比为 List。其实现类RegistryDirectory 是一个动态服务目录,可感知注册中心配置的变化,它所持有的 Invoker 列表会随着注册中心内容的变化而变化。每次变化后,RegistryDirectory 会动态增删 Invoker,并调用 Router 的 route 方法进行路由,过滤掉不符合路由规则的 Invoker。当 FailoverClusterInvoker 拿到Directory 返回的 Invoker 列表后,它会通过 LoadBalance 从 Invoker 列表中选择一个 Invoker。最后 FailoverClusterInvoker 会将参数传给 LoadBalance选择出的 Invoker 实例的 invoke 方法,进行真正的远程调用。

5.2 集群容错机制

5.2.1 策略

集群某个某些节点出现问题是大概率事件,因此在设计分布式RPC框架的过程中,必须要把失败作为设计的一等公民来对待。

Dubbo默认内置了若干容错策略,如果不能满足用户需求,则可以通过自
定义容错策略进行配置

Dubbo主要内置了如下几种策略:

  • Failover(失败自动切换) 默认 默认配置的重试次数是2
  • Failsafe(失败安全)
  • Failfast(快速失败)
  • Failback(失败自动恢复)
  • Forking(并行调用)
  • Broadcast(广播调用)
5.2.2 源码

几个实现,之后看

6. 网络通信

6.1 介绍

网络通信位于Remoting模块:

  • Remoting 实现是 Dubbo 协议的实现,如果你选择 RMI 协议,整个Remoting 都不会用上;
  • Remoting 内部再划为 Transport 传输层 和 Exchange 信息交换层 ;
  • Transport 层只负责单向消息传输,是对 Mina, Netty, Grizzly 的抽象,它也可以扩展 UDP 传输;
  • Exchange 层是在传输层之上封装了 Request-Response 语义;

网络通信问题:

  • 客户端与服务端连通性问题
  • 粘包拆包问题
  • 异步多线程数据一致问题

6.2 通信协议

dubbo内置,dubbo协议 ,rmi协议,hessian协议,http协议,webservice协议,thrift协议,rest协议,grpc协议,memcached协议,redis协议等10种通讯协议

6.2.1 dubbo协议

采用单一长连接和 NIO 异步通讯,适合于小数据量大并发的服务调用,以及服务消费者机器数远大于服务提供者机器数的情况

基于mina 1.1.7 和 hessian 3.2.1

6.2.2 其他

专门针对Java语言的:Kryo,序列化方式的性能多数都显著优于 hessian2

6.3 网络通信深入

6.3.1 dubbo数据格式

解决socket数据粘包拆包问题,三种方式:

  • 定长协议
  • 特殊结束符
  • 变长协议(协议头+payload模式) 定长头变长体

dubbo数据包分为消息头和消息体,消息头用于存储一些元信息,比如魔数(Magic),数据包类型(Request/Response),消息体长度(Data Length)等。消息体中用于存储具体的调用消息,比如方法名称,参数列表等。下面简单列举一下消息头的内容,整个协议消息头共占据16字节。

6.3.2 具体

编码

编码过程,该过程首先会通过位运算将消息头写入到header 数组中。然后对 Request 对象的 data 字段执行序列化操作,序列化后的数据最终会存储到 ChannelBuffer 中。序列化操作执行完后,可得到数据序列化后的长度 len,紧接着将 len 写入到 header 指定位置处。最后再将消息头字节数组 header 写入到 ChannelBuffer 中,整个编码过程就结束了。

解码

通过检测消息头中的魔数是否与规定的魔数相等,提前拦截掉非常规数据包,比如通过 telnet 命令行发出的数据包。接着再对消息体长度,以及可读字节数进行检测。最后调用 decodeBody 方法进行后续的解码工作,ExchangeCodec 中实现了 decodeBody 方法

6.4 心跳检查

双向心跳,服务端会向客户端发送心跳,客户端也会向服务端发送心跳,接收的一方更新 lastRead 字段,发送的一方更新 lastWrite 字段,超过心跳间隙的时间,便发送心跳请求给对端。这里的 lastRead/lastWrite 同样会被同一个通道上的普通调用更新,通过更新这两个字段,实现了只在连接空闲时才会真正发送空闲报文的机制

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值