日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

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

發布時間:2023/12/15 编程问答 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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服务性能的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。