Hystrix線程隔離技術(shù)解析-線程池

認(rèn)識(shí)Hystrix

Hystrix是Netflix開(kāi)源的一款容錯(cuò)框架,包含常用的容錯(cuò)方法:線程隔離妙同、信號(hào)量隔離砚蓬、降級(jí)策略矢门、熔斷技術(shù)。
在高并發(fā)訪問(wèn)下,系統(tǒng)所依賴的服務(wù)的穩(wěn)定性對(duì)系統(tǒng)的影響非常大祟剔,依賴有很多不可控的因素涩拙,比如網(wǎng)絡(luò)連接變慢眷唉,資源突然繁忙,暫時(shí)不可用,服務(wù)脫機(jī)等怔接。我們要構(gòu)建穩(wěn)定棋电、可靠的分布式系統(tǒng)橙困,就必須要有這樣一套容錯(cuò)方法依啰。
本文主要討論線程隔離技術(shù)。

為什么要做線程隔離

比如我們現(xiàn)在有3個(gè)業(yè)務(wù)調(diào)用分別是查詢訂單案训、查詢商品买置、查詢用戶,且這三個(gè)業(yè)務(wù)請(qǐng)求都是依賴第三方服務(wù)-訂單服務(wù)强霎、商品服務(wù)忿项、用戶服務(wù)。三個(gè)服務(wù)均是通過(guò)RPC調(diào)用城舞。當(dāng)查詢訂單服務(wù)轩触,假如線程阻塞了,這個(gè)時(shí)候后續(xù)有大量的查詢訂單請(qǐng)求過(guò)來(lái)家夺,那么容器中的線程數(shù)量則會(huì)持續(xù)增加直致CPU資源耗盡到100%脱柱,整個(gè)服務(wù)對(duì)外不可用,集群環(huán)境下就是雪崩拉馋。如下圖


訂單服務(wù)不可用.png

整個(gè)tomcat容器不可用.png
Hystrix是如何通過(guò)線程池實(shí)現(xiàn)線程隔離的

Hystrix通過(guò)命令模式榨为,將每個(gè)類型的業(yè)務(wù)請(qǐng)求封裝成對(duì)應(yīng)的命令請(qǐng)求,比如查詢訂單->訂單Command煌茴,查詢商品->商品Command随闺,查詢用戶->用戶Command。每個(gè)類型的Command對(duì)應(yīng)一個(gè)線程池蔓腐。創(chuàng)建好的線程池是被放入到ConcurrentHashMap中矩乐,比如查詢訂單:

final static ConcurrentHashMap<String, HystrixThreadPool> threadPools = new ConcurrentHashMap<String, HystrixThreadPool>();
threadPools.put(“hystrix-order”, new HystrixThreadPoolDefault(threadPoolKey, propertiesBuilder));

當(dāng)?shù)诙尾樵冇唵握?qǐng)求過(guò)來(lái)的時(shí)候,則可以直接從Map中獲取該線程池回论。具體流程如下圖:

hystrix線程執(zhí)行過(guò)程和異步化.png

創(chuàng)建線程池中的線程的方法散罕,查看源代碼如下:

public ThreadPoolExecutor getThreadPool(final HystrixThreadPoolKey threadPoolKey, HystrixProperty<Integer> corePoolSize, HystrixProperty<Integer> maximumPoolSize, HystrixProperty<Integer> keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
    ThreadFactory threadFactory = null;
    if (!PlatformSpecific.isAppEngineStandardEnvironment()) {
        threadFactory = new ThreadFactory() {
            protected final AtomicInteger threadNumber = new AtomicInteger(0);

            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r, "hystrix-" + threadPoolKey.name() + "-" + threadNumber.incrementAndGet());
                thread.setDaemon(true);
                return thread;
            }

        };
    } else {
        threadFactory = PlatformSpecific.getAppEngineThreadFactory();
    }

    final int dynamicCoreSize = corePoolSize.get();
    final int dynamicMaximumSize = maximumPoolSize.get();

    if (dynamicCoreSize > dynamicMaximumSize) {
        logger.error("Hystrix ThreadPool configuration at startup for : " + threadPoolKey.name() + " is trying to set coreSize = " +
                dynamicCoreSize + " and maximumSize = " + dynamicMaximumSize + ".  Maximum size will be set to " +
                dynamicCoreSize + ", the coreSize value, since it must be equal to or greater than the coreSize value");
        return new ThreadPoolExecutor(dynamicCoreSize, dynamicCoreSize, keepAliveTime.get(), unit, workQueue, threadFactory);
    } else {
        return new ThreadPoolExecutor(dynamicCoreSize, dynamicMaximumSize, keepAliveTime.get(), unit, workQueue, threadFactory);
    }
}

執(zhí)行Command的方式一共四種,直接看官方文檔(https://github.com/Netflix/Hystrix/wiki/How-it-Works)傀蓉,具體區(qū)別如下:

  • execute():以同步堵塞方式執(zhí)行run()笨使。調(diào)用execute()后,hystrix先創(chuàng)建一個(gè)新線程運(yùn)行run()僚害,接著調(diào)用程序要在execute()調(diào)用處一直堵塞著,直到run()運(yùn)行完成。

  • queue():以異步非堵塞方式執(zhí)行run()萨蚕。調(diào)用queue()就直接返回一個(gè)Future對(duì)象靶草,同時(shí)hystrix創(chuàng)建一個(gè)新線程運(yùn)行run(),調(diào)用程序通過(guò)Future.get()拿到run()的返回結(jié)果岳遥,而Future.get()是堵塞執(zhí)行的奕翔。

  • observe():事件注冊(cè)前執(zhí)行run()/construct()。第一步是事件注冊(cè)前浩蓉,先調(diào)用observe()自動(dòng)觸發(fā)執(zhí)行run()/construct()(如果繼承的是HystrixCommand派继,hystrix將創(chuàng)建新線程非堵塞執(zhí)行run();如果繼承的是HystrixObservableCommand捻艳,將以調(diào)用程序線程堵塞執(zhí)行construct())驾窟,第二步是從observe()返回后調(diào)用程序調(diào)用subscribe()完成事件注冊(cè),如果run()/construct()執(zhí)行成功則觸發(fā)onNext()和onCompleted()认轨,如果執(zhí)行異常則觸發(fā)onError()绅络。

  • toObservable():事件注冊(cè)后執(zhí)行run()/construct()。第一步是事件注冊(cè)前嘁字,調(diào)用toObservable()就直接返回一個(gè)Observable<String>對(duì)象恩急,第二步調(diào)用subscribe()完成事件注冊(cè)后自動(dòng)觸發(fā)執(zhí)行run()/construct()(如果繼承的是HystrixCommand,hystrix將創(chuàng)建新線程非堵塞執(zhí)行run()纪蜒,調(diào)用程序不必等待run()衷恭;如果繼承的是HystrixObservableCommand,將以調(diào)用程序線程堵塞執(zhí)行construct()纯续,調(diào)用程序等待construct()執(zhí)行完才能繼續(xù)往下走)随珠,如果run()/construct()執(zhí)行成功則觸發(fā)onNext()和onCompleted(),如果執(zhí)行異常則觸發(fā)onError()
    注:
    execute()和queue()是在HystrixCommand中杆烁,observe()和toObservable()是在HystrixObservableCommand 中牙丽。從底層實(shí)現(xiàn)來(lái)講,HystrixCommand其實(shí)也是利用Observable實(shí)現(xiàn)的(看Hystrix源碼兔魂,可以發(fā)現(xiàn)里面大量使用了RxJava)烤芦,盡管它只返回單個(gè)結(jié)果。HystrixCommand的queue方法實(shí)際上是調(diào)用了toObservable().toBlocking().toFuture()析校,而execute方法實(shí)際上是調(diào)用了queue().get()构罗。

如何應(yīng)用到實(shí)際代碼中
package myHystrix.threadpool;

import com.netflix.hystrix.*;
import org.junit.Test;

import java.util.List;
import java.util.concurrent.Future;

/**
 * Created by wangxindong on 2017/8/4.
 */
public class GetOrderCommand extends HystrixCommand<List> {

    OrderService orderService;

    public GetOrderCommand(String name){
        super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ThreadPoolTestGroup"))
                .andCommandKey(HystrixCommandKey.Factory.asKey("testCommandKey"))
                .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey(name))
                .andCommandPropertiesDefaults(
                        HystrixCommandProperties.Setter()
                                .withExecutionTimeoutInMilliseconds(5000)
                )
                .andThreadPoolPropertiesDefaults(
                        HystrixThreadPoolProperties.Setter()
                                .withMaxQueueSize(10)   //配置隊(duì)列大小
                                .withCoreSize(2)    // 配置線程池里的線程數(shù)
                )
        );
    }

    @Override
    protected List run() throws Exception {
        return orderService.getOrderList();
    }

    public static class UnitTest {
        @Test
        public void testGetOrder(){
//            new GetOrderCommand("hystrix-order").execute();
            Future<List> future =new GetOrderCommand("hystrix-order").queue();
        }

    }
}
總結(jié)

執(zhí)行依賴代碼的線程與請(qǐng)求線程(比如Tomcat線程)分離,請(qǐng)求線程可以自由控制離開(kāi)的時(shí)間智玻,這也是我們通常說(shuō)的異步編程遂唧,Hystrix是結(jié)合RxJava來(lái)實(shí)現(xiàn)的異步編程。通過(guò)設(shè)置線程池大小來(lái)控制并發(fā)訪問(wèn)量吊奢,當(dāng)線程飽和的時(shí)候可以拒絕服務(wù)盖彭,防止依賴問(wèn)題擴(kuò)散。

線程隔離.png

線程隔離的優(yōu)點(diǎn):
[1]:應(yīng)用程序會(huì)被完全保護(hù)起來(lái),即使依賴的一個(gè)服務(wù)的線程池滿了召边,也不會(huì)影響到應(yīng)用程序的其他部分铺呵。
[2]:我們給應(yīng)用程序引入一個(gè)新的風(fēng)險(xiǎn)較低的客戶端lib的時(shí)候,如果發(fā)生問(wèn)題隧熙,也是在本lib中片挂,并不會(huì)影響到其他內(nèi)容,因此我們可以大膽的引入新lib庫(kù)贞盯。
[3]:當(dāng)依賴的一個(gè)失敗的服務(wù)恢復(fù)正常時(shí)音念,應(yīng)用程序會(huì)立即恢復(fù)正常的性能。
[4]:如果我們的應(yīng)用程序一些參數(shù)配置錯(cuò)誤了躏敢,線程池的運(yùn)行狀況將會(huì)很快顯示出來(lái)闷愤,比如延遲、超時(shí)父丰、拒絕等肝谭。同時(shí)可以通過(guò)動(dòng)態(tài)屬性實(shí)時(shí)執(zhí)行來(lái)處理糾正錯(cuò)誤的參數(shù)配置。
[5]:如果服務(wù)的性能有變化蛾扇,從而需要調(diào)整攘烛,比如增加或者減少超時(shí)時(shí)間,更改重試次數(shù)镀首,就可以通過(guò)線程池指標(biāo)動(dòng)態(tài)屬性修改坟漱,而且不會(huì)影響到其他調(diào)用請(qǐng)求。
[6]:除了隔離優(yōu)勢(shì)外更哄,hystrix擁有專門的線程池可提供內(nèi)置的并發(fā)功能芋齿,使得可以在同步調(diào)用之上構(gòu)建異步的外觀模式,這樣就可以很方便的做異步編程(Hystrix引入了Rxjava異步框架)成翩。

盡管線程池提供了線程隔離觅捆,我們的客戶端底層代碼也必須要有超時(shí)設(shè)置,不能無(wú)限制的阻塞以致線程池一直飽和麻敌。

線程隔離的缺點(diǎn):
[1]:線程池的主要缺點(diǎn)就是它增加了計(jì)算的開(kāi)銷栅炒,每個(gè)業(yè)務(wù)請(qǐng)求(被包裝成命令)在執(zhí)行的時(shí)候,會(huì)涉及到請(qǐng)求排隊(duì)术羔,調(diào)度和上下文切換赢赊。不過(guò)Netflix公司內(nèi)部認(rèn)為線程隔離開(kāi)銷足夠小,不會(huì)產(chǎn)生重大的成本或性能的影響级历。

The Netflix API processes 10+ billion Hystrix Command executions per day using thread isolation. Each API instance has 40+ thread-pools with 5–20 threads in each (most are set to 10).
Netflix API每天使用線程隔離處理10億次Hystrix Command執(zhí)行释移。 每個(gè)API實(shí)例都有40多個(gè)線程池,每個(gè)線程池中有5-20個(gè)線程(大多數(shù)設(shè)置為10個(gè))寥殖。

對(duì)于不依賴網(wǎng)絡(luò)訪問(wèn)的服務(wù)玩讳,比如只依賴內(nèi)存緩存這種情況下涩蜘,就不適合用線程池隔離技術(shù),而是采用信號(hào)量隔離熏纯,后面文章會(huì)介紹皱坛。

因此我們可以放心使用Hystrix的線程隔離技術(shù),來(lái)防止雪崩這種可怕的致命性線上故障豆巨。

轉(zhuǎn)載請(qǐng)注明出處,并附上鏈接 http://www.reibang.com/p/df1525d58c20

參考資料:
https://github.com/Netflix/Hystrix/wiki

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末掐场,一起剝皮案震驚了整個(gè)濱河市往扔,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌熊户,老刑警劉巖萍膛,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異嚷堡,居然都是意外死亡蝗罗,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門蝌戒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)串塑,“玉大人,你說(shuō)我怎么就攤上這事北苟∽耍” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵友鼻,是天一觀的道長(zhǎng)傻昙。 經(jīng)常有香客問(wèn)我,道長(zhǎng)彩扔,這世上最難降的妖魔是什么妆档? 我笑而不...
    開(kāi)封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮虫碉,結(jié)果婚禮上贾惦,老公的妹妹穿的比我還像新娘。我一直安慰自己蔗衡,他們只是感情好纤虽,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著绞惦,像睡著了一般逼纸。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上济蝉,一...
    開(kāi)封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天杰刽,我揣著相機(jī)與錄音菠发,去河邊找鬼。 笑死贺嫂,一個(gè)胖子當(dāng)著我的面吹牛滓鸠,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播第喳,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼糜俗,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了曲饱?” 一聲冷哼從身側(cè)響起悠抹,我...
    開(kāi)封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎扩淀,沒(méi)想到半個(gè)月后楔敌,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡驻谆,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年卵凑,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片胜臊。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡勺卢,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出区端,到底是詐尸還是另有隱情值漫,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布织盼,位于F島的核電站杨何,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏沥邻。R本人自食惡果不足惜危虱,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望唐全。 院中可真熱鬧埃跷,春花似錦、人聲如沸邮利。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)延届。三九已至剪勿,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間方庭,已是汗流浹背厕吉。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工酱固, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人头朱。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓运悲,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親项钮。 傳聞我的和親對(duì)象是個(gè)殘疾皇子班眯,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

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

  • 一、認(rèn)識(shí)Hystrix Hystrix是Netflix開(kāi)源的一款容錯(cuò)框架烁巫,包含常用的容錯(cuò)方法:線程池隔離鳖敷、信號(hào)量隔...
    新棟BOOK閱讀 4,024評(píng)論 0 19
  • 一、認(rèn)識(shí)Hystrix Hystrix是Netflix開(kāi)源的一款容錯(cuò)框架程拭,包含常用的容錯(cuò)方法:線程池隔離、信號(hào)量隔...
    新棟BOOK閱讀 26,460評(píng)論 1 37
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理棍潘,服務(wù)發(fā)現(xiàn)恃鞋,斷路器,智...
    卡卡羅2017閱讀 134,599評(píng)論 18 139
  • 導(dǎo)語(yǔ):網(wǎng)上資料(尤其中文文檔)對(duì)hystrix基礎(chǔ)功能的解釋比較籠統(tǒng)亦歉,看了往往一頭霧水恤浪。為此,本文將通過(guò)若干dem...
    star24閱讀 99,698評(píng)論 36 119
  • (git上的源碼:https://gitee.com/rain7564/spring_microservices_...
    sprainkle閱讀 9,331評(píng)論 13 33