Java 利用DeferredResult實(shí)現(xiàn)http輪詢(xún)實(shí)時(shí)返回?cái)?shù)據(jù)接口
今天這篇文章呢,不難,其實(shí)是解答我一直以來(lái)心里的一個(gè)疑問(wèn)。是這樣的,之前看五八技術(shù)委員會(huì)主席沈劍老師的公眾號(hào)架構(gòu)師之路的一篇文章:http 如何像 tcp 一樣實(shí)時(shí)的收消息,里面其中的一個(gè)方案是用 http 短連接輪詢(xún)的方式實(shí)現(xiàn)“偽長(zhǎng)連接”。但是對(duì)于輪詢(xún),我們的第一反應(yīng)肯定是有延時(shí),但是標(biāo)題不是說(shuō)的是實(shí)時(shí)嗎?當(dāng)然我們可以把輪詢(xún)的時(shí)長(zhǎng)縮短一些,先不說(shuō)這樣大部分時(shí)間的輪詢(xún)調(diào)用,可能都沒(méi)消息返回,造成服務(wù)器資源浪費(fèi),輪詢(xún)時(shí)間再短也是有延時(shí)啊,所以難道是偽實(shí)時(shí)?反正一般消息延時(shí)個(gè)三五秒,甚至十秒八秒一分鐘,大家也不會(huì)在意,只會(huì)認(rèn)為對(duì)方返回慢,對(duì)不起,這是我們程序員的鍋,但是 http 真的不能實(shí)現(xiàn)實(shí)時(shí)嗎?沈劍老師提出了一種方法:首選 webim 和 webserver 之間建立一條 http 連接,專(zhuān)門(mén)用作消息通道,這條連接叫 http 消息連接。然后會(huì)有如下處理:
1. 沒(méi)有消息到達(dá)的時(shí)候,這個(gè) http 消息連接將被夯住,不返回,由于 http 是短連接,這個(gè) http 消息連接最多被夯住 90 秒,就會(huì)被斷開(kāi)(這是瀏覽器或者 webserver 的行為);
2. 在 1 的情況下,如果 http 消息連接被斷開(kāi),立馬再發(fā)起一個(gè) http 消息連接;
此時(shí)在在 1 和 2 的配合下,瀏覽器與 webserver 之間將永遠(yuǎn)有一條消息連接在,然后還有一種情況
3. 每次收到消息時(shí),這個(gè)消息連接就能及時(shí)將消息帶回瀏覽器頁(yè)面,并且在返回后,會(huì)立馬再發(fā)起一個(gè) http 消息連接
這樣就能做到使用 http 端連接輪詢(xún)的方式實(shí)現(xiàn)了實(shí)時(shí)收消息。不過(guò)需要說(shuō)明的是,其實(shí)還有一種情況:消息到達(dá)時(shí),上一個(gè) http 消息連接正在返回,也就是第二種情況的時(shí)候突然來(lái)了一個(gè)消息,此時(shí)沒(méi)有 http 消息連接可用。雖然理論上 http 消息連接的返回是瞬時(shí)的,沒(méi)有消息連接可用出現(xiàn)的概率極小,但是根據(jù)墨菲定律我們知道,這種情況肯定會(huì)出現(xiàn),所以這種情況下我們可以將消息暫存入消息池中,下一個(gè)消息連接到達(dá)后,無(wú)需等待,直接去消息池中取消息,將將消息帶回,然后立刻返回生成新的消息連接即可。
不過(guò)以上都不是今天這篇文章的重點(diǎn),和今天這篇文章的標(biāo)題也沒(méi)有任何關(guān)系。重點(diǎn)是當(dāng)時(shí)看了沈劍老師的這篇文章后我一直有一個(gè)疑問(wèn):第一步的時(shí)候如何夯住?總不能 sleep 吧,這多不優(yōu)雅啊,由于一直以為沒(méi)有遇到過(guò)類(lèi)似的需求,所以這么幾年來(lái)我也沒(méi)深究這個(gè)問(wèn)題,但是心里確實(shí)一直記著,直到前一段時(shí)間,聽(tīng)馬士兵教育的公開(kāi)課,當(dāng)時(shí)再講類(lèi)似的問(wèn)題的時(shí)候提到了夯住 http 的連接(具體是哪個(gè)問(wèn)題,還真不記得了),雖然當(dāng)時(shí)上課的老師沒(méi)提怎么實(shí)現(xiàn),但是評(píng)論區(qū)我問(wèn)了一下,如何夯住不返回?然后有一個(gè)同學(xué)回復(fù)說(shuō),用 DeferredResult,然后下課后搜了一下資料,果然可以,如下是實(shí)現(xiàn)的筆記,所以這才是重點(diǎn),希望對(duì)有這個(gè)疑問(wèn)的同學(xué)也有一點(diǎn)幫助。
1. 消息返回實(shí)體類(lèi),大家可以根據(jù)實(shí)際情況,自己定義即可:
package cn.bridgeli.deferredresulttest.entity;
import lombok.Data;
import lombok.Getter;
/**
* @author bridgeli
*/
@Data
public class DeferredResultResponse {
private Integer code;
private String msg;
public enum Msg {
TIMEOUT("超時(shí)"),
FAILED("失敗"),
SUCCESS("成功");
@Getter
private String desc;
Msg(String desc) {
this.desc = desc;
}
}
}
2. controller 接口:
package cn.bridgeli.deferredresulttest.controller;
import cn.bridgeli.deferredresulttest.entity.DeferredResultResponse;
import cn.bridgeli.deferredresulttest.service.DeferredResultService;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;
import javax.annotation.Resource;
/**
* @author bridgeli
*/
@RestController
@RequestMapping(value = "/deferred-result")
public class DeferredResultController {
@Resource
private DeferredResultService deferredResultService;
/**
* 為了方便測(cè)試,簡(jiǎn)單模擬一個(gè)
* 多個(gè)請(qǐng)求用同一個(gè)requestId會(huì)出問(wèn)題
*/
private final String requestId = "test";
@GetMapping(value = "/get")
public DeferredResult<DeferredResultResponse> get(@RequestParam(value = "timeout", required = false, defaultValue = "10000") Long timeout) {
DeferredResult<DeferredResultResponse> deferredResult = new DeferredResult<>(timeout);
deferredResultService.process(requestId, deferredResult);
return deferredResult;
}
/**
* 設(shè)置DeferredResult對(duì)象的result屬性,模擬異步操作
*
* @param desired
* @return
*/
@GetMapping(value = "/result")
public String settingResult(@RequestParam(value = "desired", required = false, defaultValue = "成功") String desired) {
DeferredResultResponse deferredResultResponse = new DeferredResultResponse();
if (DeferredResultResponse.Msg.SUCCESS.getDesc().equals(desired)) {
deferredResultResponse.setCode(HttpStatus.OK.value());
deferredResultResponse.setMsg(desired);
} else {
deferredResultResponse.setCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
deferredResultResponse.setMsg(DeferredResultResponse.Msg.FAILED.getDesc());
}
deferredResultService.settingResult(requestId, deferredResultResponse);
return "Done";
}
}
其中:/get 接口模擬沈劍老師說(shuō)的消息連接,/result 接口模擬有一條新消息來(lái)了,然后 /get 接口會(huì)立即返回。主要注意的是 requestId,在實(shí)際項(xiàng)目中不能使用同一個(gè),否則會(huì)出現(xiàn)問(wèn)題,這個(gè)測(cè)一下就知道了,也很容易想到原因。
3. service 實(shí)現(xiàn):
package cn.bridgeli.deferredresulttest.service;
import cn.bridgeli.deferredresulttest.entity.DeferredResultResponse;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.async.DeferredResult;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
/**
* @author bridgeli
*/
@Service
public class DeferredResultService {
private Map<String, Consumer<DeferredResultResponse>> taskMap;
public DeferredResultService() {
taskMap = new ConcurrentHashMap<>();
}
/**
* 將請(qǐng)求id與setResult映射
*
* @param requestId
* @param deferredResult
*/
public void process(String requestId, DeferredResult<DeferredResultResponse> deferredResult) {
// 請(qǐng)求超時(shí)的回調(diào)函數(shù)
deferredResult.onTimeout(() -> {
taskMap.remove(requestId);
DeferredResultResponse deferredResultResponse = new DeferredResultResponse();
deferredResultResponse.setCode(HttpStatus.REQUEST_TIMEOUT.value());
deferredResultResponse.setMsg(DeferredResultResponse.Msg.TIMEOUT.getDesc());
deferredResult.setResult(deferredResultResponse);
});
Optional.ofNullable(taskMap)
.filter(t -> !t.containsKey(requestId))
.orElseThrow(() -> new IllegalArgumentException(String.format("requestId=%s is existing", requestId)));
taskMap.putIfAbsent(requestId, deferredResult::setResult);
}
/**
* 這里相當(dāng)于異步的操作方法
* 設(shè)置DeferredResult對(duì)象的setResult方法
*
* @param requestId
* @param deferredResultResponse
*/
public void settingResult(String requestId, DeferredResultResponse deferredResultResponse) {
if (taskMap.containsKey(requestId)) {
Consumer<DeferredResultResponse> deferredResultResponseConsumer = taskMap.get(requestId);
// 這里相當(dāng)于DeferredResult對(duì)象的setResult方法
deferredResultResponseConsumer.accept(deferredResultResponse);
taskMap.remove(requestId);
}
}
}
文章最后,我想在說(shuō)明另外一個(gè)問(wèn)題,我們利用 DeferredResult 實(shí)現(xiàn)了 http 輪詢(xún)返回,其實(shí)換個(gè)思路想問(wèn)題,我們是不是也實(shí)現(xiàn)了 http 接口延時(shí)返回?所以如果你有延時(shí)返回的需求,同樣可以借助 DeferredResult 實(shí)現(xiàn)。
以上就是Java 利用 DeferredResult 實(shí)現(xiàn) http 輪詢(xún)實(shí)時(shí)返回?cái)?shù)據(jù)接口的詳細(xì)內(nèi)容,更多關(guān)于Java 實(shí)現(xiàn) http 輪詢(xún)實(shí)時(shí)返回?cái)?shù)據(jù)接口的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- java如何動(dòng)態(tài)的處理接口的返回?cái)?shù)據(jù)
- 如何使用java制作假數(shù)據(jù)接口
- Java?如何通過(guò)注解實(shí)現(xiàn)接口輸出時(shí)數(shù)據(jù)脫敏
- Java實(shí)現(xiàn)調(diào)用對(duì)方http接口得到返回?cái)?shù)據(jù)
- java讀取其他服務(wù)接口返回的json數(shù)據(jù)示例代碼
- Java編程通過(guò)list接口實(shí)現(xiàn)數(shù)據(jù)的增刪改查代碼示例
- Linux實(shí)時(shí)查看Java接口數(shù)據(jù)的案例方法
相關(guān)文章
SpringBoot整合PageHelper分頁(yè)無(wú)效的常見(jiàn)原因分析
這篇文章主要介紹了SpringBoot整合PageHelper分頁(yè)無(wú)效的常見(jiàn)原因分析,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-08-08
Java中關(guān)于內(nèi)存泄漏出現(xiàn)的原因匯總及如何避免內(nèi)存泄漏(超詳細(xì)版)
這篇文章主要介紹了Java中關(guān)于內(nèi)存泄漏出現(xiàn)的原因匯總及如何避免內(nèi)存泄漏(超詳細(xì)版)的相關(guān)資料,需要的朋友可以參考下2016-09-09
解決idea 從mapper方法直接點(diǎn)進(jìn)xml文件的問(wèn)題
這篇文章主要介紹了解決idea 從mapper方法直接點(diǎn)進(jìn)xml文件的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-02-02
Spring RedisTemplate 批量獲取值的2種方式小結(jié)
這篇文章主要介紹了Spring RedisTemplate 批量獲取值的2種方式小結(jié),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-06-06
SpringCloud-Gateway轉(zhuǎn)發(fā)WebSocket失敗問(wèn)題及解決
這篇文章主要介紹了SpringCloud-Gateway轉(zhuǎn)發(fā)WebSocket失敗問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-09-09
Java中String的JdbcTemplate連接SQLServer數(shù)據(jù)庫(kù)的方法
這篇文章主要介紹了Java中String的JdbcTemplate連接SQLServer數(shù)據(jù)庫(kù)的方法,在研發(fā)過(guò)程中我們需要與其他系統(tǒng)對(duì)接的場(chǎng)景,連接SQLServer拉取數(shù)據(jù),所以就用jdbc連接數(shù)據(jù)庫(kù)的方式連接外部數(shù)據(jù)源,需要的朋友可以參考下2021-10-10
Java中構(gòu)造器內(nèi)部的多態(tài)方法的行為實(shí)例分析
這篇文章主要介紹了Java中構(gòu)造器內(nèi)部的多態(tài)方法的行為,結(jié)合實(shí)例形式分析了java構(gòu)造器內(nèi)部多態(tài)方法相關(guān)原理、功能及操作技巧,需要的朋友可以參考下2019-10-10

