如何从mongodb中检索长响应?

时间:2014-10-15 09:01:30

标签: java spring mongodb spring-mvc spring-data-mongodb

在Spring mvc + mongodb应用程序中,我有400k文件。如果我在查询时需要返回300k文件,我该怎么办呢?

以下是堆栈跟踪,

HTTP Status 500 - Request processing failed; nested exception is java.lang.IllegalArgumentException: response too long: 1634887426

type Exception report

message Request processing failed; nested exception is java.lang.IllegalArgumentException: response too long: 1634887426

description The server encountered an internal error that prevented it from fulfilling this request.

exception

org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.IllegalArgumentException: response too long: 1634887426
    org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:973)
    org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:863)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:646)
    org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:837)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:727)
root cause

java.lang.IllegalArgumentException: response too long: 1634887426
    com.mongodb.Response.<init>(Response.java:49)
    com.mongodb.DBPort$1.execute(DBPort.java:141)
    com.mongodb.DBPort$1.execute(DBPort.java:135)
    com.mongodb.DBPort.doOperation(DBPort.java:164)
    com.mongodb.DBPort.call(DBPort.java:135)
    com.mongodb.DBTCPConnector.innerCall(DBTCPConnector.java:292)
    com.mongodb.DBTCPConnector.call(DBTCPConnector.java:271)
    com.mongodb.DBCollectionImpl.find(DBCollectionImpl.java:84)
    com.mongodb.DBCollectionImpl.find(DBCollectionImpl.java:66)
    com.mongodb.DBCursor._check(DBCursor.java:458)
    com.mongodb.DBCursor._hasNext(DBCursor.java:546)
    com.mongodb.DBCursor.hasNext(DBCursor.java:571)
    org.springframework.data.mongodb.core.MongoTemplate.executeFindMultiInternal(MongoTemplate.java:1803)
    org.springframework.data.mongodb.core.MongoTemplate.doFind(MongoTemplate.java:1628)
    org.springframework.data.mongodb.core.MongoTemplate.doFind(MongoTemplate.java:1611)
    org.springframework.data.mongodb.core.MongoTemplate.find(MongoTemplate.java:535)
    com.AnnaUnivResults.www.service.ResultService.getStudentList(ResultService.java:38)
    com.AnnaUnivResults.www.service.ResultService$$FastClassBySpringCGLIB$$1f19973d.invoke(<generated>)
    org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
    org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:711)
    org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
    org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:136)
    org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:644)
    com.AnnaUnivResults.www.service.ResultService$$EnhancerBySpringCGLIB$$f9296292.getStudentList(<generated>)
    com.AnnaUnivResults.www.controller.ResultController.searchStudentByCollOrDept(ResultController.java:87)
    sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    java.lang.reflect.Method.invoke(Method.java:597)
    org.springframework.web.method.support.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:215)
    org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:132)
    org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:104)
    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:749)
    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:690)
    org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:83)
    org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:945)
    org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:876)
    org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:961)
    org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:863)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:646)
    org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:837)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:727)

我猜上面的堆栈跟踪是因为返回的文件非常大。我该怎么办呢?我将tomcat服务器配置更改为4096M。但我还是有问题。

1 个答案:

答案 0 :(得分:2)

首先是数学:你试图在一个查询中加载超过1.5GB。这将需要一段时间,除了非常罕见的用例显示 - 抱歉 - 糟糕的应用程序设计。

不要想到如何从数据库方面处理它。你应该重构你的代码。

加载大量文档有两种常见方案。

场景1:对结果集的计算

有时您希望对结果集的大部分进行计算。假设您想知道到目前为止EMEA生成的所有客户的营业额和您的订单文档是这样的(为此示例而简化):

{
 _id:<...>,
 customerId:<...>,
 deliveryAdress: {<...>},
 region: "EMEA",
 items:[{<...>},{<...>},...],
 total:12345.78
}

现在,您可以在一定程度上做的是从EMEA区域加载相当于

的所有订单
db.orders.find({region:"EMEA"})
// the repository method would be something like
// findByRegion(String region)

并迭代结果集,构建total之和。这种方法有几个问题。首先,即使以这种方式执行此操作,您也需要加载很多您不需要的数据(itemsdeliveryAddress)。因此,减少MongoDB返回的数据量的第一种方法是使用投影:

db.orders.find({region:"EMEA"},{_id:0,total:1})
// as of now, you would have to create a custom method
// and a custom repository implementation
// See "Further Reading"

它将为您提供大量文档,其中只包含来自EMEA的所有订单的总和,大大减少了从数据库返回的大小。据我所知,这可以通过自动使用spring-data的动态查找器(存储库)来完成。

但是这种方法仍然有缺点,它不能很好地扩展,因为可能有一个时间点你从EMEA获得的订单多于你在单个交易中加载的订单。您可以使用服务器端游标和迭代器(详见方案2),但这仍然有点尴尬。

更好的方法是让MongoDB进行计算。为此,您将使用MongoDB的聚合框架。至于示例,查询看起来像

db.orders.aggregate([{$match:{region:"EMEA"}},{$group:{_id:"$region",totalTurnover:{$sum:"$total"} } })

将返回单个文档,如

{_id:"EMEA",totalTurnover:<very large Sum>}

优势显而易见:您保持应用程序的负载,不需要加载所有数据,从而大大提高性能。它具有可扩展性。

场景2:您真的需要大量文档

即使您确实需要大量文档,将它们全部加载到一个巨大的结果集中也是不好的做法,因为您发现这种方法不具备可扩展性。更好的方法是请求结果集的部分。为此,您使用服务器端游标。

使用spring-data-mongodb,你可以使用PagingAndSortingRepository而不是CrudRepository或其他任何东西。由于PagingAndSortingRepository是CrudRepository的扩展,因此迁移应该非常简单。优点是您只在给定时间点请求结果集的一部分,这使您的查询可以以手动迭代它为代价进行扩展。

进一步阅读