亚洲视频二区_亚洲欧洲日本天天堂在线观看_日韩一区二区在线观看_中文字幕不卡一区

公告:魔扣目錄網(wǎng)為廣大站長提供免費收錄網(wǎng)站服務(wù),提交前請做好本站友鏈:【 網(wǎng)站目錄:http://www.430618.com 】, 免友鏈快審服務(wù)(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747


線程隔離淺析

 

前言

隨著微服務(wù)的流行,單體應(yīng)用被拆分成一個個獨立的微進(jìn)程,可能一個簡單的請求,需要多個微服務(wù)共同處理,這樣其實是增加了出錯的概率,所以如何保證在單個微服務(wù)出現(xiàn)問題的時候,對整個系統(tǒng)的負(fù)面影響降到最低,這就需要用到我們今天要介紹的線程隔離。

線程模型

在介紹線程隔離之前,我們先了解一下主流容器,框架的線程模型,因為微服務(wù)是一個個獨立的進(jìn)程,之間的調(diào)用其實就是走網(wǎng)絡(luò)io,網(wǎng)絡(luò)io的處理容器如Tomcat,通信框架如netty,微服務(wù)框架如dubbo,都很好的幫我們處理了底層的網(wǎng)絡(luò)io流,讓我們可以更加的關(guān)注于業(yè)務(wù)處理;

Netty

Netty是基于JAVA nio的高性能通信框架,使用了主從多線程模型,借鑒Netty系列之 Netty線程模型的一張圖片如下所示:

線程隔離淺析

 

主線程負(fù)責(zé)認(rèn)證,連接,成功之后交由從線程負(fù)責(zé)連接的讀寫操作,大致如下代碼:

EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();

ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup);

主線程是一個單線程,從線程是一個默認(rèn)為cpu*2個數(shù)的線程池,可以在我們的業(yè)務(wù)handler中做一個簡單測試:

    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        System.out.println("thread name=" + Thread.currentThread().getName() + " server receive msg=" + msg);
    }

服務(wù)端在讀取數(shù)據(jù)的時候打印一下當(dāng)前的線程:

thread name=nioEventLoopGroup-3-1 server receive msg="..."

可以發(fā)現(xiàn)這里使用的線程其實和處理io線程是同一個;

Dubbo

Dubbo的底層通信框架其實使用的就是Netty,但是Dubbo并沒有直接使用Netty的io線程來處理業(yè)務(wù),可以簡單在生產(chǎn)者端輸出當(dāng)前線程名稱:

thread name=DubboServerHandler-192.168.1.115:20880-thread-2,...

可以發(fā)現(xiàn)業(yè)務(wù)邏輯使用并不是nioEventLoopGroup線程,這是因為Dubbo有自己的線程模型,可以看看官網(wǎng)提供的模型圖:

線程隔離淺析

 

其中的Dispatcher調(diào)度器可以配置消息的處理線程:

  • all 所有消息都派發(fā)到線程池,包括請求,響應(yīng),連接事件,斷開事件,心跳等。
  • direct 所有消息都不派發(fā)到線程池,全部在 IO 線程上直接執(zhí)行。
  • message 只有請求響應(yīng)消息派發(fā)到線程池,其它連接斷開事件,心跳等消息,直接在 IO 線程上執(zhí)行。
  • execution 只有請求消息派發(fā)到線程池,不含響應(yīng),響應(yīng)和其它連接斷開事件,心跳等消息,直接在 IO 線程上執(zhí)行。
  • connection 在 IO 線程上,將連接斷開事件放入隊列,有序逐個執(zhí)行,其它消息派發(fā)到線程池。

Dubbo默認(rèn)使用FixedThreadPool,線程數(shù)默認(rèn)為200

Tomcat

Tomcat可以配置四種線程模型:BIO,NIO,APR,AIO;Tomcat8開始默認(rèn)配置NIO,此模型和Netty的線程模型很像,可以理解為都是Reactor模式,在此不過多介紹;其中maxThreads參數(shù)配置專門處理IO的Worker數(shù),默認(rèn)是200;可以在業(yè)務(wù)Controller中輸出當(dāng)前線程名稱:

ThreadName=http-nio-8888-exec-1...

可以發(fā)現(xiàn)處理業(yè)務(wù)的線程就是Tomcat的io線程;

為什么要線程隔離

從上面的介紹的線程模型可以知道,處理業(yè)務(wù)的時候還是使用的io線程比如Tomcat和netty,這樣會有什么問題那,比如當(dāng)前服務(wù)進(jìn)程需要同步調(diào)用另外三個微服務(wù),但是由于某個服務(wù)出現(xiàn)問題,導(dǎo)致線程阻塞,然后阻塞越積越多,占滿所有的io線程,最終當(dāng)前服務(wù)無法接受數(shù)據(jù),直至奔潰;Dubbo本身做了IO線程和業(yè)務(wù)線程的隔離,出現(xiàn)問題不至于影響IO線程,但是如果同樣有以上的問題,業(yè)務(wù)線程也會被占滿;做線程隔離的目的就是如果某個服務(wù)出現(xiàn)問題可以把它控制在一個小的范圍,不至于影響到全局;

如何做線程隔離

做線程隔離原理也很簡單,給每個請求分配單獨的線程池,每個請求做到互不影響,當(dāng)然也可以使用一些成熟的框架比如Hystrix(已經(jīng)不更新了),Sentinel等;

線程池隔離

SpringBoot+Tomcat做一個簡單的隔離測試,為了方便模擬配置MaxThreads=5,提供隔離Controller,大致如下所示:

@RequestMApping("/h1")
String home() throws Exception {
    System.out.println("h1-->ThreadName=" + Thread.currentThread().getName());
    Thread.sleep(200000);
    return "h1";
}
    
@RequestMapping("/h3")
String home3() {
    System.out.println("h3-->ThreadName=" + Thread.currentThread().getName());
    return "h3";
}

請求5次/h1請求,再次請求/h3,觀察日志:

h1-->ThreadName=http-nio-8888-exec-1
h1-->ThreadName=http-nio-8888-exec-2
h1-->ThreadName=http-nio-8888-exec-3
h1-->ThreadName=http-nio-8888-exec-4
h1-->ThreadName=http-nio-8888-exec-5

可以發(fā)現(xiàn)h1請求占滿了5條線程,請求h3的時候Tomcat無法接受請求;改造一下h1請求使用使用線程池來處理:

ExecutorService executorService = Executors.newFixedThreadPool(2);
List<Future<String>> list = new CopyOnWriteArrayList<Future<String>>();
@RequestMapping("/h2")
String home2() throws Exception {
    Future<String> result = executorService.submit(new Callable<String>() {
        @Override
        public String call() throws Exception {
            System.out.println("h2-->ThreadName=" + Thread.currentThread().getName());
            Thread.sleep(200000);
            return "h2";
        }
    });
    list.add(result);
    //降級處理
    if (list.size() >= 3) {
        return "h2-fallback";
    }
    String resultStr = result.get();
    list.remove(result);
    return resultStr;
}

如上部分偽代碼,使用線程池異步執(zhí)行,并且超出限制范圍做降級處理,這樣再次請求h3的時候,就不受影響了;當(dāng)然上面代碼比較簡陋,我們可以使用成熟的隔離框架;

Hystrix

Hystrix 提供兩種隔離策略:線程池隔離(Bulkhead Pattern)和信號量隔離,其中最推薦也是最常用的是線程池隔離。Hystrix的線程池隔離針對不同的資源分別創(chuàng)建不同的線程池,不同服務(wù)調(diào)用都發(fā)生在不同的線程池中,在線程池排隊、超時等阻塞情況時可以快速失敗,并可以提供fallback機制;可以看一個簡單的實例:

public class HelloCommand extends HystrixCommand<String> {

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

    @Override
    protected String run() throws InterruptedException {
        StringBuffer sb = new StringBuffer("Thread name=" + Thread.currentThread().getName() + ",");
        Thread.sleep(2000);
        return sb.append(System.currentTimeMillis()).toString();
    }

    @Override
    protected String getFallback() {
        return "Thread name=" + Thread.currentThread().getName() + ",fallback order";
    }

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        List<Future<String>> list = new ArrayList<>();
        System.out.println("Thread name=" + Thread.currentThread().getName());
        for (int i = 0; i < 8; i++) {
            Future<String> future = new HelloCommand("hystrix-order").queue();
            list.add(future);
        }
        for (Future<String> future : list) {
            System.out.println(future.get());
        }
        Thread.sleep(1000000);
    }
}

如上配置了處理此業(yè)務(wù)的線程數(shù)為2,并且指定當(dāng)線程滿了之后可以放入隊列的最大數(shù)量,運行此程序結(jié)果如下:

Thread name=main
Thread name=hystrix-hystrix-order-1,1589776137342
Thread name=hystrix-hystrix-order-2,1589776137342
Thread name=hystrix-hystrix-order-1,1589776139343
Thread name=hystrix-hystrix-order-2,1589776139343
Thread name=hystrix-hystrix-order-1,1589776141343
Thread name=hystrix-hystrix-order-2,1589776141343
Thread name=hystrix-hystrix-order-2,1589776143343
Thread name=main,fallback order

主線程執(zhí)行可以理解為就是io線程,業(yè)務(wù)執(zhí)行使用的是hystrix線程,線程數(shù)2+隊列5可以同時處理7條并發(fā)請求,超過的部分直接fallback;

信號量隔離

線程池隔離的好處是隔離度比較高,可以針對某個資源的線程池去進(jìn)行處理而不影響其它資源,但是代價就是線程上下文切換的開銷比較大,特別是對低延時的調(diào)用有比較大的影響;上面對線程模型的介紹,我們發(fā)現(xiàn)Tomcat默認(rèn)提供了200個io線程,Dubbo默認(rèn)提供了200個業(yè)務(wù)線程,線程數(shù)已經(jīng)很多了,如果每個命令在使用一個線程池,線程數(shù)會非常多,對系統(tǒng)的影響其實也很大;有一種更輕量的隔離方式就是信號量隔離,僅限制對某個資源調(diào)用的并發(fā)數(shù),而不是顯式地去創(chuàng)建線程池,所以開銷比較小;Hystrix和Sentinel都提供了信號量隔離方式,Hystrix已經(jīng)停止更新,而Sentinel干脆就沒有提供線程隔離,或者說線程隔離是沒有必要的,完全可以用更輕量的信號量隔離代替;

總結(jié)

本文從線程模型開始,講到了IO線程,以及為什么要分開IO線程和業(yè)務(wù)線程,具體如何去實現(xiàn),最后簡單介紹了一下更加輕量的信號量隔離,為什么說更加輕量哪,其實業(yè)務(wù)還是在IO線程處理,只不過會限制某個資源的并發(fā)數(shù),沒有多余的線程產(chǎn)生;當(dāng)然也不是說線程隔離就沒有價值了,其實還是要根據(jù)實際情況來定,根據(jù)你使用的容器,框架本身的線程模型來決定。

分享到:
標(biāo)簽:線程 隔離
用戶無頭像

網(wǎng)友整理

注冊時間:

網(wǎng)站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨大挑戰(zhàn)2018-06-03

數(shù)獨一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學(xué)四六

運動步數(shù)有氧達(dá)人2018-06-03

記錄運動步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績評定2018-06-03

通用課目體育訓(xùn)練成績評定