[TOC]
Spring - DeferredResult 異步返回實(shí)現(xiàn)
@Edit by Typora
Keyword
LongPolling毕箍、長(zhǎng)輪詢雹顺、請(qǐng)求異步返回
前言
通常我們經(jīng)常會(huì)遇到一些需要實(shí)現(xiàn)異步返回的場(chǎng)景检盼,如長(zhǎng)輪詢瓜饥、服務(wù)器端處理流程較為復(fù)雜并且處理時(shí)間比較長(zhǎng)的情況诊笤,這個(gè)時(shí)候嫡锌,如果接受http請(qǐng)求的線程被一直阻塞著,會(huì)導(dǎo)致服務(wù)器端無(wú)法接受處理更多的請(qǐng)求檬寂,導(dǎo)致拒絕服務(wù)的問題出現(xiàn)终抽,這個(gè)時(shí)候,將接收請(qǐng)求的線程讓出來(lái)焰薄,會(huì)大大提升服務(wù)器端并發(fā)能力拿诸。
Spring在3.2
的版本上就已經(jīng)為我們提供的相應(yīng)的機(jī)制,以應(yīng)對(duì)Http Nio的場(chǎng)景塞茅。
筆者在以下的示例使用的是5.2.9.RELEASE
的版本,請(qǐng)讀者使用高于3.2
的版本進(jìn)行驗(yàn)證季率。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.web.version}</version>
<scope>provided</scope>
</dependency>
關(guān)于DeferredResult
關(guān)于DeferredResult野瘦,在Spring的類注釋上是這樣描述的。
DeferredResult provides an alternative to using a Callable for asynchronous request processing. While a Callable is executed concurrently on behalf of the application, with a DeferredResult the application can produce the result from a thread of its choice.
Subclasses can extend this class to easily associate additional data or behavior with the DeferredResult. For example, one might want to associate the user used to create the DeferredResult by extending the class and adding an additional property for the user. In this way, the user could easily be accessed later without the need to use a data structure to do the mapping.
An example of associating additional behavior to this class might be realized by extending the class to implement an additional interface. For example, one might want to implement Comparable so that when the DeferredResult is added to a PriorityQueue it is handled in the correct order.
簡(jiǎn)要來(lái)說(shuō)就是飒泻,提供了一個(gè)可替代Callable進(jìn)行異步請(qǐng)求的方案鞭光,接口直接返回DeferredResult,Spring會(huì)在值設(shè)置進(jìn)該DeferredResult實(shí)例的時(shí)候泞遗,返回給請(qǐng)求方惰许。
如果讀者對(duì)Future及其派生類有所了解的話,可以發(fā)現(xiàn)史辙,兩者有異曲同工之妙汹买,都是等值回來(lái)之后進(jìn)行相應(yīng)的回調(diào)佩伤,可以將線程資源釋放出來(lái),做其他的事晦毙,節(jié)約服務(wù)器端的線程資源生巡,可以提高服務(wù)器端的并發(fā)能力。
示例代碼
package com.iwuyc.spring.web.demo;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.zcyiot.dm.commons.serializable.GsonUtil;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.async.DeferredResult;
import java.util.Map;
@RestController
@RequestMapping("/longpolling")
public class LongPollingController {
private static final Cache<String, DeferredResult<String>> SETTABLE_FUTURE_LOADING_CACHE = CacheBuilder.newBuilder().build();
/**
* 模擬長(zhǎng)輪詢的請(qǐng)求
*
* @param requestId 在該用例中见妒,使用該值做請(qǐng)求標(biāo)識(shí)孤荣,用于注冊(cè)、區(qū)別DeferredResult實(shí)例
* @return 返回DeferredResult實(shí)例給Spring框架
*/
@RequestMapping(method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public DeferredResult<String> longPolling(@RequestParam("requestId") String requestId) {
// 設(shè)定30秒超時(shí)须揣,如果30秒內(nèi)沒有設(shè)置值盐股,則直接返回timeoutResult指定的值
DeferredResult<String> result = new DeferredResult<>(30_000L, "{\"error\":\"timeout\"}");
if (null == requestId) {
// 如果缺少RequestId直接返回錯(cuò)誤
result.setResult("{\"error\":\"Missing important properties.\"}");
return result;
}
// 將請(qǐng)求的延期返回的值注冊(cè)到cache中,以便后續(xù)流程可以找到相應(yīng)的實(shí)例耻卡,進(jìn)行值設(shè)置遂庄。
SETTABLE_FUTURE_LOADING_CACHE.put(requestId, result);
// 設(shè)置完成的回調(diào)函數(shù),以便完成了之后劲赠,將該實(shí)例從緩存中清除涛目。
result.onCompletion(() -> SETTABLE_FUTURE_LOADING_CACHE.invalidate(requestId));
return result;
}
/**
* 模擬延時(shí)響應(yīng)
* @param body 模擬返回值
* @param requestId 長(zhǎng)輪詢中的RequestId。
*/
@PostMapping
public void response(@RequestBody Map<?, ?> body, @RequestParam("requestId") String requestId) {
// 根據(jù)RequestId找到之前請(qǐng)求的DeferredResult實(shí)例
final DeferredResult<String> responseFuture = SETTABLE_FUTURE_LOADING_CACHE.getIfPresent(requestId);
if (null == responseFuture) {
return;
}
// 將值設(shè)置到DeferredResult的實(shí)例中
responseFuture.setResult(GsonUtil.toJson(body));
}
}
該示例展示了長(zhǎng)輪詢的場(chǎng)景凛澎,方法longPolling模擬長(zhǎng)輪詢請(qǐng)求霹肝,超時(shí)時(shí)間為30秒。
response方法模擬服務(wù)器端處理完結(jié)果塑煎,并且返回給輪詢請(qǐng)求的場(chǎng)景沫换。這種方式只是一種示例,在實(shí)際生產(chǎn)中最铁,可能會(huì)直接將DeferredResult轉(zhuǎn)交給另外一個(gè)線程讯赏,進(jìn)行業(yè)務(wù)處理,處理完成之后冷尉,直接設(shè)置返回值漱挎,而無(wú)需注冊(cè)到統(tǒng)一的一個(gè)緩存中。
長(zhǎng)輪詢請(qǐng)求示例(對(duì)應(yīng)longPolling方法):
curl --location --request GET 'http://localhost:51001/longpolling?requestId=1' \
--header 'Cookie: hotusm.session.id=fb2dda00-8245-4c03-b19c-9e3842982607; JSESSIONID=8BB1C54884E2ECB04DC2FDA2277B5C83'
相應(yīng)請(qǐng)求示例(對(duì)應(yīng)response方法):
curl --location --request POST 'http://localhost:51001/longpolling?requestId=1' \
--header 'Content-Type: application/json' \
--header 'Cookie: hotusm.session.id=fb2dda00-8245-4c03-b19c-9e3842982607; JSESSIONID=8BB1C54884E2ECB04DC2FDA2277B5C83' \
--data-raw '{
"name":"Neil",
"address":"深圳"
}'
上面兩個(gè)示例是該示例的controller的兩個(gè)請(qǐng)求雀哨,可以使用curl進(jìn)行嘗試磕谅,也可以轉(zhuǎn)換為相應(yīng)的postman請(qǐng)求報(bào)文,進(jìn)行模擬請(qǐng)求雾棺。