深入解析dubbo的延迟暴露

news2024/11/20 8:31:03

一、引子

最近搭建了一个新的Java工程,主要是提供dubbo服务给其他业务用的。突然想起之前dubbo服务都会配置延迟暴露来解决平滑发布的问题,但是好像现在新的Java项目都没有配置延迟暴露了,觉得很奇怪,所以去研究了一下关于dubbo延迟暴露的细节。

说明:

  • 延迟暴露(export)也叫延迟注册(register),为了统一概念,后续内容统一称“延迟暴露”。
  • 本篇文章是基于dubbo 2.6.6来讲的。

本篇文章主要介绍了以下几点:

  • 什么是dubbo延迟暴露
  • 延迟暴露解决了什么问题
  • dubbo延迟暴露使用及原理
  • 结合公司老项目和新项目的平滑发布问题来分析延迟暴露的使用案例

二、什么是dubbo延迟暴露

dubbo service默认是在容器启动的时候暴露的,一旦暴露,consumer端就可以发现这个service并且调用到这个provider。所谓延迟暴露即在启动之后延迟一定时间再暴露,比如延迟3s。

三、为什么需要延迟暴露

3.1 场景一:组件初始化需要一定的时间

比如你提供的service需要初始化缓存数据,这个数据需要读取DB,然后进行计算(假设这个时间需要10s)。如果提早暴露了service,consumer在调用时就会穿透缓存,导致DB压力变大。

这个时候设置一个延迟时间(>10s)来让service晚一点暴露则是很关键的。

3.2 场景二:平滑发布(本篇重点)

某些外部容器(比如tomcat)在未完全启动完毕之前,对于dubbo service的调用会存在阻塞,导致consumer端timeout,这种情况在发布的时候有一定概率会发生。

为了避免这个问题,设置一定的延时时间(保证在tomcat启动完毕之后)就可以做到平滑发布。

四、dubbo延迟暴露使用及原理

4.1 使用

老的spring工程(xml)和spring boot工程(properties)的用法不太一样,下面针对这2种用法做介绍。

4.1.1 xml配置

provider级别的配置:

<!-- delay属性,表示延迟时间,单位ms。这里延迟20s暴露 -->
<dubbo:provider delay="20000"/>

service级别的配置:

<!-- 关键就是delay属性,这里延迟3s暴露 -->
<dubbo:service interface="com.xxx.xxxService" ref="xxxService" delay="3000"/>

思考题:会不会有method级别的delay配置?想想dubbo的注册流程...

4.1.2 Spring Boot工程的配置

springboot工程的特色就是配置变少了,少量的properties配置+各种组件的xxx-spring-boot-autoconfigure就搞定了大部分的配置。

dubbo延迟暴露在application.properties中的配置如下:

# 单位也是ms,这里表示延迟3s暴露
dubbo.provider.delay = 3000

注意:在properties中只能配置provider级别的延迟,如果你想配置service级别的延迟,可以通过xml或者注解的方式。

用注解的方式配置service级别的延迟如下:

import com.alibaba.dubbo.config.annotation.Service;

@Service(delay = 3000)
public class CategoryTreeServiceImpl implements CategoryTreeService {
 ...   
}

注意:上面@Service注解import的是dubbo包的,不是用的spring包的

4.2 原理

dubbo延迟暴露在源码中主要体现在ServiceBean类和它的父类ServiceConfig中。

以下是我从dubbo源码中把延迟暴露相关的代码抠出来的精简代码。

/**
 * 这个类相当于就是在xml中配置的<dubbo:service ... />所代表的一个bean
 */
public class ServiceBean<T> extends ServiceConfig<T> implements InitializingBean,                                                   DisposableBean, ApplicationContextAware,                                                ApplicationListener<ContextRefreshedEvent>,                                           BeanNameAware, ApplicationEventPublisherAware {
    //...
    //此方法是在spring容器初始化完成后触发的一个事件回调
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        if (isDelay() && !isExported() && !isUnexported()) {
            //...
            export();
        }
    }

    private boolean isDelay() {
        Integer delay = getDelay();//这里取的是service中的delay配置
        ProviderConfig provider = getProvider();
        
        //如果service没有配置delay则再取provider级别的delay配置
        if (delay == null && provider != null) {
            delay = provider.getDelay();
        }
        
        /*
         * supportedApplicationListener你可以理解成肯定是true,所以结果就看后面
         * 1. 默认不配置delay(即delay=null)或配置delay=-1的情况下则return true
         * 2. 如果delay配置了除-1以外的值(如delay=3000)则return false
         */
        return supportedApplicationListener && (delay == null || delay == -1);
    }
    
    @Override
    public void afterPropertiesSet() throws Exception {
        //...
        if (!isDelay()) {
            export();
        }
    }
    
    @Override
    public void export() {
        super.export();
        //...
    }
}

/**
 * 这个类是真正处理service暴露的地方
 */
public class ServiceConfig<T> extends AbstractServiceConfig {
    //...
    
    public synchronized void export() {
        if (provider != null) {
            if (export == null) {
                export = provider.getExport();
            }
            //这里优先用的是service级别的delay配置, 如果为null则再取provider级别的delay配置
            if (delay == null) {
                delay = provider.getDelay();
            }
        }
        if (export != null && !export) {
            return;
        }

        //如果配置了delay, 则用延迟任务(延迟时间就是delay的配置)去执行doExport()
        if (delay != null && delay > 0) {
            delayExportExecutor.schedule(new Runnable() {
                @Override
                public void run() {
                    doExport();
                }
            }, delay, TimeUnit.MILLISECONDS);
        } else {//如果没有配置delay, 则马上执行doExport()
            doExport();//这是真正暴露服务的方法
        }
    }
}

从上面的代码分析,ServiceBean作为spring bean时有2个关键的生命周期:

  1. 在初始化一个ServiceBean时,会执行afterPropertiesSet()
  2. 在spring容器初始化完成时,会执行onApplicationEvent(ContextRefreshedEvent event)

而对dubbo服务的暴露时机也是基于上面这2个入口控制的,中间穿插了对delay配置的判断及延迟任务的控制。

ServiceBean类中的isDelay()这个方法主要就是用来判断服务是否需要延迟暴露的。

注意!注意!注意!下面这个点必须注意!

这里的isDelay()方法从名字上会让人理解成是配置delay则返回true,没有配置delay则返回false。但事实刚好相反,配了delay参数(比如delay=2000)时isDelay()返回false,未配置delay参数时isDelay()返回true。

ServiceBean类的afterPropertiesSetonApplicationEvent方法中都有可能执行export()来暴露服务,区别就是这2个方法中对isDelay()的判断是相反的,afterPropertiesSet中是if(!isDelay())onApplicationEvent中是if(isDelay()),所以最终只会在其中一个地方去执行export()。

4.2.1 代码执行时序图

下面是没有配置延迟配置了延迟这2种情况分别对应的时序图。

非延迟时序图

延迟时序图

小结:

  • 延迟(配了delay参数)暴露服务是在ServiceBeanafterPropertiesSet方法(bean初始化时)中执行export(),然后通过延时任务(ScheduledExecutor)来触发服务暴露的。
  • 非延迟(未配置delay参数)暴露服务是在ServiceBeanonApplicationEvent方法(spring容器初始化完成时)中执行export()来立即触发服务暴露的。

说明:dubbo 2.6.5之前版本和之后版本在延迟暴露策略有一些区别,这里不再展开讨论,可以参考官方文档http://dubbo.apache.org/zh-cn/docs/user/demos/delay-publish.html

五、平滑发布案例分析

5.1 老的Java工程为什么需要延迟暴露

5.1.1 当rest协议和外置Tomcat结合时

rest协议其实就是http请求,所以需要配合web server来使用。由于我司用的是Tomcat,所以我以Tomcat为例来说。

当用的是外置Tomcat作为容器时,rest协议配置的端口号(port)需要和Tomcat中server.xml的http端口号保持一致。

5.1.1.1 未配置延迟暴露的问题

假设现在配置的rest协议端口号是8001,那么在非延迟暴露的情况下,整个启动的流程如下图所示:

 

上图的关键点有2个:

  1. 一个服务暴露出去按照协议会注册多个provider的URL(这里rest和dubbo协议会注册2个URL),consumer端如果没有指定reference的协议,那么负载均衡器有一定概率会走到rest协议对应的URL(原理见下面的图4),这个时候就会通过Tomcat所监听的8001端口。
  2. dubbo provider在暴露服务的时候,Tomcat还没有进行组件start的步骤,此时虽然8001端口已经暴露出去,但是socket是不接受请求的。此时如果有8001端口的请求进来,会wait直到Tomcat启动完毕。

 

基于以上2点,我们在看consumer端配置的timeout是多少,假设rest请求到Tomcat启动完毕的时间超过了timeout,那么consumer端就会throw Exception:timeout。这样,未配置延迟暴露所导致的平滑发布问题就出现了。

5.1.1.2 配置延迟暴露来解决问题

接下来我们再看下配置了延迟暴露后的启动流程:

 

上图的关键点就在于通过延时任务来进行服务暴露,而延时任务的触发是在Tomcat启动完成之后,这样来保证rest请求过来时,Tomcat已经准备好并且可以正常处理请求了。以此解决了平滑发布的问题。

注意:这里的延时任务的触发时间是通过delay的具体值来保证的,如果delay配的特别小,那么延时任务的触发并一定在Tomcat启动完成之后。

5.1.2 dubbo协议会出问题吗

上面我们讨论的都是基于rest协议的请求可能会出现平滑发布的问题,那么如果consumer用的是dubbo协议,问题还会出现吗?

其实dubbo协议是不会有问题的。原因在于dubbo协议的请求在provider端是用NettyServer来处理的,而NettyServer在第一个服务暴露之前就会完全初始化完毕并等待连接了,NettyServer本身不依赖Tomcat,所以不存在Tomcat这种服务暴露和接受请求之间存在时间差的问题。

那么本质上来讲,上面的问题主要还是由于rest协议所引起的(PHP只能通过rest协议调用,有些Java的consumer也没有指定协议),如果指定用dubbo协议去调用服务的话,这个问题也就没有了。

5.2 新的Spring Boot工程为什么就不用了

Spring Boot工程除了配置少,我个人觉得最大的好处就是集成了内嵌的服务器(比如Tomcat),部署特别简单,直接调main函数就行。那在dubbo服务暴露的问题上,Spring Boot工程和老的spring工程到底有什么区别呢?

5.2.1 当rest协议和内嵌Tomcat结合时

我们先来看一下Spring Boot工程基于内嵌Tomcat的启动流程,这里只是关注dubbo服务暴露的问题。

 

注意:上图是基于未配置延迟暴露下的启动流程。

上图的关键点就在于暴露服务前会先启动内嵌的Tomcat,等待内嵌Tomcat启动完毕之后再去做暴露动作,这个时候Tomcat已经具备了完整的处理能力,在步骤1.5请求进来时,Tomcat就开始马上处理请求了。

因为当Spring Boot工程结合内嵌Tomcat部署时,则不存在上面说的平滑发布的问题。

5.2.2 rest协议已不受待见

除了Spring Boot本身的原因以外,rest协议本身的使用场景已经越来越少了,也就是说以后这样的平滑发布问题其实就越来越少了。

因为rest的短连接(http)请求对于高并发的接口调用场景是不太适合的。而dubbo协议是基于长连接,避免了创建连接和销毁连接的消耗,更适合互联网的高并发场景。

那rest的存在还有什么意义?

我理解rest的意义主要还是为了跨语言(比如给PHP调用),因为rest协议本质就是http。

但是现在公司都在各种Java化,大部分后端业务用的都是Java语言,所以rest的跨语言优势就没那么明显了,包括公司现在的Java网关在进行dubbo泛化调用时,都指定了使用dubbo协议

六、总结

  • dubbo服务默认是在spring容器初始化完成时(onApplicationEvent)暴露,如果配置了delay参数且delay>0(单位ms),则会进行延迟暴露(初始化bean时afterPropertiesSet -> export -> ScheduleExecutor)。
  • delay的配置有provider级别和service级别2种,Spring工程可在xml中配置;Spring Boot工程可在properties中声明provider级别配置,在service实现类上通过注解声明service级别配置。
  • 外置Tomcat部署dubbo应用时的平滑发布问题(consumer调用会timeout),本质是因为consumer端用rest协议请求的时候provider端的Tomcat还没有完全启动所导致的,可以通过dubbo服务延迟暴露来解决。
  • Spring Boot工程结合内嵌Tomcat不会有平滑发布的问题,因为在服务暴露前会等待内嵌Tomcat完全启动。
  • consumer端可以尽量指定使用dubbo协议来提升一点点的调用性能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/371014.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Delete `␍`eslint(prettier/prettier) in vscode 的解决方案

错误描述从 Github 仓库拉取代码&#xff0c;使用 vscode 打开&#xff0c;页面报错&#xff0c;每一行都爆红 &#xff08;如下图&#xff09;问题原因由于历史原因&#xff0c;windows下和linux下的文本文件的换行符不一致。Windows在换行的时候&#xff0c;使用了换行符CRLF…

ROG配置ubuntu20.04.5双系统要点

win11ubuntu20.04.5 1. BIOS设置 开机长按F2进入bios设置&#xff0c;修改advanced参数&#xff1a; boot -> 关闭fast bootsecurity -> 关闭secure boot设置VMD controller为Disabled&#xff08;其他电脑是修改硬盘的SATA和ACHI模式&#xff09;。但是改了之后windo…

【大数据离线开发】8.1 Hive介绍和架构

Hive&#xff1a;数据分析引擎 了解 大数据的终极目标: 使用SQL语句来处理大数据 Hadoop的体系架构中: Hive:支持SOLPig:支持PiqLatin Spark的体系架构中: Spark SQL:类似Hive&#xff0c;支持SOL、支持DSL 另一个&#xff1a;Impala 8.1 什么是Hive&#xff1f; 起源自f…

Linux 离线安装Ruby和RubyGems环境

文章目录简介1. 安装GCC环境2. 安装ruby3. 安装 RubyGems4. 安装 redis-xxx.gem提示&#xff1a;以下是本篇文章正文内容&#xff0c;Redis系列学习将会持续更新 简介 在学习 Redis 集群对这几个词的概念和关系感混乱&#xff0c;故在此对其梳理。 Ruby 是什么&#xff1f; 它…

鼠标更换指针图案和更改typora的主题

鼠标更换指针图案 由此偶然看见好几个朋友都使用了新的图案替换掉了原有的鼠标图案&#xff0c;今天寻思自己也换一个图案 主要是觉得鼠标大一点儿会好看一些&#xff0c;所以就找了一些教程 官方教程&#xff0c;小的变动 当然最多的是官方教程&#xff0c;如果你只是想要…

Web Spider Ast-Hook 浏览器内存漫游 - 数据检索

文章目录一、资源下载二、通过npm安装anyproxy模块三、anyproxy的介绍以及基本使用1. anyproxy的功能介绍2. anyproxy的基本使用四、给浏览器挂代理五、实操极验demo案例总结提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、资源下载 Github&#x…

性能优化之node中间件耗时

背景 中间件在node框架中是很基本的套件&#xff0c;使用不当很容易对页面性能造成影响。除了node服务端外&#xff0c;前端做的SSR项目也要特别重视这块 哪些场景会造成中间件耗时特别严重&#xff1f; 罪魁祸首是&#xff1a;await阻塞 举个例子&#xff1a; 1.如何得到 …

稀疏特征和密集特征

在机器学习中&#xff0c;特征是指对象、人或现象的可测量和可量化的属性或特征。特征可以大致分为两类&#xff1a;稀疏特征和密集特征。 稀疏特征 稀疏特征是那些在数据集中不连续出现的特征&#xff0c;并且大多数值为零。稀疏特征的示例包括文本文档中特定单词的存在或不存…

高性能 Message ToJavaBean 工具 【easy.server.mapper】

easy.server.mapper 介绍 后端开发中&#xff0c;消息转换常见问题 Map 中的数据 转换成实体Bean数组 中的数据 转换成实体BeanServet 中的 param 转换成实体Bean 以上的三个问题是最常见的消息转换困扰。 以Map 举例 常见做法是 手动转换 Map<String,Object> da…

stm32f407探索者开发板(二十三)——定时器中断实验

文章目录一、通用定时器知识回顾1.1 时钟的选择1.2 内部时钟的选择1.3 计数器模式二、常用寄存器和库函数配置2.1 计数器当前值寄存器CNT2.2 预分频寄存器TIMx_PSC2.3 自动重装载寄存器&#xff08;TIMx_ARR)2.4 控制寄存器1&#xff08;TIMx_CR1&#xff09;2.5 DMA中断使能寄…

基因净化车间装修设计方案SICOLAB

基因净化车间的设计方案应该根据实际需求进行定制&#xff0c;以下是一些规划建设要点和洁净设计要注意的事项&#xff1a;一、净化车间规划建设要点&#xff1a;&#xff08;1&#xff09;基因车间的面积应该根据实验项目的规模进行规划&#xff0c;包括充足的操作区域和足够的…

华为OD机试题,用 Java 解【DNA 序列】问题

最近更新的博客 华为OD机试 - 猴子爬山 | 机试题算法思路 【2023】华为OD机试 - 分糖果(Java) | 机试题算法思路 【2023】华为OD机试 - 非严格递增连续数字序列 | 机试题算法思路 【2023】华为OD机试 - 消消乐游戏(Java) | 机试题算法思路 【2023】华为OD机试 - 组成最大数…

自动化测试selenium

目录 一、为什么引入自动化测试&#xff1f; 二、为什么选择selenium作为自动化测试工具&#xff1f; 三、环境部署 四、什么是驱动&#xff1f;驱动的工作原理&#xff1f; 五、selenium的基础语法 元素定位 元素操作 点击元素 模拟键盘输入 清除对象输入的文本…

vue-draggable浏览器拖拽event事件对象拖动时 DragEvent path undefined

场景&#xff1a; 在做组件拖拽过程中&#xff0c;需要获取到触发元素冒泡过程中的所有元素&#xff0c;所以使用了event.path属性。在Chrome下正常运行&#xff0c;但是在FireFox下测试时发现&#xff0c;完犊子&#xff0c;失效了&#xff0c;通过问题排查&#xff0c;发现了…

开源运维监控工具WGCLOUD - 功能概述及架构介绍(理论篇)

一、项目简介 开源运维监控系统WGCLOUD&#xff0c;基于springboot和golang开发&#xff0c;可以监控各种设备&#xff08;物理机&#xff0c;云主机&#xff0c;虚拟机等都可以&#xff0c;安卓也可以&#xff09;。 二、实现功能 支持windows和redHat、centos、ubuntu、deb…

ASEMI低压MOS管20N06参数,20N06体积,20N06大小

编辑-Z ASEMI低压MOS管20N06参数&#xff1a; 型号&#xff1a;20N06 漏极-源极电压&#xff08;VDS&#xff09;&#xff1a;60V 栅源电压&#xff08;VGS&#xff09;&#xff1a;20V 漏极电流&#xff08;ID&#xff09;&#xff1a;20A 功耗&#xff08;PD&#xff0…

【设计模式】 工厂模式介绍及C代码实现

【设计模式】 工厂模式介绍及C代码实现 背景 在软件系统中&#xff0c;经常面临着创建对象的工作&#xff1b;由于需求的变化&#xff0c;需要创建的对象的具体类型经常变化。 如何应对这种变化&#xff1f;如何绕过常规的对象创建方法(new)&#xff0c;提供一种“封装机制”来…

宝塔搭建实战php悟空CRM前后端分离源码-vue前端篇(二)

大家好啊&#xff0c;我是测评君&#xff0c;欢迎来到web测评。 上一期给大家分享了悟空CRM server端在宝塔部署的方式&#xff0c;但是由于前端是用vue开发的&#xff0c;如果要额外开发新的功能&#xff0c;就需要在本地运行、修改、打包重新发布到宝塔才能实现功能更新&…

Kubernetes之job

job job用于执行一次性任务&#xff0c;如数据处理、分析、测试、运算等需求&#xff0c;运算完成后&#xff0c;也就结束了&#xff0c;不用一直计算下去。 创建一个job后&#xff0c;会创建一个pod&#xff0c;如果pod执行成功了&#xff0c;则此job结束&#xff0c;若此pod…

Linux 配置NFS与autofs自动挂载

目录 配置NFS服务器 安装nfs软件包 配置共享目录 防火墙放行相关服务 配置NFS客户端 autofs自动挂载 配置autofs 配置NFS服务器 nfs主配置文件参数&#xff08;/etc/exports&#xff09; 共享目录 允许地址1访问&#xff08;选项1&#xff0c;选项2&#xff09; 循序地…