您的位置:首页 > 编程语言 > Java开发

【工作记录】SpringMVC下js提交大数据量到controller失败解决

2018-02-28 21:15 218 查看
【前提】
      js通过ajax提交数据到后台,一旦数据量大了,就会出现问题,比如:
       


      
【分析】
      对于这个问题,目前场景中,数据的传输量在5M到20M之间,先通过js拼接成一个大的json串,之后在通过ajax发送post请求到达后端。
      局部代码展示:
      1.jsfunction submitForm() {
layer.load(1, {shade: [0.3]});
var stringJson=JSON.stringify(paramsBuilder()); //将数据放到json中
var formData = new FormData();
formData.append("params",stringJson);
formData.append("type",2);
$.ajax({
url: CAR_PATH + "/vincent/test_1.do",
type: "POST",
data:formData,
// 告诉jQuery不要去处理发送的数据
processData : false,
// 告诉jQuery不要去设置Content-Type请求头
contentType : false,
success: function (result) {
layer.closeAll();
if (result && result.success) {
closeWin();
window.opener.location.reload();
} else {
alert(result.messages[0]);
}
},
error: function (result) {
layer.closeAll();
alert("系统异常,请稍后重试!");
}
});
}      2.controller@RequestMapping(value = "/test_1", method = RequestMethod.POST)
@ResponseBody
public Message editProductConfig(HttpServletRequest request,String params,Integer type) {
LOGGER.error("上传文件限制的大小: "+ multipartResolver.getFileUpload().getSizeMax());
LOGGER.error("入参打印到控制台: " + params);
final Object logsId = LogUtils.initLogsId();
final String desc = "";
Message message = new Message();
try {
if (StringUtils.isBlank(params)) {
message.setSuccess(false);
message.getMessages().add("非法调用!");
return message;
}
ProductConfigForm form = fromJson(params, ProductConfigForm.class); //将入参从json反解析成实体
//and so on ~逻辑
}
}      问题可能出现在两个方向:
      一、json数据拼接太过耗时间和浏览器性能,导致浏览器直接超时甚至卡死。
      验证思路:断点跟前端js代码,看是否浏览器的内存卡死是死在多个for循环的拼接过程。

      二、json数据量太大,ajax不能将这么大的数据传输到后台
      验证思路:后台controller出打断点,通过Log输出param入参。
      解决思路:如果是这个原因导致,可以从如下几个角度来思考解决1)pako_deflate.js 压缩请求内容(注意点:什么压缩 什么时候不要缩)
2)数据拆分 多次请求
3)拼装数据 后移
4)稀疏矩阵
5)流传输
6)验证是否运维方面的配置导致      
【验证】
  1. js拼接json导致浏览器卡死
     这一步确实有问题,解决方案请参考我的下一篇博客:(后续放入链接)

  2. 数据传输大小限制(看到这里,有个前提,js拼接过程出的问题已经解决)
  (1)在controller设置断点,发现param的值为null,此时的数据量在1M~2M之间。
     很明显,post请求并没有将入参传到controller中,此时需要查看tomcat/conf/server.xml下的配置:<Connector Executor="tomcatThreadPool" URIEncoding="UTF-8" acceptCount="800" compressableMimeType="text/html,text/xml,text/javascript,text/css,text/plain,application/msexcel" compression="on" compressionMinSize="2048" connectionTimeout="60000" disableUploadTimeout="true" enableLookups="false" maxHttpHeaderSize="8192" maxSpareThreads="75" maxThreads="800" minSpareThreads="25" port="80" protocol="HTTP/1.1" redirectPort="8443" maxPostSize="31457280"/>     将其中的maxPostSize值设置为可能传输到controller的最大数据量,这里我设置了30Mb(31457280)。
     查资料,采取了通过FormData()的方式包装数据到controller:https://segmentfault.com/a/1190000012327982  (因为我们将一个大的Form表单在js中拆分了多个Form表单,具体参考我的下一篇博客(后续放入链接))。
      之后发现,数据量小于5M的情况下,可以将json数据传输到controller当中。

   (2)当数据量继续增加的时候,新的问题出现了,如下:[ERROR]: 系统异常:
org.springframework.web.multipart.MaxUploadSizeExceededException: Maximum upload size of 5242880 bytes exceeded; nested exception is org.apache.commons.fileupload.FileUploadBase$SizeLimitExceededException: the request was rejected because its size (10423705) exceeds the configured maximum (5242880)       这个错误是SpringMVC报出来的,时间点在进入Controller之前,因为我打的logger查看param入参都没有执行。之后跟了源码,有几个类值得注意:
      1.FileUploadBase.java
      SpringMVC控制文件传输到controller数据量的set方法,此类为基类,在他下面有一些子类丰富该功能。public void setSizeMax(long sizeMax) {
this.sizeMax = sizeMax; //设置sizeMax的值,Spring允许传输到后台的最大数据量
}      抛出我遇到异常的方法:在这里比较了允许的最大值,和实际的数据量FileItemIteratorImpl(RequestContext ctx)
throws FileUploadException, IOException {
if (ctx == null) {
throw new NullPointerException("ctx parameter");
}

String contentType = ctx.getContentType();
if ((null == contentType)
|| (!contentType.toLowerCase().startsWith(MULTIPART))) {
throw new InvalidContentTypeException(
"the request doesn't contain a "
+ MULTIPART_FORM_DATA
+ " or "
+ MULTIPART_MIXED
+ " stream, content type header is "
+ contentType);
}

InputStream input = ctx.getInputStream();

if (sizeMax >= 0) {
int requestSize = ctx.getContentLength();
if (requestSize == -1) {
input = new LimitedInputStream(input, sizeMax) {
protected void raiseError(long pSizeMax, long pCount)
throws IOException {
FileUploadException ex =
new SizeLimitExceededException(
"the request was rejected because"
+ " its size (" + pCount
+ ") exceeds the configured maximum"
+ " (" + pSizeMax + ")",
pCount, pSizeMax);
throw new FileUploadIOException(ex);
}
};
} else {
if (sizeMax >= 0 && requestSize > sizeMax) {
throw new SizeLimitExceededException(
"the request was rejected because its size ("
+ requestSize
+ ") exceeds the configured maximum ("
+ sizeMax + ")",
requestSize, sizeMax);
}
}
}
}
      2.CommonsFileUploadSupport.java
       对第1个类的适配类,参考方法:public void setMaxUploadSize(long maxUploadSize) {
this.fileUpload.setSizeMax(maxUploadSize);
}
      3.CommonsMultipartResolver.java
      元素为多个时候,对servlet进行请求和解析。protected MultipartParsingResult parseRequest(HttpServletRequest request) throws MultipartException {
String encoding = determineEncoding(request);
FileUpload fileUpload = prepareFileUpload(encoding);
try {
List<FileItem> fileItems = ((ServletFileUpload) fileUpload).parseRequest(request);
return parseFileItems(fileItems, encoding);
}
catch (FileUploadBase.SizeLimitExceededException ex) {
throw new MaxUploadSizeExceededException(fileUpload.getSizeMax(), ex);
}
catch (FileUploadException ex) {
throw new MultipartException("Could not parse multipart servlet request", ex);
}
}      主要在上述3个方法中设置断点,通过比较发现每次被限制的大小为5242880(5M),之后在web.xml中发现了它被spring初始化过程扫描,于是重新写一个xml,在<servlet>的<param-value>中覆盖之前的值:classpath*:maxuploadsizeContext.xml
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="52428800"/>
<property name="defaultEncoding" value="UTF-8"/>
</bean>
       设置maxUploadSize值大小为50Mb,再次启动,问题解决。

     (3)本地测试没有问题之后,部署预生产环境,发现问题重现,考虑出现原因,client端发送请求到预生产tomcat服务器,期间经过nginx代理服务器,可能在这里被做了拦截,经过排查,发现:



      nginx报错:upstream time out(110: Connection timed out) while reading response header from upstream.
      很明显,nginx响应超时了,说明nginx代理对json数据也做了处理,另外需要排查nginx是否有限制数据量大小,结果发现而且都做了限制,修改如下图所示:
  


      重置在代理服务器上的响应时间,以及nginx允许的最大传输量之后,该问题解决。

      That's all,期待下一篇分享js拼接json数据太慢,占内存的解决方案。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: