摘要:四層負(fù)載均衡不會(huì)引起超時(shí)。動(dòng)態(tài)修改包的目標(biāo)地址,并轉(zhuǎn)發(fā)數(shù)據(jù)包使其到達(dá)不同的機(jī)器上來實(shí)現(xiàn)負(fù)載均衡的目的,因此節(jié)點(diǎn)不會(huì)引起超時(shí)。七層負(fù)載均衡等待上游響應(yīng)超時(shí)。例如使用多線程并發(fā)減少遠(yuǎn)程查詢的總體時(shí)間如需數(shù)據(jù)有序,可以使用方案。
B端業(yè)務(wù)經(jīng)常要提供下載報(bào)表的功能,一般的方法是先查詢出所有數(shù)據(jù),然后在內(nèi)存中組裝成報(bào)表(如XLS/XLSX格式)后統(tǒng)一輸出。但是如果生成報(bào)表需要查詢的數(shù)據(jù)量很大,遠(yuǎn)程服務(wù)的調(diào)用時(shí)間之和遠(yuǎn)遠(yuǎn)超過了鏈路上某節(jié)點(diǎn)(比如代理服務(wù)器Nginx、瀏覽器Chrome)的等待時(shí)間,因此該次Http連接就會(huì)被強(qiáng)制關(guān)閉,導(dǎo)致下載失敗。
下面的示例代碼調(diào)用了Thread.sleep,將處理線程掛起3分鐘,模擬耗時(shí)的數(shù)據(jù)查詢操作。
@GetMapping("/trade/income/excel") public HttpEntity常見超時(shí)原因和優(yōu)化思路downloadTradeIncome() { ServletOutputStream stream = response.getOutputStream(); response.setContentType("application/octet-stream;charset=UTF-8"); response.setHeader("Content-Disposition", "attachment;fileName=test.csv"); stream.write("start".getBytes(Charsets.UTF_8)); response.flushBuffer(); Thread.sleep((long) (3 * 64 * 1000));//chrome 2min超時(shí)會(huì)主動(dòng)斷開連接 stream.write("finish".getBytes(Charsets.UTF_8)); }
大型的Web應(yīng)用一般都不是單純的Client/Server模型。一次Http請(qǐng)求會(huì)在網(wǎng)絡(luò)鏈路上經(jīng)過多于2個(gè)的節(jié)點(diǎn)。
Chrome(用戶端的瀏覽器) <=> 四層負(fù)載均衡(工作在傳輸層,如LVS和MGW) <=> 七層負(fù)載均衡(工作在應(yīng)用層,如反向代理用的Nginx) <=> Tomcat(后端應(yīng)用服務(wù)器)。
鏈路上的每個(gè)節(jié)點(diǎn)都有可能會(huì)產(chǎn)生超時(shí),因此具體的超時(shí)原因也可以分為:
Chrome發(fā)起請(qǐng)求后,等待響應(yīng)超時(shí)。該值為120秒,且用戶不可更改,超時(shí)后頁面上會(huì)提示EmptyResponse。
四層負(fù)載均衡不會(huì)引起超時(shí)。LVS動(dòng)態(tài)修改TCP包的目標(biāo)IP地址,并轉(zhuǎn)發(fā)數(shù)據(jù)包使其到達(dá)不同的機(jī)器上來實(shí)現(xiàn)負(fù)載均衡的目的,因此LVS節(jié)點(diǎn)不會(huì)引起超時(shí)。個(gè)人理解,不一定準(zhǔn)確。
七層負(fù)載均衡等待上游響應(yīng)超時(shí)。Nginx代理了客戶端瀏覽器對(duì)后端服務(wù)器的Http請(qǐng)求,作為反向代理服務(wù)器需要“同時(shí)”維護(hù)與瀏覽器和后端服務(wù)器的Http連接,因此也會(huì)產(chǎn)生相應(yīng)的超時(shí),例如Nginx等待上游響應(yīng)超時(shí)就會(huì)產(chǎn)生504 Gateway Timeout。
Tomcat/Servlet處理超時(shí)。這層對(duì)應(yīng)本地環(huán)境產(chǎn)生的超時(shí),如Socket超時(shí)、InputStream/OutputStream超時(shí)。
對(duì)應(yīng)的超時(shí)優(yōu)化有3種思路。
1. 縮短后端查詢數(shù)據(jù)的時(shí)間。例如使用多線程并發(fā)減少遠(yuǎn)程查詢的總體時(shí)間(如需數(shù)據(jù)有序,可以使用Fork/Join方案)。
該方案的優(yōu)點(diǎn)是減少了對(duì)外的整體查詢的時(shí)間。缺點(diǎn)是多線程增加了開發(fā)和維護(hù)的難度;高并發(fā)壓力轉(zhuǎn)移到內(nèi)部的查詢服務(wù)上,對(duì)其QPS響應(yīng)提出了更高的要求。
2. 將數(shù)據(jù)查詢和下載的流程異步化。瀏覽器請(qǐng)求下載后,服務(wù)端立即返回報(bào)表的唯一標(biāo)識(shí)Key同時(shí)開始遠(yuǎn)程查詢數(shù)據(jù),客戶端可以憑借該Key查詢報(bào)表的生成進(jìn)度,報(bào)表完成后就可以下載;或者使用另一種方案,服務(wù)器在報(bào)表生成完成后通過一些渠道(如Long-Polling、WebSocket、即時(shí)通信軟件、郵件等)通知客戶端下載。
該方案的優(yōu)點(diǎn)是并發(fā)能力強(qiáng),不會(huì)阻塞服務(wù)器的Web連接池。缺點(diǎn)是需要開發(fā)Key的CRUD操作和相應(yīng)的UI;需要公有文件云的支持用于存儲(chǔ)生成的報(bào)表文件。
3. 服務(wù)端邊生成報(bào)表,瀏覽器邊下載報(bào)表。就像下載大文件一樣,瀏覽器不斷開和服務(wù)器的Http連接,同時(shí)服務(wù)器不斷向?yàn)g覽器追加Http體數(shù)據(jù)直到報(bào)表生成結(jié)束。
該方案的優(yōu)點(diǎn)是開發(fā)難度低、速度快。缺點(diǎn)是數(shù)據(jù)查詢是單線程的,速度較慢;而且文件下載會(huì)一直占用服務(wù)器的Web連接池,如果并發(fā)下載量較大可能會(huì)阻塞其他的Http請(qǐng)求。
因?yàn)樵趯?shí)際的業(yè)務(wù)開發(fā)中,前2種思路做的比較多,所以后文不再贅述。
方案3的具體實(shí)現(xiàn)該方案的關(guān)鍵在于業(yè)務(wù)方法返回后SpringMvc/Servlet不能主動(dòng)關(guān)閉Http連接,而是要像平常下載文件一樣保持Http的長連接(注意Http長連接要和Http 1.1協(xié)議默認(rèn)采用的Tcp長連接相區(qū)分),唯一不同的是這次瀏覽器無法提前知道文件的大小。因此對(duì)于技術(shù)方案我考慮有幾種選擇:
服務(wù)器邊查詢數(shù)據(jù)并生成,瀏覽器邊下載,像平常下載文件一樣。
分多次查詢/推送數(shù)據(jù),瀏覽器最后把數(shù)據(jù)組裝為報(bào)表。
輪詢(Polling)。客戶端輪詢服務(wù)器,每次查詢報(bào)表數(shù)據(jù)的一部分,查詢結(jié)束后再組裝成報(bào)表文件。
長輪詢(Long-Polling)。客戶端輪詢服務(wù)器,服務(wù)器在收到請(qǐng)求后Hold住Http連接,等待另一部分的數(shù)據(jù)查詢完成才釋放連接并返回Response。
WebSocket。支持Html5特性的瀏覽器和服務(wù)器之間建立Socket管道,可以雙向傳遞任意類型的消息。
第一種方案的優(yōu)點(diǎn)是不需要前端參與開發(fā),缺點(diǎn)是無法支持二進(jìn)制格式的報(bào)表文件(如XLS/XLSX),只能用文本格式(如CSV/TSV),這會(huì)帶來格式的損失,比如CSV格式里位數(shù)超過10位的數(shù)字會(huì)被Excel自動(dòng)顯示成科學(xué)記數(shù)法。第二種方案正好相反,需要前端開發(fā)人力,但是可以支持組裝二進(jìn)制格式的報(bào)表。
PS:除了經(jīng)典的Apache POI庫,據(jù)說Java世界還有流式生成XLS/XLSX的庫,這點(diǎn)有待確認(rèn)。
因?yàn)楦悴坏角岸巳肆Γ瑢?shí)際上還是用方案1實(shí)現(xiàn)。下面的代碼模擬了用SpringMvc實(shí)現(xiàn)異步下載報(bào)表的功能。handle7()結(jié)束后會(huì)立即返回Http頭,告訴瀏覽器將返回一個(gè)長度未知且格式未知1的二進(jìn)制文件,并推薦執(zhí)行文件下載操作。
private ExecutorService pool = Executors.newFixedThreadPool(5); @GetMapping("events7") public ResponseEntityhandle7() throws IOException { ResponseBodyEmitter emitter = new ResponseBodyEmitter(); emitter.send("start,"); pool.execute(() -> { try { Thread.sleep((long) (3 * 64 * 1000)); emitter.send("finish "); emitter.complete(); } catch (IOException | InterruptedException e) {} }); HttpHeaders headers = new HttpHeaders(); headers.set("Content-Type", "application/octet-stream;charset=UTF-8"); headers.set("Transfer-Encoding", "chunked"); headers.setContentDispositionFormData("attachment", "test.csv", Charsets.UTF_8); return new ResponseEntity<>(emitter, headers, HttpStatus.OK); }
SpringMvc的ResponseBodyEmitter實(shí)際上利用了Servlet3+的異步特性,耗時(shí)較長的請(qǐng)求無需一直占用Web請(qǐng)求處理的線程池,大大提高了服務(wù)器的并發(fā)能力。啟動(dòng)Tomcat后訪問http://localhost:8080/events7即可查看效果。
但實(shí)際上上面的代碼無法在Webkit核心下的Chrome/Safari瀏覽器上得到預(yù)期的結(jié)果。測(cè)試中Chrome無法自動(dòng)開始下載,而是會(huì)阻塞在Loading階段,直到超過了2分鐘的最大等待時(shí)間后告訴用戶發(fā)生了EmptyResponse。
在Inspector界面上不顯示Response的Http頭和部分Http體數(shù)據(jù)(即"start"字符串)。但是通過Charles抓包發(fā)現(xiàn),Response的Http頭和"start"字符串已經(jīng)發(fā)出,這是一個(gè)奇怪的地方。
幾次嘗試后發(fā)現(xiàn),問題出現(xiàn)在MIME(即Content-Type)上,Chrome對(duì)application/octet-stream類型似乎采取了接受到完整的Http包才開始下載文件的邏輯,換成application/csv后Chrome順利的開始自動(dòng)下載,下方狀態(tài)欄出現(xiàn)Loading圓圈,文案提示即將開始下載,然后文件大小開始逐漸增長,最終完成下載過程。
兩個(gè)未解之謎 1. MIME對(duì)Chrome下載行為的影響我嘗試了幾種Chrome會(huì)立刻觸發(fā)下載的MIME。
text/csv
text/css
text/markdown
text/event-stream
text/html
application/csv
application/pdf
application/json
application/xhtml+xml
application/x-www-form-urlencoded
application/atom+xml
multipart/form-data
還有一些Chrome不會(huì)自動(dòng)觸發(fā)下載并最終導(dǎo)致超時(shí)的MIME。
application/octet-stream
application/xml
text/xml
text/plain
要解釋這個(gè)問題可能需要查看Webkit源碼,但是我沒有找到相關(guān)邏輯,也有可能我找錯(cuò)了方向,希望熟悉這塊的朋友不吝賜教。
2. Nginx引起的502問題解決了上面的問題后,代碼在Beta環(huán)境出現(xiàn)了新的問題。Nginx代理提示502 Bad Gateway The proxy server received an invalid response from an upstream server。查看Nginx日志,具體的錯(cuò)誤信息如下。應(yīng)該是Transfer-Encoding設(shè)置為chunked,導(dǎo)致Nginx認(rèn)為該Http頭非法。這個(gè)問題也是令人摸不到頭腦,希望熟悉Http1.1規(guī)范和分塊傳輸編碼的朋友不吝賜教。
Reference2017/10/19 15:14:17 [error] 30016#0: *409143 upstream sent invalid chunked response while reading upstream, client: 10.72.227.11, server: www.dianping.com, request: "GET /s/ajax/shop/finance/trade/income/excel?shopIdList[]=&startDate=2017-09-20%2000:00&endDate=2017-10-19%2023:59 HTTP/1.1", upstream: "http://127.0.0.1:8080/s/ajax/shop/finance/trade/income/excel?shopIdList[]=&startDate=2017-09-20%2000:00&endDate=2017-10-19%2023:59", host: "dev.orderdish.ecom.web.meituan.com"
MIME (Multipurpose Internet Mail Extensions) Part One: Mechanisms for Specifying and Describing the Format of Internet Message Bodies
Returning Values from Forms: multipart/form-data
Multipurpose Internet Mail Extensions (MIME) Part Two: Media Types
Content-Disposition
webkit-2.18.0
Use of the Content-Disposition Header Field in the Hypertext Transfer Protocol (HTTP)
Communicating Presentation Information in Internet Messages: The Content-Disposition Header Field
Returning Values from Forms: multipart/form-data
深入剖析 WebKit
HTTP 協(xié)議中的 Transfer-Encoding
分塊傳輸編碼
Webkit學(xué)習(xí) ----網(wǎng)頁資源的構(gòu)建加載流程
WebKit內(nèi)核源代碼分析(四)
Nginx中502和504錯(cuò)誤詳解
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/39687.html
摘要:項(xiàng)目介紹本項(xiàng)目基本開發(fā)實(shí)現(xiàn),并同時(shí)使用框架來進(jìn)行開發(fā)實(shí)現(xiàn),主要實(shí)現(xiàn)一個(gè)倉庫管理系統(tǒng)。本系統(tǒng)的用戶角色分為四個(gè)角色分別為客服角色,倉庫人員,倉庫管理員,系統(tǒng)管理員,不同的用戶登陸系統(tǒng)可以進(jìn)行不同的模塊操作。 項(xiàng)目介紹: 本項(xiàng)目基本Springboot開發(fā)實(shí)現(xiàn),并同時(shí)使用Springmvc+my...
摘要:為了能夠處理中文的請(qǐng)求,再配置一個(gè),以避免請(qǐng)求中文出現(xiàn)亂碼情況至此,配置完畢。一般為一些基本的,用于進(jìn)行相應(yīng)的頁面顯示,用于處理網(wǎng)站的請(qǐng)求。現(xiàn)在,需要配置來運(yùn)行該項(xiàng)目。 摘要講解如何配置SpringMVC框架xml,以及如何在Tomcat中運(yùn)行轉(zhuǎn)載請(qǐng)注明出處:Gaussic(一個(gè)致力于AI研究卻不得不兼顧項(xiàng)目的研究生)。 注:此文承接上一文:使用IntelliJ IDEA開發(fā)Sprin...
摘要:進(jìn)階多線程開發(fā)關(guān)鍵技術(shù)后端掘金原創(chuàng)文章,轉(zhuǎn)載請(qǐng)務(wù)必將下面這段話置于文章開頭處保留超鏈接。關(guān)于中間件入門教程后端掘金前言中間件 Java 開發(fā)人員最常犯的 10 個(gè)錯(cuò)誤 - 后端 - 掘金一 、把數(shù)組轉(zhuǎn)成ArrayList 為了將數(shù)組轉(zhuǎn)換為ArrayList,開發(fā)者經(jīng)常... Java 9 中的 9 個(gè)新特性 - 后端 - 掘金Java 8 發(fā)布三年多之后,即將快到2017年7月下一個(gè)版...
摘要:多線程編程這篇文章分析了多線程的優(yōu)缺點(diǎn),如何創(chuàng)建多線程,分享了線程安全和線程通信線程池等等一些知識(shí)。 中間件技術(shù)入門教程 中間件技術(shù)入門教程,本博客介紹了 ESB、MQ、JMS 的一些知識(shí)... SpringBoot 多數(shù)據(jù)源 SpringBoot 使用主從數(shù)據(jù)源 簡(jiǎn)易的后臺(tái)管理權(quán)限設(shè)計(jì) 從零開始搭建自己權(quán)限管理框架 Docker 多步構(gòu)建更小的 Java 鏡像 Docker Jav...
閱讀 3722·2021-11-17 09:33
閱讀 2744·2021-09-22 15:12
閱讀 3351·2021-08-12 13:24
閱讀 2449·2019-08-30 11:14
閱讀 1738·2019-08-29 14:09
閱讀 1330·2019-08-26 14:01
閱讀 3066·2019-08-26 13:49
閱讀 1782·2019-08-26 12:16