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

【前提】

      js通过ajax提交数据到后台,一旦数据量大了,就会出现问题,比如:

       

      

【分析】

      对于这个问题,目前场景中,数据的传输量在5M到20M之间,先通过js拼接成一个大的json串,之后在通过ajax发送post请求到达后端。

      局部代码展示:

      1.js

function 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数据拼接太过耗时间和浏览器性能,导致浏览器直接超时甚至卡死。

扫描二维码关注公众号,回复: 145649 查看本文章

      验证思路:断点跟前端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数据太慢,占内存的解决方案。







猜你喜欢

转载自blog.csdn.net/u013047584/article/details/79404852