java - 如何从 mongodb 检索长响应?

标签 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.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 的扩展,因此迁移应该非常容易。优点是您只在给定时间点请求结果集的一部分,这使得您的查询可扩展,但代价是手动迭代它。

进一步阅读

关于java - 如何从 mongodb 检索长响应?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26378500/

相关文章:

java - 从java中的接口(interface)实现泛型方法

java - 使用 Docker 进行 Spring 启动安全配置

mongodb - 可靠地重新连接到 MongoDB

java - 解决冲突的依赖关系 (Maven)

java - 保留循环变量值的问题

java - php 将原始输入传递给变量

java - 使用 Spring boot 的 SOAP 和 Rest Web 服务

spring - Spring Data Redis 模板中没有 maxlen 选项来限制流大小

php - Windows 上的 MongoDB PHP 驱动程序 fatal error

javascript - 在 mongodb 运算符中使用 javascript 数组