日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 >

rest服务器性能,使用多线程提高REST服务性能

發(fā)布時(shí)間:2023/12/15 45 豆豆
生活随笔 收集整理的這篇文章主要介紹了 rest服务器性能,使用多线程提高REST服务性能 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

目錄

使用Runnable異步處理rest服務(wù)

使用DeferredResult異步處理rest服務(wù)

異步處理配置

一、前言

先來說一下為什么需要異步處理rest服務(wù)?

傳統(tǒng)的同步處理:http請求進(jìn)來,tomcat或者其他的容器會有一個(gè)相應(yīng)的線程去處理http請求,所有的業(yè)務(wù)邏輯都會在這個(gè)線程中執(zhí)行,最后會給出一個(gè)http響應(yīng)。但是一般對于tomcat這種容器,它可以管理的線程是有數(shù)量的,當(dāng)數(shù)量達(dá)到一定程度之后,再有請求進(jìn)來,Tomcat就已經(jīng)沒辦法處理了(因?yàn)樗械木€程都已經(jīng)在工作了)。

同步處理http請求

所謂的異步處理是什么?

異步處理指的是,當(dāng)一個(gè)http請求進(jìn)來之后Tomcat的主線程去調(diào)起一個(gè)副線程來執(zhí)行業(yè)務(wù)邏輯,當(dāng)副線程處理邏輯完成之后,主線程再將執(zhí)行結(jié)果返回回去,在副線程處理業(yè)務(wù)邏輯的過程中,主線程是可以空閑出來去處理其他請求的。如果采用這種模式去處理的話,對于我們的服務(wù)器的吞吐量會有一個(gè)明顯的提升

異步處理http請求

二、同步的處理方式

首先,為了效果明顯,我先需要一個(gè)打印日志的對象logger

private Logger logger = LoggerFactory.getLogger(getClass());

然后我去定義一個(gè)controller,模擬一個(gè)下訂單的一個(gè)請求,其中的sleep就相當(dāng)于下單的業(yè)務(wù)邏輯

@RequestMapping("/order")

public String order() throws InterruptedException {

logger.info("主線程開始");

Thread.sleep(1000);

logger.info("主線程返回");

return "success";

}

最后訪問這個(gè)接口,可以看到打印的輸出內(nèi)容:

2019-01-02 11:26:07.877 INFO 12364 --- [nio-8060-exec-1] com.tinner.web.async.AsyncController : 主線程開始

2019-01-02 11:26:08.877 INFO 12364 --- [nio-8060-exec-1] com.tinner.web.async.AsyncController : 主線程返回

可以看到都是一個(gè)線程[nio-8060-exec-1] 打印出來的

三、異步處理---使用Runnable

首先定義一個(gè)controller

@RequestMapping("/callable")

public Callable callable() throws InterruptedException {

logger.info("主線程開始");

//單開一個(gè)線程

Callable result = new Callable() {

@Override

public String call() throws Exception {

logger.info("副線程開始");

Thread.sleep(1000);

logger.info("副線程返回");

return "success";

}

};

logger.info("主線程返回");

return result;

}

當(dāng)我們?nèi)ピL問的時(shí)候,可以看到打印的日志:

2019-01-02 11:37:21.098 INFO 13908 --- [nio-8060-exec-4] com.tinner.web.async.AsyncController : 主線程開始

2019-01-02 11:37:21.099 INFO 13908 --- [nio-8060-exec-4] com.tinner.web.async.AsyncController : 主線程返回

2019-01-02 11:37:21.108 INFO 13908 --- [ MvcAsync1] com.tinner.web.async.AsyncController : 副線程開始

2019-01-02 11:37:22.108 INFO 13908 --- [ MvcAsync1] com.tinner.web.async.AsyncController : 副線程返回

可以看到 主線程[nio-8060-exec-4]是在21秒開始的,幾乎是在同時(shí)就返回了,副線程[MvcAsync1]也是在21秒開始,然后去睡了1秒,在22秒的時(shí)候返回了。主線程基本上沒有任何的停頓,而是主線程在喚醒了副線程之后立刻就返回了。也就是說,副線程在處理業(yè)務(wù)的時(shí)間里面,主線程可以空閑出來去處理其他的業(yè)務(wù)請求。以此來提升服務(wù)器的吞吐量。

四、異步處理---使用DeferredResult

我已經(jīng)知道了使用runnable去實(shí)現(xiàn)異步處理,為什么還需要使用DeferredResult去處理呢?是因?yàn)楫?dāng)我們使用runnable來異步處理的時(shí)候,副線程必須是由主線程來調(diào)起的,在真正的企業(yè)級開發(fā)里面有的時(shí)候場景是要比這個(gè)復(fù)雜的,我們還是來用下單這個(gè)例子來說明一下:

使用DeferredResult來進(jìn)行異步處理

在圖中可以看到,真正處理業(yè)務(wù)邏輯應(yīng)用和接受下單請求的應(yīng)用并不是一臺服務(wù)器,是兩臺服務(wù)器,當(dāng)應(yīng)用1接受到下單請求之后,它會把這個(gè)請求放到一個(gè)消息隊(duì)列mq里面,然后另一個(gè)服務(wù)器去監(jiān)聽這個(gè)消息隊(duì)列,當(dāng)它知道消息隊(duì)列里面有下單的請求之后,應(yīng)用2便會去處理下單的邏輯,當(dāng)它將下單的業(yè)務(wù)處理完成之后,它會把處理結(jié)果放到這個(gè)消息隊(duì)列中,同時(shí)在應(yīng)用1里面有另外一個(gè)線程2去監(jiān)聽這個(gè)消息隊(duì)列,當(dāng)它發(fā)現(xiàn)這個(gè)消息隊(duì)列中有處理下單的結(jié)果的時(shí)候,它會根據(jù)這個(gè)結(jié)果去返回一個(gè)http響應(yīng)。

在這個(gè)場景里面,線程1和線程2完全是隔離的,它們倆誰也不知道對方的存在http請求是由線程1來處理的,而最終的處理結(jié)果是放在消息隊(duì)列里面由線程2去監(jiān)聽的。

在這個(gè)場景下,實(shí)現(xiàn)Runnable是滿足不了這個(gè)需求的,這時(shí)就需要用到DeferredResult

代碼

我不會去開發(fā)應(yīng)用2,我也不會去搭建這個(gè)消息隊(duì)列,具體的做法:

1.我會用對象來模擬這個(gè)消息隊(duì)列,在接受到下單請求之后會延遲一秒,處理完之后會在對象中放一個(gè)“處理完成”這樣一個(gè)消息

package com.tinner.web.async;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.stereotype.Component;

@Component

public class MockQueue {

private Logger logger = LoggerFactory.getLogger(getClass());

/**

* 下單的消息

* 當(dāng)這個(gè)字符串有值的時(shí)候就認(rèn)為接到了一個(gè)下單的消息

*/

private String placeOrder;

/**

* 訂單完成的消息

* 當(dāng)這個(gè)字符串有值的時(shí)候就認(rèn)為訂單處理完成

*/

private String completeOrder;

public String getPlaceOrder() {

return placeOrder;

}

/**

* 在收到下單請求之后睡一秒,然后相當(dāng)于處理完成

* @param placeOrder

* @throws InterruptedException

*/

public void setPlaceOrder(String placeOrder) throws InterruptedException {

new Thread(() -> {

logger.info("接到下單請求,"+placeOrder);

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

//訂單處理完成

this.completeOrder = placeOrder;

logger.info("下單請求處理完成,"+placeOrder);

}).start();

}

public String getCompleteOrder() {

return completeOrder;

}

public void setCompleteOrder(String completeOrder) {

this.completeOrder = completeOrder;

}

}

2.開發(fā)線程1的處理

@Autowired

private MockQueue mockQueue;

@Autowired

private DeferredResultHolder deferredResultHolder;

@RequestMapping("/deferred")

public DeferredResult deferred() throws InterruptedException {

logger.info("主線程開始");

//生成一個(gè)隨機(jī)的訂單號

String orderNum = RandomStringUtils.randomNumeric(8);

//放到消息隊(duì)列里面去

mockQueue.setPlaceOrder(orderNum);

DeferredResult result = new DeferredResult();

deferredResultHolder.getMap().put(orderNum,result);

logger.info("主線程返回");

return result;

}

3.監(jiān)聽器(線程2)的代碼,當(dāng)監(jiān)聽到“處理完成”這個(gè)消息的時(shí)候它會把結(jié)果響應(yīng)回去

package com.tinner.web.async;

import org.apache.commons.lang.StringUtils;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.context.ApplicationListener;

import org.springframework.context.event.ContextClosedEvent;

import org.springframework.context.event.ContextRefreshedEvent;

import org.springframework.stereotype.Component;

/**

* 隊(duì)列的監(jiān)聽器

* ContextRefreshedEvent這個(gè)事件就是整個(gè)spring初始化完畢的一個(gè)事件

* 監(jiān)聽這個(gè)事件就相當(dāng)于“當(dāng)系統(tǒng)整個(gè)啟動起來之后我要做什么事情(監(jiān)聽消息隊(duì)列里面的completeOrder中的值)”

*/

@Component

public class QueueListener implements ApplicationListener {

private Logger logger = LoggerFactory.getLogger(getClass());

@Autowired

private MockQueue mockQueue;

@Autowired

private DeferredResultHolder deferredResultHolder;

@Override

public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {

//因?yàn)槭且粋€(gè)無限循環(huán),所以需要單開一個(gè)線程

new Thread(() -> {

while (true){

//當(dāng)模擬的這個(gè)隊(duì)列中訂單完成的這個(gè)字段有值了,不為空

if (StringUtils.isNotBlank(mockQueue.getCompleteOrder())){

String orderNum = mockQueue.getCompleteOrder();

logger.info("返回訂單處理結(jié)果:"+orderNum);

//當(dāng)調(diào)用setResult方法的時(shí)候就意味著整個(gè)訂單處理的業(yè)務(wù)完成了,該去返回結(jié)果了

deferredResultHolder.getMap().get(orderNum).setResult("訂單處理完成");

mockQueue.setCompleteOrder(null);

}else{

try {

Thread.sleep(100);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}).start();

}

}

4.開發(fā)DeferredResultHolder,因?yàn)槲乙诰€程1、線程2這兩個(gè)線程之間去傳遞DeferredResult對象,相當(dāng)于是讓他倆建立一定的聯(lián)系

@Component

public class DeferredResultHolder {

/**

* key代表訂單號,DeferredResult放的是處理結(jié)果

*/

private Map> map = new HashMap>() ;

public Map> getMap() {

return map;

}

public void setMap(Map> map) {

this.map = map;

}

}

運(yùn)行

可以看到控制臺中打印的結(jié)果:

2019-01-02 12:25:54.968 INFO 19356 --- [nio-8060-exec-1] com.tinner.web.async.AsyncController : 主線程開始

2019-01-02 12:25:54.970 INFO 19356 --- [nio-8060-exec-1] com.tinner.web.async.AsyncController : 主線程返回

2019-01-02 12:25:54.970 INFO 19356 --- [ Thread-37] com.tinner.web.async.MockQueue : 接到下單請求,42147337

2019-01-02 12:25:55.970 INFO 19356 --- [ Thread-37] com.tinner.web.async.MockQueue : 下單請求處理完成,42147337

2019-01-02 12:25:55.984 INFO 19356 --- [ Thread-24] com.tinner.web.async.QueueListener : 返回訂單處理結(jié)果:42147337

可以看到有三個(gè)線程去進(jìn)行下單的這個(gè)業(yè)務(wù)邏輯:

1、主線程[nio-8060-exec-1]

2、[ Thread-37]為應(yīng)用2的線程,接到下單請求然后去進(jìn)行處理,

3、[ Thread-24]是應(yīng)用1中的線程2監(jiān)聽到消息處理完畢,進(jìn)行返回

這三個(gè)線程是相互隔離的,誰都不知道誰的存在,互相通過消息隊(duì)列進(jìn)行通訊。

五、相關(guān)異步配置

我們都知道攔截器,在webConfig中繼承了WebMvcConfigurerAdapter類,在這個(gè)類中重寫了addInterceptor方法去自定義攔截器的,但是在異步的情況下跟同步的處理是不一樣的,里面有個(gè)configureAsyncSupport方法,用來配置異步支持的。其中的configurer有四個(gè)方法:

configurer中的方法

其中,registerCallableInterceptors和registerDeferredResultInterceptors可以針對Callable和DeferredResult兩種異步方式去注冊攔截器,里面有特定的異步攔截方法(比如handleTimeout異步請求如果超時(shí)了怎么處理)。

第三種方法setDefaultTimeout用來設(shè)置異步請求的超時(shí)時(shí)間,因?yàn)槭情_了異步線程去處理業(yè)務(wù)邏輯,那么那些線程有可能阻塞或者死掉沒有響應(yīng),在多長的時(shí)間內(nèi),http就響應(yīng)回去釋放掉,需要用這個(gè)來設(shè)置。

第四種方法SetTaskExecutor,在默認(rèn)的情況下,比如用runnable去執(zhí)行的時(shí)候,Spring其實(shí)是用一個(gè)簡單的異步線程池去處理的,它不是一個(gè)真正的一個(gè)線程池,而是每次都會創(chuàng)建一個(gè)新的線程,我們可以自定義設(shè)置一些可重用的線程池來替代Spring默認(rèn)的不支持重用的線程池。

總結(jié)

以上是生活随笔為你收集整理的rest服务器性能,使用多线程提高REST服务性能的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。