Dubbo在Docker中的優(yōu)雅停機(jī)

Dubbo在Docker中的優(yōu)雅停機(jī)

優(yōu)雅停機(jī)

優(yōu)雅停機(jī)是指在停止應(yīng)用時(shí)栋荸,執(zhí)行的一系列保證應(yīng)用正常關(guān)閉的操作穆端。這些操作往往包括等待已有請(qǐng)求執(zhí)行完成、關(guān)閉線程鸠真、關(guān)閉連接和釋放資源等诗宣,優(yōu)雅停機(jī)可以避免非正常關(guān)閉程序可能造成數(shù)據(jù)異潮炫拢或丟失,應(yīng)用異常等問題召庞。優(yōu)雅停機(jī)本質(zhì)上是JVM即將關(guān)閉前執(zhí)行的一些額外的處理代碼岛心。這個(gè)功能官方是支持的来破,只要正常kill SIGTERM或SIGIN 就可以。

Dubbo服務(wù)關(guān)閉流程

Provider在接收到停機(jī)指令后

  • 從注冊(cè)中心上注銷所有服務(wù)忘古;
  • 從配置中心取消監(jiān)聽動(dòng)態(tài)配置徘禁;
  • 向所有連接的客戶端發(fā)送只讀事件,停止接收新請(qǐng)求髓堪;
  • 等待一段時(shí)間以處理已到達(dá)的請(qǐng)求送朱,然后關(guān)閉請(qǐng)求處理線程池;
  • 斷開所有客戶端連接旦袋。

Consumer在接收到停機(jī)指令后

  • 拒絕新到請(qǐng)求骤菠,直接返回調(diào)用異常;
  • 等待當(dāng)前已發(fā)送請(qǐng)求執(zhí)行完畢疤孕,如果響應(yīng)超時(shí)則強(qiáng)制關(guān)閉連接。

源碼分析

參考Dubbo版本2.7.6

  1. Provider 啟動(dòng)時(shí)注冊(cè)鉤子
private DubboBootstrap() {
        DubboShutdownHook.getDubboShutdownHook().register();
        ShutdownHookCallbacks.INSTANCE.addCallback(new ShutdownHookCallback() {
            @Override
            public void callback() throws Throwable {
                DubboBootstrap.this.destroy();
            }
        });
    }

public void register() {
    if (registered.compareAndSet(false, true)) {
        DubboShutdownHook dubboShutdownHook = getDubboShutdownHook();
        Runtime.getRuntime().addShutdownHook(dubboShutdownHook);
        dispatch(new DubboShutdownHookRegisteredEvent(dubboShutdownHook));
    }
}
  1. 收到退出信號(hào)
//執(zhí)行銷毀
DubboBootstrap.java
public void destroy() {
        if (destroyLock.tryLock()) {
            try {
                DubboShutdownHook.destroyAll();
                .
                .
                .
            } finally {
                destroyLock.unlock();
            }
        }
    }

//銷毀
DubboShutdownHook.java
public static void destroyAll() {
    if (destroyed.compareAndSet(false, true)) {
        AbstractRegistryFactory.destroyAll();
        destroyProtocols();
    }
}

//從注冊(cè)中心刪除央拖,并取消監(jiān)聽
AbstractRegistry.java
public void destroy() {
    //刪除節(jié)點(diǎn)
    Set<URL> destroyRegistered = new HashSet<>(getRegistered());
    if (!destroyRegistered.isEmpty()) {
        for (URL url : new HashSet<>(getRegistered())) {
            if (url.getParameter(DYNAMIC_KEY, true)) {
                try {
                    unregister(url);
                    if (logger.isInfoEnabled()) {
                        logger.info("Destroy unregister url " + url);
                    }
                } catch (Throwable t) {
                    logger.warn("Failed to unregister url " + url + " to registry " + getUrl() + " on destroy, cause: " + t.getMessage(), t);
                }
            }
        }
    }
    //取消監(jiān)聽
    Map<URL, Set<NotifyListener>> destroySubscribed = new HashMap<>(getSubscribed());
    if (!destroySubscribed.isEmpty()) {
        for (Map.Entry<URL, Set<NotifyListener>> entry : destroySubscribed.entrySet()) {
            URL url = entry.getKey();
            for (NotifyListener listener : entry.getValue()) {
                try {
                    unsubscribe(url, listener);
                } catch (Throwable t) {
                    logger.warn("Failed to unsubscribe url " + url + " to registry " + getUrl() + " on destroy, cause: " + t.getMessage(), t);
                }
            }
        }
    }
    AbstractRegistryFactory.removeDestroyedRegistry(this);
}

//注銷協(xié)議
DubboShutdownHook.java
public static void destroyProtocols() {
    ExtensionLoader<Protocol> loader = ExtensionLoader.getExtensionLoader(Protocol.class);
    for (String protocolName : loader.getLoadedExtensions()) {
        try {
            Protocol protocol = loader.getLoadedExtension(protocolName);
            if (protocol != null) {
                protocol.destroy();
            }
        } catch (Throwable t) {
            logger.warn(t.getMessage(), t);
        }
    }
}

docker容器中Dubbo不優(yōu)雅停機(jī)

按理說祭阀,我們不用做任何干預(yù)就可以實(shí)現(xiàn)優(yōu)雅停機(jī)了,但是實(shí)際上卻是Dubbo Provider服務(wù)重啟時(shí)鲜戒,總是能收到如下告警专控,且服務(wù)關(guān)閉慢。

Connection refused: /192.168.1.112:20880
    at com.alibaba.dubbo.remoting.transport.netty.NettyClient.doConnect(NettyClient.java:124)
    at com.alibaba.dubbo.remoting.transport.AbstractClient.connect(AbstractClient.java:280)
    at com.alibaba.dubbo.remoting.transport.AbstractClient$1.run(AbstractClient.java:145)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308)
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:180)
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:294)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)
Caused by: java.net.ConnectException: Connection refused: /192.168.1.112:20880
    at sun.nio.ch.SocketChannelImpl.checkConnect(Native Method)
    at sun.nio.ch.SocketChannelImpl.finishConnect(SocketChannelImpl.java:717)
    at org.jboss.netty.channel.socket.nio.NioClientBoss.connect(NioClientBoss.java:152)
    at org.jboss.netty.channel.socket.nio.NioClientBoss.processSelectedKeys(NioClientBoss.java:105)
    at org.jboss.netty.channel.socket.nio.NioClientBoss.process(NioClientBoss.java:79)
    at org.jboss.netty.channel.socket.nio.AbstractNioSelector.run(AbstractNioSelector.java:337)
    at org.jboss.netty.channel.socket.nio.NioClientBoss.run(NioClientBoss.java:42)
    at org.jboss.netty.util.ThreadRenamingRunnable.run(ThreadRenamingRunnable.java:108)
    at org.jboss.netty.util.internal.DeadLockProofWorker$1.run(DeadLockProofWorker.java:42)
    ... 3 common frames omitted

通過分析得知是Dockerfile中啟動(dòng)java進(jìn)程命令導(dǎo)致的遏餐,原始:

CMD java ${CMD_JAVA_ARGS} -jar /opt/app/application.jar 2>&1

改為

CMD ["java",$CMD_JAVA_ARGS,"-jar","/opt/app/${JobFile}","2>&1"]

兩種CMD啟動(dòng)方式

  1. CMD ["executable","param1","param2"] (直接啟動(dòng)程序伦腐,這是推薦Docker官方推薦用法)

  2. CMD command param1 param2 (通過shell啟動(dòng))

按照方式一 ,進(jìn)程結(jié)構(gòu)為:

UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 Apr17 ?        00:11:21 java -jar application.jar

按照方式二,進(jìn)程結(jié)構(gòu)為

UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 Mar24 ?        00:00:00 /bin/sh -c java -jar application.jar
root         5     1  0 Mar24 ?        01:37:21 java -jar application.jar

此時(shí)失都,我們的sh進(jìn)程是我們的java程序的父進(jìn)程柏蘑。

Docker停止容器

docker stop,當(dāng)我們用docker stop命令來停掉容器的時(shí)候粹庞,docker默認(rèn)會(huì)允許容器中的應(yīng)用程序有10秒的時(shí)間用以終止運(yùn)行咳焚。在docker stop命令執(zhí)行的時(shí)候,會(huì)先向容器中PID為1的進(jìn)程發(fā)送系統(tǒng)信號(hào)SIGTERM庞溜,然后等待容器中的應(yīng)用程序終止執(zhí)行革半,如果等待時(shí)間達(dá)到設(shè)定的超時(shí)時(shí)間,或者默認(rèn)的10秒流码,會(huì)繼續(xù)發(fā)送SIGKILL的系統(tǒng)信號(hào)強(qiáng)行kill掉進(jìn)程又官。在容器中的應(yīng)用程序,可以選擇忽略和不處理SIGTERM信號(hào)漫试,不過一旦達(dá)到超時(shí)時(shí)間六敬,程序就會(huì)被系統(tǒng)強(qiáng)行kill掉,因?yàn)镾IGKILL信號(hào)是直接發(fā)往系統(tǒng)內(nèi)核的商虐,應(yīng)用程序沒有機(jī)會(huì)去處理它觉阅。

那么問題來了崖疤,對(duì)于啟動(dòng)方式二,我們的bash進(jìn)程是1號(hào)進(jìn)程典勇,但它并不會(huì)將SIGTERM信號(hào)傳遞給我們的java進(jìn)程劫哼,我們就沒有機(jī)會(huì)進(jìn)行優(yōu)雅停機(jī)了。

總結(jié)

Dubbo官方是支持優(yōu)雅停機(jī)的割笙,Docker官方也是支持優(yōu)雅停機(jī)的权烧,但是使用需掌握正確的姿勢(shì)。

參考:

https://dubbo.apache.org/zh-cn/blog/dubbo-gracefully-shutdown.html
https://www.infoq.cn/article/2016/01/dumb-init-Docker
https://yeasy.gitbooks.io/docker_practice/image/dockerfile/cmd.html
https://xiaozhou.net/stop-docker-container-gracefully-2016-09-08.html

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末伤溉,一起剝皮案震驚了整個(gè)濱河市般码,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌乱顾,老刑警劉巖板祝,帶你破解...
    沈念sama閱讀 218,386評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異走净,居然都是意外死亡券时,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門伏伯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來橘洞,“玉大人,你說我怎么就攤上這事说搅≌ㄔ妫” “怎么了?”我有些...
    開封第一講書人閱讀 164,704評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵弄唧,是天一觀的道長适肠。 經(jīng)常有香客問我,道長套才,這世上最難降的妖魔是什么迂猴? 我笑而不...
    開封第一講書人閱讀 58,702評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮背伴,結(jié)果婚禮上沸毁,老公的妹妹穿的比我還像新娘。我一直安慰自己傻寂,他們只是感情好息尺,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,716評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著疾掰,像睡著了一般搂誉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上静檬,一...
    開封第一講書人閱讀 51,573評(píng)論 1 305
  • 那天炭懊,我揣著相機(jī)與錄音并级,去河邊找鬼。 笑死侮腹,一個(gè)胖子當(dāng)著我的面吹牛嘲碧,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播父阻,決...
    沈念sama閱讀 40,314評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼愈涩,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了加矛?” 一聲冷哼從身側(cè)響起履婉,我...
    開封第一講書人閱讀 39,230評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎斟览,沒想到半個(gè)月后毁腿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,680評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡苛茂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,873評(píng)論 3 336
  • 正文 我和宋清朗相戀三年狸棍,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片味悄。...
    茶點(diǎn)故事閱讀 39,991評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖塌鸯,靈堂內(nèi)的尸體忽然破棺而出侍瑟,到底是詐尸還是另有隱情,我是刑警寧澤丙猬,帶...
    沈念sama閱讀 35,706評(píng)論 5 346
  • 正文 年R本政府宣布涨颜,位于F島的核電站,受9級(jí)特大地震影響茧球,放射性物質(zhì)發(fā)生泄漏庭瑰。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,329評(píng)論 3 330
  • 文/蒙蒙 一抢埋、第九天 我趴在偏房一處隱蔽的房頂上張望弹灭。 院中可真熱鬧,春花似錦揪垄、人聲如沸穷吮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽捡鱼。三九已至,卻和暖如春酷愧,著一層夾襖步出監(jiān)牢的瞬間驾诈,已是汗流浹背缠诅。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評(píng)論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留乍迄,地道東北人管引。 一個(gè)月前我還...
    沈念sama閱讀 48,158評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像就乓,于是被迫代替她去往敵國和親汉匙。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,941評(píng)論 2 355

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

  • 00 前言 微服務(wù)部署是一個(gè)非常嚴(yán)謹(jǐn)?shù)脑掝}生蚁,微服務(wù)開發(fā)完成需要上線部署噩翠,在整個(gè)部署過程中怎么保證業(yè)務(wù)的連續(xù)性,怎么...
    rabbitGYK閱讀 6,063評(píng)論 1 39
  • SIGINT SIGTERM SIGKILL區(qū)別 三者都是結(jié)束/終止進(jìn)程運(yùn)行邦投。 1.SIGINT SIGTERM區(qū)...
    go4it閱讀 4,790評(píng)論 0 3
  • 什么是優(yōu)雅升級(jí)伤锚? 優(yōu)雅升級(jí)即在對(duì)業(yè)務(wù)和用戶無感知的情況下,對(duì)系統(tǒng)進(jìn)行升級(jí) 如今互聯(lián)網(wǎng)基于微服務(wù)架構(gòu)部署越來越流行志衣。...
    楓葉_huazhe閱讀 7,408評(píng)論 7 11
  • 背景 我們內(nèi)部壓力(cpu 80%屯援,內(nèi)存90%)通過stress (做頁面壓力測(cè)試)在容器內(nèi)部做測(cè)試中,發(fā)現(xiàn)某幾個(gè)...
    marshalzxy閱讀 32,138評(píng)論 0 0
  • 在與人交往中念脯,我大多是被動(dòng)的那一個(gè)狞洋。 即使有時(shí)候主動(dòng)了,也是有些卑微討好绿店,害怕得罪他人吉懊,害怕影響他人。 將自尊自我...
    萍梗子閱讀 325評(píng)論 0 2