【问题标题】:PlayFramework: poor json deserializing performancePlayFramework:糟糕的 json 反序列化性能
【发布时间】: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


【解决方案1】:

TLDR:此问题与 PlayFrameworks JSON 反序列化性能无关,与某些 eBean / 数据库问题无关。在 application.conf 中启用 SQL 日志记录向我指出了这一点。


进一步的评论和想法: 感谢 cmets 中的 marcospereira 提示,我将问题确定为 play / ebeans 中的 fetch 问题,而不是 JSON 性能问题。

显然,我的实体一开始是惰性加载的(/flat),通过启用 SQL 日志记录,我可以看到只有在我的代码命中 .toJson() 时才会触发正确的准备好的 SELECT。如此多的子对象仅在调用 .toJson() 时才从数据库中获取,这会导致数百个 SELECT,因此需要相当长的时间才能完成。

玩了一下 RDS 实例规模,我发现了一些非常奇怪的结果。这与最初提出的问题并没有真正的关系,但我想分享我的发现,也许它可以对那里的人有所帮助。在下面的部分中阅读它。

RDS 扩展实验...

在我的开发环境 (t1.micro) 中,我在一个小型 RDS 实例 (db.t2.micro) 上连接了我的 prod DB 的复制实例,以查看是否有任何变化。

我的 prod 环境 (t2.large) + prod RDS (db.t2.large) 花了大约 19.5 秒来完成 API 调用。新的开发环境(t1.micro + db.t2.micro)在计算和数据库方面都较弱,只用了大约 10.5 秒,这是非常不确定的,因为基本上两个实例都运行相同的代码,只是指向到另一个数据库服务器(具有相同的数据库内容)。我将开发数据库切换到 db.m4.large 以查看这是否带来了任何改进,并且加载时间下降到大约 5.5 秒。

我完全不明白为什么更快的产品 EC2 实例在完全相同的 API 调用上比开发实例需要更多时间。最后,我将 prod db 类从 db.t2.large 更改为 db.m4.large,现在的响应时间为 4.0s。

感觉“旧”的 prod 数据库实例有点磨损/堵塞(有这样的事情吗?我有点怀疑......)。即使是较小的开发实例 + 开发数据库也响应得更快。尽管不同的 RDS 缩放带来了一些改进,但我怀疑 db.t2.large -> db.m4.large 之间的差异会导致这种幅度的变化。

如果有人对正在发生的事情有一些想法,我会很乐意讨论这个问题。

【讨论】:

    猜你喜欢
    • 2015-11-23
    • 2021-04-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-26
    • 2013-02-04
    • 2015-05-11
    • 1970-01-01
    相关资源
    最近更新 更多