亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

【SpringMvc】后臺系統(tǒng)超大報表下載超時的處理

kohoh_ / 2809人閱讀

摘要:四層負載均衡不會引起超時。動態(tài)修改包的目標地址,并轉發(fā)數(shù)據(jù)包使其到達不同的機器上來實現(xiàn)負載均衡的目的,因此節(jié)點不會引起超時。七層負載均衡等待上游響應超時。例如使用多線程并發(fā)減少遠程查詢的總體時間如需數(shù)據(jù)有序,可以使用方案。

B端業(yè)務經(jīng)常要提供下載報表的功能,一般的方法是先查詢出所有數(shù)據(jù),然后在內存中組裝成報表(如XLS/XLSX格式)后統(tǒng)一輸出。但是如果生成報表需要查詢的數(shù)據(jù)量很大,遠程服務的調用時間之和遠遠超過了鏈路上某節(jié)點(比如代理服務器Nginx、瀏覽器Chrome)的等待時間,因此該次Http連接就會被強制關閉,導致下載失敗。

下面的示例代碼調用了Thread.sleep,將處理線程掛起3分鐘,模擬耗時的數(shù)據(jù)查詢操作。

@GetMapping("/trade/income/excel")
public HttpEntity 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超時會主動斷開連接
    stream.write("finish".getBytes(Charsets.UTF_8));
}
常見超時原因和優(yōu)化思路

大型的Web應用一般都不是單純的Client/Server模型。一次Http請求會在網(wǎng)絡鏈路上經(jīng)過多于2個的節(jié)點。

Chrome(用戶端的瀏覽器) <=> 四層負載均衡(工作在傳輸層,如LVS和MGW) <=> 七層負載均衡(工作在應用層,如反向代理用的Nginx) <=> Tomcat(后端應用服務器)

鏈路上的每個節(jié)點都有可能會產生超時,因此具體的超時原因也可以分為:

Chrome發(fā)起請求后,等待響應超時。該值為120秒,且用戶不可更改,超時后頁面上會提示EmptyResponse

四層負載均衡不會引起超時。LVS動態(tài)修改TCP包的目標IP地址,并轉發(fā)數(shù)據(jù)包使其到達不同的機器上來實現(xiàn)負載均衡的目的,因此LVS節(jié)點不會引起超時。個人理解,不一定準確。

七層負載均衡等待上游響應超時。Nginx代理了客戶端瀏覽器對后端服務器的Http請求,作為反向代理服務器需要“同時”維護與瀏覽器和后端服務器的Http連接,因此也會產生相應的超時,例如Nginx等待上游響應超時就會產生504 Gateway Timeout

Tomcat/Servlet處理超時。這層對應本地環(huán)境產生的超時,如Socket超時、InputStream/OutputStream超時。

對應的超時優(yōu)化有3種思路。

1. 縮短后端查詢數(shù)據(jù)的時間。

例如使用多線程并發(fā)減少遠程查詢的總體時間(如需數(shù)據(jù)有序,可以使用Fork/Join方案)。

該方案的優(yōu)點是減少了對外的整體查詢的時間。缺點是多線程增加了開發(fā)和維護的難度;高并發(fā)壓力轉移到內部的查詢服務上,對其QPS響應提出了更高的要求。

2. 將數(shù)據(jù)查詢和下載的流程異步化。

瀏覽器請求下載后,服務端立即返回報表的唯一標識Key同時開始遠程查詢數(shù)據(jù),客戶端可以憑借該Key查詢報表的生成進度,報表完成后就可以下載;或者使用另一種方案,服務器在報表生成完成后通過一些渠道(如Long-PollingWebSocket、即時通信軟件、郵件等)通知客戶端下載。

該方案的優(yōu)點是并發(fā)能力強,不會阻塞服務器的Web連接池。缺點是需要開發(fā)Key的CRUD操作和相應的UI;需要公有文件云的支持用于存儲生成的報表文件。

3. 服務端邊生成報表,瀏覽器邊下載報表。

就像下載大文件一樣,瀏覽器不斷開和服務器的Http連接,同時服務器不斷向瀏覽器追加Http體數(shù)據(jù)直到報表生成結束。

該方案的優(yōu)點是開發(fā)難度低、速度快。缺點是數(shù)據(jù)查詢是單線程的,速度較慢;而且文件下載會一直占用服務器的Web連接池,如果并發(fā)下載量較大可能會阻塞其他的Http請求。

因為在實際的業(yè)務開發(fā)中,前2種思路做的比較多,所以后文不再贅述。

方案3的具體實現(xiàn)

該方案的關鍵在于業(yè)務方法返回后SpringMvc/Servlet不能主動關閉Http連接,而是要像平常下載文件一樣保持Http的長連接(注意Http長連接要和Http 1.1協(xié)議默認采用的Tcp長連接相區(qū)分),唯一不同的是這次瀏覽器無法提前知道文件的大小。因此對于技術方案我考慮有幾種選擇:

服務器邊查詢數(shù)據(jù)并生成,瀏覽器邊下載,像平常下載文件一樣。

分多次查詢/推送數(shù)據(jù),瀏覽器最后把數(shù)據(jù)組裝為報表。

輪詢(Polling)??蛻舳溯喸兎掌鳎看尾樵儓蟊頂?shù)據(jù)的一部分,查詢結束后再組裝成報表文件。

長輪詢(Long-Polling)??蛻舳溯喸兎掌?,服務器在收到請求后Hold住Http連接,等待另一部分的數(shù)據(jù)查詢完成才釋放連接并返回Response。

WebSocket。支持Html5特性的瀏覽器和服務器之間建立Socket管道,可以雙向傳遞任意類型的消息。

第一種方案的優(yōu)點是不需要前端參與開發(fā),缺點是無法支持二進制格式的報表文件(如XLS/XLSX),只能用文本格式(如CSV/TSV),這會帶來格式的損失,比如CSV格式里位數(shù)超過10位的數(shù)字會被Excel自動顯示成科學記數(shù)法。第二種方案正好相反,需要前端開發(fā)人力,但是可以支持組裝二進制格式的報表。

PS:除了經(jīng)典的Apache POI庫,據(jù)說Java世界還有流式生成XLS/XLSX的庫,這點有待確認。

因為搞不到前端人力,實際上還是用方案1實現(xiàn)。下面的代碼模擬了用SpringMvc實現(xiàn)異步下載報表的功能。handle7()結束后會立即返回Http頭,告訴瀏覽器將返回一個長度未知且格式未知1的二進制文件,并推薦執(zhí)行文件下載操作。

private ExecutorService pool = Executors.newFixedThreadPool(5);
@GetMapping("events7")
public ResponseEntity handle7() 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);
}

SpringMvcResponseBodyEmitter實際上利用了Servlet3+的異步特性,耗時較長的請求無需一直占用Web請求處理的線程池,大大提高了服務器的并發(fā)能力。啟動Tomcat后訪問http://localhost:8080/events7即可查看效果。

但實際上上面的代碼無法在Webkit核心下的Chrome/Safari瀏覽器上得到預期的結果。測試中Chrome無法自動開始下載,而是會阻塞在Loading階段,直到超過了2分鐘的最大等待時間后告訴用戶發(fā)生了EmptyResponse

在Inspector界面上不顯示Response的Http頭和部分Http體數(shù)據(jù)(即"start"字符串)。但是通過Charles抓包發(fā)現(xiàn),Response的Http頭和"start"字符串已經(jīng)發(fā)出,這是一個奇怪的地方。

幾次嘗試后發(fā)現(xiàn),問題出現(xiàn)在MIME(即Content-Type)上,Chrome對application/octet-stream類型似乎采取了接受到完整的Http包才開始下載文件的邏輯,換成application/csv后Chrome順利的開始自動下載,下方狀態(tài)欄出現(xiàn)Loading圓圈,文案提示即將開始下載,然后文件大小開始逐漸增長,最終完成下載過程。

兩個未解之謎 1. MIME對Chrome下載行為的影響

我嘗試了幾種Chrome會立刻觸發(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不會自動觸發(fā)下載并最終導致超時的MIME。

application/octet-stream

application/xml

text/xml

text/plain

要解釋這個問題可能需要查看Webkit源碼,但是我沒有找到相關邏輯,也有可能我找錯了方向,希望熟悉這塊的朋友不吝賜教。

2. Nginx引起的502問題

解決了上面的問題后,代碼在Beta環(huán)境出現(xiàn)了新的問題。Nginx代理提示502 Bad Gateway The proxy server received an invalid response from an upstream server。查看Nginx日志,具體的錯誤信息如下。應該是Transfer-Encoding設置為chunked,導致Nginx認為該Http頭非法。這個問題也是令人摸不到頭腦,希望熟悉Http1.1規(guī)范分塊傳輸編碼的朋友不吝賜教。

2017/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"

Reference

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學習 ----網(wǎng)頁資源的構建加載流程

WebKit內核源代碼分析(四)

Nginx中502和504錯誤詳解


  • RFC1521規(guī)定application/octet-stream代表未知格式的二進制流。 ?

  • 文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

    轉載請注明本文地址:http://www.ezyhdfw.cn/yun/39687.html

    相關文章

    • 用剛學Springboot去實現(xiàn)一個完整倉庫管理系統(tǒng)

      摘要:項目介紹本項目基本開發(fā)實現(xiàn),并同時使用框架來進行開發(fā)實現(xiàn),主要實現(xiàn)一個倉庫管理系統(tǒng)。本系統(tǒng)的用戶角色分為四個角色分別為客服角色,倉庫人員,倉庫管理員,系統(tǒng)管理員,不同的用戶登陸系統(tǒng)可以進行不同的模塊操作。 項目介紹: 本項目基本Springboot開發(fā)實現(xiàn),并同時使用Springmvc+my...

      Vixb 評論0 收藏0
    • [轉載]使用IntelliJ IDEA開發(fā)SpringMVC網(wǎng)站(二)框架配置

      摘要:為了能夠處理中文的請求,再配置一個,以避免請求中文出現(xiàn)亂碼情況至此,配置完畢。一般為一些基本的,用于進行相應的頁面顯示,用于處理網(wǎng)站的請求?,F(xiàn)在,需要配置來運行該項目。 摘要講解如何配置SpringMVC框架xml,以及如何在Tomcat中運行轉載請注明出處:Gaussic(一個致力于AI研究卻不得不兼顧項目的研究生)。 注:此文承接上一文:使用IntelliJ IDEA開發(fā)Sprin...

      baukh789 評論0 收藏0
    • java篇 - 收藏集 - 掘金

      摘要:進階多線程開發(fā)關鍵技術后端掘金原創(chuàng)文章,轉載請務必將下面這段話置于文章開頭處保留超鏈接。關于中間件入門教程后端掘金前言中間件 Java 開發(fā)人員最常犯的 10 個錯誤 - 后端 - 掘金一 、把數(shù)組轉成ArrayList 為了將數(shù)組轉換為ArrayList,開發(fā)者經(jīng)常... Java 9 中的 9 個新特性 - 后端 - 掘金Java 8 發(fā)布三年多之后,即將快到2017年7月下一個版...

      OpenDigg 評論0 收藏0
    • java篇

      摘要:多線程編程這篇文章分析了多線程的優(yōu)缺點,如何創(chuàng)建多線程,分享了線程安全和線程通信線程池等等一些知識。 中間件技術入門教程 中間件技術入門教程,本博客介紹了 ESB、MQ、JMS 的一些知識... SpringBoot 多數(shù)據(jù)源 SpringBoot 使用主從數(shù)據(jù)源 簡易的后臺管理權限設計 從零開始搭建自己權限管理框架 Docker 多步構建更小的 Java 鏡像 Docker Jav...

      honhon 評論0 收藏0
    • Java后端

      摘要:,面向切面編程,中最主要的是用于事務方面的使用。目標達成后還會有去構建微服務,希望大家多多支持。原文地址手把手教程優(yōu)雅的應用四手把手實現(xiàn)后端搭建第四期 SpringMVC 干貨系列:從零搭建 SpringMVC+mybatis(四):Spring 兩大核心之 AOP 學習 | 掘金技術征文 原本地址:SpringMVC 干貨系列:從零搭建 SpringMVC+mybatis(四):Sp...

      joyvw 評論0 收藏0

    發(fā)表評論

    0條評論

    最新活動
    閱讀需要支付1元查看
    <