Ajax + Spring MVC上傳文件失敗的問題的排查解決方案

最近在作一個文件上傳需求,發現當上傳文件大於60M時,前端ajax文件上傳後,服務請求不到服務端。前端

排查過程:     java

第一步,頁面請求返回NS_BINDING_ABORTED,先查了下Nginx日誌,Nginx 400 Bad Request 猜想是由於客戶端Post請求Packet在網絡傳輸過程當中部分丟失致使到服務端沒法正常響應,客戶端10s超時斷開鏈接,這時候nginx記錄了400。這種狀況下,nginx實際未反饋400的response,只是在鏈接斷開時記錄了400的日誌。nginx

因而排查前端代碼以下:前端在上傳文件請求後,設置10S的請求超時時間,初步猜想是上傳文件較大,前端請求包尚未發送完畢就中斷請求了,因而把timeout時間設置加大,增長到20秒,發現60多M的文件上傳成功。web

 1      $.ajax({
 2                     url: '/upload_url',
 3                     type: 'post',
 4                     data: data,
 5                     timeout: 10000, // 超時
 6                     cache: false,
 7                     processData: false,
 8                     contentType: false,
 9                     success: function(json) {
10                       .......
11                         }
12                     },
13                     error: function() {
14                       
15                     },
16                     complete: function(XMLHttpRequest, status) {
17                         if (status === 'timeout') {
18                             // 超時直接提示
19                            //   '上傳時間較長,請稍後查看結果',           
20                             });
21                         }
22                     }
23                 })

 

 

第二步,測試上傳文件大於100M時的狀況,發現服務端不會接收到請求,加大了超時時間也沒有任何效果,排查Nginx配置上傳文件大小限制是300M,即 client_max_body_size 300m。ajax

而且發現Nginx以下日誌:spring

。。。。。。。。。。。a client request body is buffered to a temporary file /dev/。。。。。。。/client_body/XXXXXXX, clientapache

。。。。。。。。。。。pwrite() "/dev/。。。。。。。/client_body/XXXXXXX" failed (28: No space left on device), clientjson

 因此,優化nginx配置,client_max_body_size 300m;   client_body_buffer_size 300m; 加大上傳緩存Buffer size文件,reload nginx生效。後端

 ps :    瀏覽器

     client_max_body_size
    此指令設置NGINX能處理的最大請求主體大小。 若是請求大於指定的大小,則NGINX發回HTTP 413(Request Entity too large)錯誤。 若是服務器處理大文件上傳,則該指令很是重要。
     client_body_buffer_size
   
此指令設置用於請求主體的緩衝區大小。 若是主體超過緩衝區大小,則完整主體或其一部分將寫入臨時文件。 若是NGINX配置爲使用文件而不是內存緩衝區,則該指令會被忽略。 默認狀況下,該指令爲32位系統設置一個8k緩衝區,爲64位系統設置一個16k緩衝區。 該指令在NGINX配置的http,server和location區塊使用。

 

第三步,繼續測試100M以上的文件,仍然不成功,瀏覽器前端請求結果返回 net::ERR_INCOMPLETE_CHUNKED_ENCODING,因而排查nginx日誌以下:

  。。。。。。。。。    錯誤日誌所有是104: Connection reset by peer) while reading upstream

  顯然,因爲upstream重置鏈接了,就是說後端主動斷開了鏈接,而後發現鏈接裏有不少TIME-WAIT,一直 等待遠程TCP接收到鏈接中斷請求的確認。因而分析nginx配置,nginx做爲反向代理既然是客戶端又是服務端,當和後端服務創建鏈接時並無默認開啓長鏈接,開啓長鏈接後性能應該會提高不少,因而優化nginx配置開啓nginx長連接【說明:(從client到nginx的鏈接是長鏈接,對於客戶端來講,nginx長鏈接是默認開啓的。從nginx到server的鏈接是長鏈接,須要本身開啓】。
配置以下:
           proxy_connect_timeout      120;   
           proxy_send_timeout         200; 
           proxy_read_timeout         300;  
           proxy_http_version 1.1;    ##開啓後端,長鏈接
           proxy_set_header Connection "";  ##開啓後端,長鏈接
 
nginx reload 生效。
 
第四步,   從新上傳大於100M的文件,此次上面的nginx報錯問題解決,但仍然沒有成功,由於上一步,已經能夠確認上傳文件請求已經發送到服務端,因而排查服務端日誌,發現異常日誌以下:
org.springframework.web.multipart.MaxUploadSizeExceededException: Maximum upload size of 104857600 bytes exceeded; nested exception is org.apache.commons.fileupload.FileUploadBase$SizeLimitExceededException: the request was rejected because its size (240330252) exceeds the configured maximum (104857600)
    org.springframework.web.multipart.commons.CommonsMultipartResolver.parseRequest(CommonsMultipartResolver.java:162)
    org.springframework.web.multipart.commons.CommonsMultipartResolver.resolveMultipart(CommonsMultipartResolver.java:142)
    org.springframework.web.servlet.DispatcherServlet.checkMultipart(DispatcherServlet.java:1099)
    org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:932)
    org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:897)
    org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
    org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:872)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:660)
    org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
 
經過上面的報錯日誌,定位到 CommonsMultipartResolvermaxUploadSize配置設置成100M大小,因而加大此配置,以下參考。
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="314572800" /> <!--300M-->
<property name="maxInMemorySize" value="314572800" /> <!--300M 設置multipart請求所容許的最大大小,默認不限制 -->
<property name="defaultEncoding" value="UTF-8"></property> <!-- 設置一個大小,multipart請求小於這個大小時會存到內存中,大於這個內存會存到硬盤中 -->
</bean>
修改服務端文件大小校檢,從新編輯發佈服務端服務,問題解決。經測試300M之內文件上傳沒有問題。
固然,若是有業務需求上傳文件的大小更大的,須要同時修改nginx配置及 maxUploadSize配置,而後會報以下錯誤:
。。。。 682 client intended to send too large body:。。。。。。。。。
 
至此,問題得以解決,真相大白於天下,因爲是解決問題以後理解的,可能會有些遺漏,及忽略掉一些本人以爲不必的其它的排查過程及說明,敬請見諒。
 
 
備註:

一、Nginx 400 Bad Request

  通常致使400異常的場景:

(1)請求頭過大
(2)空請求
(3)URLConnection發起HTTPS請求通過代理400異常
(4)網絡傳輸丟包致使的400異常

 

二、Nginx 499 / ClientClosed Request

     upstream在如下幾種狀況下會返回499:

  (1)upstream 在收到讀寫事件處理以前時,會檢查鏈接是否可用:
  ngx_http_upstream_check_broken_connection,
    if (c->error) { //connecttion錯誤      …… if (!u->cacheable) { //upstream的cacheable爲false,這個值跟http_cache模塊的設置有關。指示內容是否緩存。 ngx_http_upstream_finalize_request(r, u, NGX_HTTP_CLIENT_CLOSED_REQUEST); }   }

  如上代碼,當鏈接錯誤時會返回499。

  (2)server處理請求未結束,而client提早關閉了鏈接,此時也會返回499。

  (3)在一個upstream出錯,執行next_upstream時也會判斷鏈接是否可用,不可用則返回499。

總之,這個錯誤的比例升高可能代表服務器upstream處理過慢,致使用戶提早關閉鏈接。而正常狀況下有一個小比例是正常的。

相關文章
相關標籤/搜索