深入分析dubbo延遲暴露

一驹闰、引子

最近搭建了一個(gè)新的Java工程爷辱,主要是提供dubbo服務(wù)給其他業(yè)務(wù)用的。突然想起之前dubbo服務(wù)都會(huì)配置延遲暴露來(lái)解決平滑發(fā)布的問(wèn)題跷车,但是好像現(xiàn)在新的Java項(xiàng)目都沒(méi)有配置延遲暴露了区丑,覺(jué)得很奇怪拧粪,所以去研究了一下關(guān)于dubbo延遲暴露的細(xì)節(jié)。

說(shuō)明:

  • 延遲暴露(export)也叫延遲注冊(cè)(register)沧侥,為了統(tǒng)一概念可霎,后續(xù)內(nèi)容統(tǒng)一稱“延遲暴露”。
  • 本篇文章是基于dubbo 2.6.6來(lái)講的宴杀。

本篇文章主要介紹了以下幾點(diǎn):

  • 什么是dubbo延遲暴露
  • 延遲暴露解決了什么問(wèn)題
  • dubbo延遲暴露使用及原理
  • 結(jié)合公司老項(xiàng)目和新項(xiàng)目的平滑發(fā)布問(wèn)題來(lái)分析延遲暴露的使用案例

二、什么是dubbo延遲暴露

dubbo service默認(rèn)是在容器啟動(dòng)的時(shí)候暴露的旺罢,一旦暴露旷余,consumer端就可以發(fā)現(xiàn)這個(gè)service并且調(diào)用到這個(gè)provider绢记。所謂延遲暴露即在啟動(dòng)之后延遲一定時(shí)間再暴露,比如延遲3s正卧。

三蠢熄、為什么需要延遲暴露

3.1 場(chǎng)景一:組件初始化需要一定的時(shí)間

比如你提供的service需要初始化緩存數(shù)據(jù),這個(gè)數(shù)據(jù)需要讀取DB穗酥,然后進(jìn)行計(jì)算(假設(shè)這個(gè)時(shí)間需要10s)护赊。如果提早暴露了service,consumer在調(diào)用時(shí)就會(huì)穿透緩存砾跃,導(dǎo)致DB壓力變大。

這個(gè)時(shí)候設(shè)置一個(gè)延遲時(shí)間(>10s)來(lái)讓service晚一點(diǎn)暴露則是很關(guān)鍵的节吮。

3.2 場(chǎng)景二:平滑發(fā)布(本篇重點(diǎn))

某些外部容器(比如tomcat)在未完全啟動(dòng)完畢之前抽高,對(duì)于dubbo service的調(diào)用會(huì)存在阻塞,導(dǎo)致consumer端timeout透绩,這種情況在發(fā)布的時(shí)候有一定概率會(huì)發(fā)生翘骂。

為了避免這個(gè)問(wèn)題,設(shè)置一定的延時(shí)時(shí)間(保證在tomcat啟動(dòng)完畢之后)就可以做到平滑發(fā)布帚豪。

四碳竟、dubbo延遲暴露使用及原理

4.1 使用

老的spring工程(xml)和spring boot工程(properties)的用法不太一樣,下面針對(duì)這2種用法做介紹狸臣。

4.1.1 xml配置

provider級(jí)別的配置:

<!-- delay屬性莹桅,表示延遲時(shí)間,單位ms烛亦。這里延遲20s暴露 -->
<dubbo:provider delay="20000"/>

service級(jí)別的配置:

<!-- 關(guān)鍵就是delay屬性诈泼,這里延遲3s暴露 -->
<dubbo:service interface="com.xxx.xxxService" ref="xxxService" delay="3000"/>

思考題:會(huì)不會(huì)有method級(jí)別的delay配置?想想dubbo的注冊(cè)流程...

4.1.2 Spring Boot工程的配置

springboot工程的特色就是配置變少了煤禽,少量的properties配置+各種組件的xxx-spring-boot-autoconfigure就搞定了大部分的配置铐达。

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

# 單位也是ms,這里表示延遲3s暴露
dubbo.provider.delay = 3000

注意:在properties中只能配置provider級(jí)別的延遲檬果,如果你想配置service級(jí)別的延遲瓮孙,可以通過(guò)xml或者注解的方式。

用注解的方式配置service級(jí)別的延遲如下:

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

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

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

4.2 原理

dubbo延遲暴露在源碼中主要體現(xiàn)在ServiceBean類和它的父類ServiceConfig中杭抠。

以下是我從dubbo源碼中把延遲暴露相關(guān)的代碼摳出來(lái)的精簡(jiǎn)代碼。

/**
 * 這個(gè)類相當(dāng)于就是在xml中配置的<dubbo:service ... />所代表的一個(gè)bean
 */
public class ServiceBean<T> extends ServiceConfig<T> implements InitializingBean,                                                   DisposableBean, ApplicationContextAware,                                                ApplicationListener<ContextRefreshedEvent>,                                           BeanNameAware, ApplicationEventPublisherAware {
    //...
    //此方法是在spring容器初始化完成后觸發(fā)的一個(gè)事件回調(diào)
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        if (isDelay() && !isExported() && !isUnexported()) {
            //...
            export();
        }
    }

    private boolean isDelay() {
        Integer delay = getDelay();//這里取的是service中的delay配置
        ProviderConfig provider = getProvider();
        
        //如果service沒(méi)有配置delay則再取provider級(jí)別的delay配置
        if (delay == null && provider != null) {
            delay = provider.getDelay();
        }
        
        /*
         * supportedApplicationListener你可以理解成肯定是true知牌,所以結(jié)果就看后面
         * 1. 默認(rèn)不配置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();
        //...
    }
}

/**
 * 這個(gè)類是真正處理service暴露的地方
 */
public class ServiceConfig<T> extends AbstractServiceConfig {
    //...
    
    public synchronized void export() {
        if (provider != null) {
            if (export == null) {
                export = provider.getExport();
            }
            //這里優(yōu)先用的是service級(jí)別的delay配置, 如果為null則再取provider級(jí)別的delay配置
            if (delay == null) {
                delay = provider.getDelay();
            }
        }
        if (export != null && !export) {
            return;
        }

        //如果配置了delay, 則用延遲任務(wù)(延遲時(shí)間就是delay的配置)去執(zhí)行doExport()
        if (delay != null && delay > 0) {
            delayExportExecutor.schedule(new Runnable() {
                @Override
                public void run() {
                    doExport();
                }
            }, delay, TimeUnit.MILLISECONDS);
        } else {//如果沒(méi)有配置delay, 則馬上執(zhí)行doExport()
            doExport();//這是真正暴露服務(wù)的方法
        }
    }
}

從上面的代碼分析祈争,ServiceBean作為spring bean時(shí)有2個(gè)關(guān)鍵的生命周期:

  1. 在初始化一個(gè)ServiceBean時(shí),會(huì)執(zhí)行afterPropertiesSet()
  2. 在spring容器初始化完成時(shí)角寸,會(huì)執(zhí)行onApplicationEvent(ContextRefreshedEvent event)

而對(duì)dubbo服務(wù)的暴露時(shí)機(jī)也是基于上面這2個(gè)入口控制的菩混,中間穿插了對(duì)delay配置的判斷及延遲任務(wù)的控制忿墅。

ServiceBean類中的isDelay()這個(gè)方法主要就是用來(lái)判斷服務(wù)是否需要延遲暴露的。

注意沮峡!注意疚脐!注意!下面這個(gè)點(diǎn)必須注意邢疙!

這里的isDelay()方法從名字上會(huì)讓人理解成是配置delay則返回true棍弄,沒(méi)有配置delay則返回false。但事實(shí)剛好相反疟游,\color{red}{配了}delay參數(shù)(比如delay=2000)時(shí)isDelay()返回\color{red}{false}呼畸,\color{red}{未配置}delay參數(shù)時(shí)isDelay()返回\color{red}{true}

ServiceBean類的afterPropertiesSetonApplicationEvent方法中都有可能執(zhí)行export()來(lái)暴露服務(wù)颁虐,區(qū)別就是這2個(gè)方法中對(duì)isDelay()的判斷是相反的蛮原,afterPropertiesSet中是if(!isDelay())onApplicationEvent中是if(isDelay())另绩,所以最終只會(huì)在其中一個(gè)地方去執(zhí)行export()儒陨。

4.2.1 代碼執(zhí)行時(shí)序圖

下面是沒(méi)有配置延遲配置了延遲這2種情況分別對(duì)應(yīng)的時(shí)序圖。

非延遲時(shí)序圖

延遲時(shí)序圖

小結(jié):

  • 延遲(配了delay參數(shù))暴露服務(wù)是在ServiceBeanafterPropertiesSet方法(bean初始化時(shí))中執(zhí)行export()笋籽,然后通過(guò)延時(shí)任務(wù)(ScheduledExecutor)來(lái)觸發(fā)服務(wù)暴露的蹦漠。
  • 非延遲(未配置delay參數(shù))暴露服務(wù)是在ServiceBeanonApplicationEvent方法(spring容器初始化完成時(shí))中執(zhí)行export()來(lái)立即觸發(fā)服務(wù)暴露的。

說(shuō)明:dubbo 2.6.5之前版本和之后版本在延遲暴露策略有一些區(qū)別车海,這里不再展開(kāi)討論笛园,可以參考官方文檔http://dubbo.apache.org/zh-cn/docs/user/demos/delay-publish.html

五、平滑發(fā)布案例分析

5.1 老的Java工程為什么需要延遲暴露

5.1.1 當(dāng)rest協(xié)議和外置Tomcat結(jié)合時(shí)

rest協(xié)議其實(shí)就是http請(qǐng)求容劳,所以需要配合web server來(lái)使用喘沿。由于我司用的是Tomcat,所以我以Tomcat為例來(lái)說(shuō)竭贩。

當(dāng)用的是外置Tomcat作為容器時(shí)蚜印,rest協(xié)議配置的端口號(hào)(port)需要和Tomcat中server.xml的http端口號(hào)保持一致。

5.1.1.1 未配置延遲暴露的問(wèn)題

假設(shè)現(xiàn)在配置的rest協(xié)議端口號(hào)是8001留量,那么在非延遲暴露的情況下窄赋,整個(gè)啟動(dòng)的流程如下圖所示:

上圖的關(guān)鍵點(diǎn)有2個(gè):

  1. 一個(gè)服務(wù)暴露出去按照協(xié)議會(huì)注冊(cè)多個(gè)provider的URL(這里rest和dubbo協(xié)議會(huì)注冊(cè)2個(gè)URL),consumer端如果沒(méi)有指定reference的協(xié)議楼熄,那么負(fù)載均衡器有一定概率會(huì)走到rest協(xié)議對(duì)應(yīng)的URL(原理見(jiàn)下面的圖4)忆绰,這個(gè)時(shí)候就會(huì)通過(guò)Tomcat所監(jiān)聽(tīng)的8001端口。
  2. dubbo provider在暴露服務(wù)的時(shí)候可岂,Tomcat還沒(méi)有進(jìn)行組件start的步驟错敢,此時(shí)雖然8001端口已經(jīng)暴露出去,但是socket是不接受請(qǐng)求的。此時(shí)如果有8001端口的請(qǐng)求進(jìn)來(lái)稚茅,會(huì)wait直到Tomcat啟動(dòng)完畢纸淮。
圖4

基于以上2點(diǎn),我們?cè)诳碿onsumer端配置的timeout是多少亚享,假設(shè)rest請(qǐng)求到Tomcat啟動(dòng)完畢的時(shí)間超過(guò)了timeout咽块,那么consumer端就會(huì)throw Exception:timeout。這樣欺税,未配置延遲暴露所導(dǎo)致的平滑發(fā)布問(wèn)題就出現(xiàn)了侈沪。

5.1.1.2 配置延遲暴露來(lái)解決問(wèn)題

接下來(lái)我們?cè)倏聪?strong>配置了延遲暴露后的啟動(dòng)流程:

上圖的關(guān)鍵點(diǎn)就在于通過(guò)延時(shí)任務(wù)來(lái)進(jìn)行服務(wù)暴露,而延時(shí)任務(wù)的觸發(fā)是在Tomcat啟動(dòng)完成之后晚凿,這樣來(lái)保證rest請(qǐng)求過(guò)來(lái)時(shí)亭罪,Tomcat已經(jīng)準(zhǔn)備好并且可以正常處理請(qǐng)求了。以此解決了平滑發(fā)布的問(wèn)題歼秽。

注意:這里的延時(shí)任務(wù)的觸發(fā)時(shí)間是通過(guò)delay的具體值來(lái)保證的皆撩,如果delay配的特別小,那么延時(shí)任務(wù)的觸發(fā)并一定在Tomcat啟動(dòng)完成之后哲银。

5.1.2 dubbo協(xié)議會(huì)出問(wèn)題嗎

上面我們討論的都是基于rest協(xié)議的請(qǐng)求可能會(huì)出現(xiàn)平滑發(fā)布的問(wèn)題,那么如果consumer用的是dubbo協(xié)議呻惕,問(wèn)題還會(huì)出現(xiàn)嗎荆责?

其實(shí)dubbo協(xié)議是不會(huì)有問(wèn)題的。原因在于dubbo協(xié)議的請(qǐng)求在provider端是用NettyServer來(lái)處理的亚脆,而NettyServer在第一個(gè)服務(wù)暴露之前就會(huì)完全初始化完畢并等待連接了做院,NettyServer本身不依賴Tomcat,所以不存在Tomcat這種服務(wù)暴露和接受請(qǐng)求之間存在時(shí)間差的問(wèn)題濒持。

那么本質(zhì)上來(lái)講键耕,上面的問(wèn)題主要還是由于rest協(xié)議所引起的(PHP只能通過(guò)rest協(xié)議調(diào)用,有些Java的consumer也沒(méi)有指定協(xié)議)柑营,如果指定用dubbo協(xié)議去調(diào)用服務(wù)的話屈雄,這個(gè)問(wèn)題也就沒(méi)有了。

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

Spring Boot工程除了配置少官套,我個(gè)人覺(jué)得最大的好處就是集成了內(nèi)嵌的服務(wù)器(比如Tomcat)酒奶,部署特別簡(jiǎn)單,直接調(diào)main函數(shù)就行奶赔。那在dubbo服務(wù)暴露的問(wèn)題上惋嚎,Spring Boot工程和老的spring工程到底有什么區(qū)別呢?

5.2.1 當(dāng)rest協(xié)議和內(nèi)嵌Tomcat結(jié)合時(shí)

我們先來(lái)看一下Spring Boot工程基于內(nèi)嵌Tomcat的啟動(dòng)流程站刑,這里只是關(guān)注dubbo服務(wù)暴露的問(wèn)題另伍。

注意:上圖是基于未配置延遲暴露下的啟動(dòng)流程。

上圖的關(guān)鍵點(diǎn)就在于暴露服務(wù)前會(huì)先啟動(dòng)內(nèi)嵌的Tomcat绞旅,等待內(nèi)嵌Tomcat啟動(dòng)完畢之后再去做暴露動(dòng)作摆尝,這個(gè)時(shí)候Tomcat已經(jīng)具備了完整的處理能力温艇,在步驟1.5請(qǐng)求進(jìn)來(lái)時(shí),Tomcat就開(kāi)始馬上處理請(qǐng)求了结榄。

因?yàn)楫?dāng)Spring Boot工程結(jié)合內(nèi)嵌Tomcat部署時(shí)中贝,則不存在上面說(shuō)的平滑發(fā)布的問(wèn)題。

5.2.2 rest協(xié)議已不受待見(jiàn)

除了Spring Boot本身的原因以外臼朗,rest協(xié)議本身的使用場(chǎng)景已經(jīng)越來(lái)越少了邻寿,也就是說(shuō)以后這樣的平滑發(fā)布問(wèn)題其實(shí)就越來(lái)越少了。

因?yàn)閞est的短連接(http)請(qǐng)求對(duì)于高并發(fā)的接口調(diào)用場(chǎng)景是不太適合的视哑。而dubbo協(xié)議是基于長(zhǎng)連接绣否,避免了創(chuàng)建連接和銷毀連接的消耗,更適合互聯(lián)網(wǎng)的高并發(fā)場(chǎng)景挡毅。

那rest的存在還有什么意義蒜撮?

我理解rest的意義主要還是為了跨語(yǔ)言(比如給PHP調(diào)用),因?yàn)閞est協(xié)議本質(zhì)就是http跪呈。

但是現(xiàn)在公司都在各種Java化段磨,大部分后端業(yè)務(wù)用的都是Java語(yǔ)言,所以rest的跨語(yǔ)言優(yōu)勢(shì)就沒(méi)那么明顯了耗绿,包括公司現(xiàn)在的Java網(wǎng)關(guān)在進(jìn)行dubbo泛化調(diào)用時(shí)苹支,都指定了使用dubbo協(xié)議

六误阻、總結(jié)

  • dubbo服務(wù)默認(rèn)是在spring容器初始化完成時(shí)(onApplicationEvent)暴露债蜜,如果配置了delay參數(shù)且delay>0(單位ms),則會(huì)進(jìn)行延遲暴露(初始化bean時(shí)afterPropertiesSet -> export -> ScheduleExecutor)究反。
  • delay的配置有provider級(jí)別和service級(jí)別2種寻定,Spring工程可在xml中配置;Spring Boot工程可在properties中聲明provider級(jí)別配置精耐,在service實(shí)現(xiàn)類上通過(guò)注解聲明service級(jí)別配置狼速。
  • 外置Tomcat部署dubbo應(yīng)用時(shí)的平滑發(fā)布問(wèn)題(consumer調(diào)用會(huì)timeout),本質(zhì)是因?yàn)閏onsumer端用rest協(xié)議請(qǐng)求的時(shí)候provider端的Tomcat還沒(méi)有完全啟動(dòng)所導(dǎo)致的黍氮,可以通過(guò)dubbo服務(wù)延遲暴露來(lái)解決唐含。
  • Spring Boot工程結(jié)合內(nèi)嵌Tomcat不會(huì)有平滑發(fā)布的問(wèn)題,因?yàn)樵诜?wù)暴露前會(huì)等待內(nèi)嵌Tomcat完全啟動(dòng)沫浆。
  • consumer端可以盡量指定使用dubbo協(xié)議來(lái)提升一點(diǎn)點(diǎn)的調(diào)用性能捷枯。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市专执,隨后出現(xiàn)的幾起案子淮捆,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,692評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件攀痊,死亡現(xiàn)場(chǎng)離奇詭異桐腌,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)苟径,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)案站,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人棘街,你說(shuō)我怎么就攤上這事蟆盐。” “怎么了遭殉?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,995評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵石挂,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我险污,道長(zhǎng)痹愚,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,223評(píng)論 1 292
  • 正文 為了忘掉前任蛔糯,我火速辦了婚禮拯腮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蚁飒。我一直安慰自己疾瓮,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,245評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布飒箭。 她就那樣靜靜地躺著,像睡著了一般蜒灰。 火紅的嫁衣襯著肌膚如雪弦蹂。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,208評(píng)論 1 299
  • 那天强窖,我揣著相機(jī)與錄音凸椿,去河邊找鬼。 笑死翅溺,一個(gè)胖子當(dāng)著我的面吹牛脑漫,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播咙崎,決...
    沈念sama閱讀 40,091評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼优幸,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了褪猛?” 一聲冷哼從身側(cè)響起网杆,我...
    開(kāi)封第一講書(shū)人閱讀 38,929評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后碳却,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體队秩,經(jīng)...
    沈念sama閱讀 45,346評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,570評(píng)論 2 333
  • 正文 我和宋清朗相戀三年昼浦,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了馍资。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,739評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡关噪,死狀恐怖鸟蟹,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情色洞,我是刑警寧澤戏锹,帶...
    沈念sama閱讀 35,437評(píng)論 5 344
  • 正文 年R本政府宣布,位于F島的核電站火诸,受9級(jí)特大地震影響锦针,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜置蜀,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,037評(píng)論 3 326
  • 文/蒙蒙 一奈搜、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧盯荤,春花似錦馋吗、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,677評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至灼卢,卻和暖如春绍哎,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背鞋真。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,833評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工崇堰, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人涩咖。 一個(gè)月前我還...
    沈念sama閱讀 47,760評(píng)論 2 369
  • 正文 我出身青樓海诲,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親檩互。 傳聞我的和親對(duì)象是個(gè)殘疾皇子特幔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,647評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容