rest服务器性能,使用多线程提高REST服务性能
目錄
使用Runnable異步處理rest服務
使用DeferredResult異步處理rest服務
異步處理配置
一、前言
先來說一下為什么需要異步處理rest服務?
傳統的同步處理:http請求進來,tomcat或者其他的容器會有一個相應的線程去處理http請求,所有的業務邏輯都會在這個線程中執行,最后會給出一個http響應。但是一般對于tomcat這種容器,它可以管理的線程是有數量的,當數量達到一定程度之后,再有請求進來,Tomcat就已經沒辦法處理了(因為所有的線程都已經在工作了)。
同步處理http請求
所謂的異步處理是什么?
異步處理指的是,當一個http請求進來之后Tomcat的主線程去調起一個副線程來執行業務邏輯,當副線程處理邏輯完成之后,主線程再將執行結果返回回去,在副線程處理業務邏輯的過程中,主線程是可以空閑出來去處理其他請求的。如果采用這種模式去處理的話,對于我們的服務器的吞吐量會有一個明顯的提升
異步處理http請求
二、同步的處理方式
首先,為了效果明顯,我先需要一個打印日志的對象logger
private Logger logger = LoggerFactory.getLogger(getClass());
然后我去定義一個controller,模擬一個下訂單的一個請求,其中的sleep就相當于下單的業務邏輯
@RequestMapping("/order")
public String order() throws InterruptedException {
logger.info("主線程開始");
Thread.sleep(1000);
logger.info("主線程返回");
return "success";
}
最后訪問這個接口,可以看到打印的輸出內容:
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 : 主線程返回
可以看到都是一個線程[nio-8060-exec-1] 打印出來的
三、異步處理---使用Runnable
首先定義一個controller
@RequestMapping("/callable")
public Callable callable() throws InterruptedException {
logger.info("主線程開始");
//單開一個線程
Callable result = new Callable() {
@Override
public String call() throws Exception {
logger.info("副線程開始");
Thread.sleep(1000);
logger.info("副線程返回");
return "success";
}
};
logger.info("主線程返回");
return result;
}
當我們去訪問的時候,可以看到打印的日志:
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秒開始的,幾乎是在同時就返回了,副線程[MvcAsync1]也是在21秒開始,然后去睡了1秒,在22秒的時候返回了。主線程基本上沒有任何的停頓,而是主線程在喚醒了副線程之后立刻就返回了。也就是說,副線程在處理業務的時間里面,主線程可以空閑出來去處理其他的業務請求。以此來提升服務器的吞吐量。
四、異步處理---使用DeferredResult
我已經知道了使用runnable去實現異步處理,為什么還需要使用DeferredResult去處理呢?是因為當我們使用runnable來異步處理的時候,副線程必須是由主線程來調起的,在真正的企業級開發里面有的時候場景是要比這個復雜的,我們還是來用下單這個例子來說明一下:
使用DeferredResult來進行異步處理
在圖中可以看到,真正處理業務邏輯應用和接受下單請求的應用并不是一臺服務器,是兩臺服務器,當應用1接受到下單請求之后,它會把這個請求放到一個消息隊列mq里面,然后另一個服務器去監聽這個消息隊列,當它知道消息隊列里面有下單的請求之后,應用2便會去處理下單的邏輯,當它將下單的業務處理完成之后,它會把處理結果放到這個消息隊列中,同時在應用1里面有另外一個線程2去監聽這個消息隊列,當它發現這個消息隊列中有處理下單的結果的時候,它會根據這個結果去返回一個http響應。
在這個場景里面,線程1和線程2完全是隔離的,它們倆誰也不知道對方的存在http請求是由線程1來處理的,而最終的處理結果是放在消息隊列里面由線程2去監聽的。
在這個場景下,實現Runnable是滿足不了這個需求的,這時就需要用到DeferredResult
代碼
我不會去開發應用2,我也不會去搭建這個消息隊列,具體的做法:
1.我會用對象來模擬這個消息隊列,在接受到下單請求之后會延遲一秒,處理完之后會在對象中放一個“處理完成”這樣一個消息
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());
/**
* 下單的消息
* 當這個字符串有值的時候就認為接到了一個下單的消息
*/
private String placeOrder;
/**
* 訂單完成的消息
* 當這個字符串有值的時候就認為訂單處理完成
*/
private String completeOrder;
public String getPlaceOrder() {
return placeOrder;
}
/**
* 在收到下單請求之后睡一秒,然后相當于處理完成
* @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.開發線程1的處理
@Autowired
private MockQueue mockQueue;
@Autowired
private DeferredResultHolder deferredResultHolder;
@RequestMapping("/deferred")
public DeferredResult deferred() throws InterruptedException {
logger.info("主線程開始");
//生成一個隨機的訂單號
String orderNum = RandomStringUtils.randomNumeric(8);
//放到消息隊列里面去
mockQueue.setPlaceOrder(orderNum);
DeferredResult result = new DeferredResult();
deferredResultHolder.getMap().put(orderNum,result);
logger.info("主線程返回");
return result;
}
3.監聽器(線程2)的代碼,當監聽到“處理完成”這個消息的時候它會把結果響應回去
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;
/**
* 隊列的監聽器
* ContextRefreshedEvent這個事件就是整個spring初始化完畢的一個事件
* 監聽這個事件就相當于“當系統整個啟動起來之后我要做什么事情(監聽消息隊列里面的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) {
//因為是一個無限循環,所以需要單開一個線程
new Thread(() -> {
while (true){
//當模擬的這個隊列中訂單完成的這個字段有值了,不為空
if (StringUtils.isNotBlank(mockQueue.getCompleteOrder())){
String orderNum = mockQueue.getCompleteOrder();
logger.info("返回訂單處理結果:"+orderNum);
//當調用setResult方法的時候就意味著整個訂單處理的業務完成了,該去返回結果了
deferredResultHolder.getMap().get(orderNum).setResult("訂單處理完成");
mockQueue.setCompleteOrder(null);
}else{
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
4.開發DeferredResultHolder,因為我要在線程1、線程2這兩個線程之間去傳遞DeferredResult對象,相當于是讓他倆建立一定的聯系
@Component
public class DeferredResultHolder {
/**
* key代表訂單號,DeferredResult放的是處理結果
*/
private Map> map = new HashMap>() ;
public Map> getMap() {
return map;
}
public void setMap(Map> map) {
this.map = map;
}
}
運行
可以看到控制臺中打印的結果:
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 : 返回訂單處理結果:42147337
可以看到有三個線程去進行下單的這個業務邏輯:
1、主線程[nio-8060-exec-1]
2、[ Thread-37]為應用2的線程,接到下單請求然后去進行處理,
3、[ Thread-24]是應用1中的線程2監聽到消息處理完畢,進行返回
這三個線程是相互隔離的,誰都不知道誰的存在,互相通過消息隊列進行通訊。
五、相關異步配置
我們都知道攔截器,在webConfig中繼承了WebMvcConfigurerAdapter類,在這個類中重寫了addInterceptor方法去自定義攔截器的,但是在異步的情況下跟同步的處理是不一樣的,里面有個configureAsyncSupport方法,用來配置異步支持的。其中的configurer有四個方法:
configurer中的方法
其中,registerCallableInterceptors和registerDeferredResultInterceptors可以針對Callable和DeferredResult兩種異步方式去注冊攔截器,里面有特定的異步攔截方法(比如handleTimeout異步請求如果超時了怎么處理)。
第三種方法setDefaultTimeout用來設置異步請求的超時時間,因為是開了異步線程去處理業務邏輯,那么那些線程有可能阻塞或者死掉沒有響應,在多長的時間內,http就響應回去釋放掉,需要用這個來設置。
第四種方法SetTaskExecutor,在默認的情況下,比如用runnable去執行的時候,Spring其實是用一個簡單的異步線程池去處理的,它不是一個真正的一個線程池,而是每次都會創建一個新的線程,我們可以自定義設置一些可重用的線程池來替代Spring默認的不支持重用的線程池。
總結
以上是生活随笔為你收集整理的rest服务器性能,使用多线程提高REST服务性能的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Excel如何自动创建财会工作表超链接目
- 下一篇: 服务器任务管理器详细信息,任务管理器服务