rest服务器性能,使用多线程提高REST服务性能
目錄
使用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)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Excel如何自动创建财会工作表超链接目
- 下一篇: 服务器任务管理器详细信息,任务管理器服务