【发布时间】:2016-11-09 18:33:41
【问题描述】:
基础设施和序言
我有一个托管在 AWS EC2 实例上的 PlayFramework (2.3.8) 应用程序。我有一个复杂对象数组,应该通过 Web API 作为 JSON 字符串返回。我需要一个数组的深层副本,所有子对象都完全加载到最后一层。该数组的大小为 30-100 个条目,每个条目大约有 1-10 个条目,每个条目最多有 100 个属性,最后没有涉及 BLOB 或类似内容,这一切都归结为字符串/双精度/整数/布尔值。我不确定确切的数据结构有多重要,如果您需要更多详细信息,请告诉我。生成的 .json 文件大小约为 1 MB。
反序列化这个数组的性能很糟糕,对于我本地机器上的 ~1 MB,它需要 3-5 分钟;在 EC2 上大约需要 20-30 秒。
最初的问题:使用play.libs json时性能不佳
我的对象数组被加载并存储为 JsonNode。然后这个 JsonNode 被转发给一个 ObjectMapper,它最终把它写成 prettyPrinted:
List<myObject> myObjects = myObjectService.getInstance().getAllObjects(); // simplified example
JsonNode myJsonNode = Json.toJson(myObjects); // this line of code takes a huge amount of time!
ObjectMapper om = new ObjectMapper();
return om.writerWithDefaultPrettyPrinter().writeValueAsString(myJsonNode); // this runs in <10 ms
所以我确定罪魁祸首是 Json.toJson 反序列化。据我所知,它是 PlayFramework 使用的一种包装好的 Jackson 库。
虽然我已经阅读了一些关于 JSON 反序列化的性能问题,但我不确定我们是否应该谈论几百毫秒到几秒,而不是几分钟。无论如何,我尝试实现了一些其他的 JSON 库(GSON、argonaut、flexjson),但进展并不顺利。
GSON
我“只是”尝试用 GSON 库替换 play-json 库,就像我在项目的另一小部分所做的那样。它在那里工作得很好,但即使我没有循环引用,它也会在我面前抛出 StackOverflowErrors,即使我尝试反序列化一个手动创建的微小对象。在我的开发机器和 EC2 实例上。
FlexJson
List<myObject> myObjects = myObjectService.getInstance().getAllObjects(); // simplified example
JSONSerializer serializer = new JSONSerializer().prettyPrint(true);
return serializer.deepSerialize(myObjects); // returns a prettyPrinted String
到目前为止工作得很好,与上面的 Json.toJson 方法相比,它只需要大约 20% 的时间。然而,这可能是因为它并没有真正深度复制对象。它确实在第一层对其进行了深度复制,但是由于我的模型具有一些更复杂的属性(具有子、孙和孙...),而且其中有很多,我不确定如何在这里进行。
这是我的一个嵌套对象的示例输出(这是“上层”对象的属性之一):
"class": "com.avaje.ebean.common.BeanList",
"empty": false,
"filterMany": null,
"finishedFetch": true,
"loaderIndex": 0,
"modifyAdditions": null,
"modifyListenMode": "NONE",
"modifyRemovals": null,
"populated": true,
"propertyName": "elements",
"readOnly": false,
"reference": false
您是否有任何其他解决方案建议,或提示可能出现的问题?我也在考虑,也许实体只有在我调用 .toJson() 后才完全加载?仍然不应该花费这么多时间。
提前致谢!
【问题讨论】:
-
有可能“实体只有在我调用
.toJson()时才完全加载”。如我所见,您正在使用 Ebean。尝试activate logging for sql statements 以查看对象图是否在toJson调用之前或期间加载。 -
您也可以尝试使用 VisualVM 或 YourKit 之类的工具来找出导致它变慢的原因,或者保存生成的 json 文件。将它加载到内存(解析它),然后用
toJson渲染它(如果这个过程比以前更快,那么很明显,除了 json 解析/生成之外的其他东西让它变慢了) -
感谢有关 sql 日志记录的提示,好主意。事实证明,实体及其子实体仅在 Json.toJson() 期间加载。我尝试为那些子实体更改 fetch = FetchType.Eager ,但这根本没有区别。基本上我真的看不出它会产生什么不同,在什么时候加载实体,所以为什么不在 .toJson() 的输出期间。但似乎瓶颈在于实体/数据库,而不是 JSON 序列化..?
标签: java json playframework