【问题标题】:Keeping data in memory, Design approach将数据保存在内存中,设计方法
【发布时间】:2011-11-03 05:37:36
【问题描述】:

我有一个问题,我需要处理一些大小在几 kb 到最大 1 GB 范围内的文件。用例是这样的输入是某种平面文件格式,其中数据存储在一行中,比如一些支付指令。应用程序必须通过每个支付指令并根据一些分组逻辑形成组。最后,必须将组转换为另一种格式(ISO 20022 xml),使用该格式进行支付处理。

目前的设计是这样的,我们有两个表,其中分组标准数据存储在一个表中,单个支付指令存储在另一个表中(从组表到支付指令表的一对多关系)。在第 1 步中:当我们浏览平面文件时,我们识别它所属的组,并写入数据库(btw 批量提交)。

在第 2 步中:在批处理中,组被一一读取并形成输出 xml 并发送到目的地。

我现在面临的问题是,如果整个事情都可以在内存中完成,那么写入两个表并从中获取是过度的。

我正在考虑一种方法,我可以在其中保留 HashTable(google guava (MapMaker)) 类型的缓存,以及我可以指定的大小,一旦缓存达到上限,我可以写将它们放入数据库表中(在放入缓存中编织一个方面)。

以同样的方式检索条目时,我可以先在缓存中检查键,如果不存在,则查询数据库。

您对这种设计方法有什么看法(这是另一个错误还是我可以使其实用且同时稳定且可以扩展的东西)。

为什么我想到这个,我们并不总是有大文件进来,只有当我们无法在内存中处理整个文件并且可能导致 OutOfMemory 问题时,我们才需要这些临时表。

你能不能给点建议?

谢谢

【问题讨论】:

  • 为了开发自定义面向方面的解决方案的成本,我敢打赌,您可以购买几台具有足够 RAM 的服务器来处理内存中的 1GB 文件 :) 听起来像是一个基金的想法!
  • 写一个方面并没有那么复杂,但是在这种情况下它可能有点矫枉过正,你不能简单地将哈希图包装在你自己的一个类中,在一定数量的“添加”之后刷新到数据库” 并在数据库中搜索“get”是否没有结果并且已经缓存了结果?正如任何缓存或多或少一样:D
  • @SimoneGianni 看到问题是我在其他任何地方都没有见过这样的设计。因此,如果我提出这样的建议,大佬们会因为我现在无法预见的任何其他原因而直接拒绝,还是您认为这是解决手头问题的好方法?
  • @nobody 您基本上是在缓冲对 DB 的写入并缓存从中读取的内容,您“转储”到 DB 的 RAM 上限。我认为,如果你把这件事简单地说出来,没有人会不同意。

标签: java algorithm design-patterns caching batch-file


【解决方案1】:

我认为你的设计听起来很合理。但是,有几件事要记住。首先,您确定增加额外的复杂性是合理的吗?也就是说,写入一堆文件然后读回它们的性能是否会成为一个重要的瓶颈?如果浪费的时间不重要,我强烈警告您不要进行此更改。您只会增加系统的复杂性而没有太多好处。我假设您已经考虑过这一点,但以防万一您还没有想到我会在这里发布。

其次,您是否考虑过通过MappedByteBuffer 使用内存映射文件?如果您正在处理超出 Java 堆空间的大型对象并且愿意付出一些努力,那么您可能需要考虑设计对象,以便将它们存储在内存映射文件中。您可以通过创建一个包装器类来做到这一点,该包装器类本质上是一个将请求转换为映射字节缓冲区中的操作的瘦包装器。例如,如果您想存储请求列表,您可以通过创建一个使用MappedByteBuffer 在磁盘上存储字符串列表的对象来实现。例如,字符串可以由换行符或空终止符分隔存储。然后,您可以通过遍历文件的字节并重新水化它们来遍历字符串。这种方法的优势在于它将缓存复杂性转移到操作系统上,该操作系统已经进行了数十年的性能调整(假设您使用的是主要操作系统!)以有效地处理这种情况。我曾经在一个 Java 项目上工作过,我在其中构建了一个框架来自动化它,它在许多情况下都运行得非常好。克服它绝对是一个学习曲线,但是一旦它起作用,您可以在 Java 堆空间中保存比以前更多的数据。这基本上完成了您在上面提出的建议,只是它牺牲了一些前期实现的复杂性来让操作系统处理所有缓存。

第三,有没有办法将通行证(1)和(2)结合起来?也就是说,您能否在生成数据库的同时生成 XML 文件?我从您的描述中假设问题是在所有条目都准备好之前您无法生成 XML。但是,您可能需要考虑在磁盘上创建几个不同的文件,每个文件都以序列化 XML 格式存储一种类型的对象,并且可以在传递结束时使用标准命令行实用程序(如 cat)将它们连接在一起.由于这可以简单地通过执行批量字节连接而不是解析数据库内容来完成,因此这可能比您提出的方法更快(并且更容易实现)。如果文件在操作系统缓存中仍然很热(它们可能是,因为您刚刚写入它们),这实际上可能比您当前的方法更快。

第四,如果您关心性能,您是否考虑过并行化您的代码?鉴于要处理的文件大得惊人,您可以考虑将该文件拆分为许多较小的区域。然后每个任务将从文件中读取并将这些片段分发到适当的输出文件中。然后,您可以有一个最终过程来将相同的文件合并在一起并生成整个 XML 报告。由于我假设这是一个主要受 I/O 限制的操作(它主要只是文件读取),因此与尝试将所有内容都保存在内存中的单线程方法相比,这可以为您带来更大的性能提升。

希望这会有所帮助!

【讨论】:

  • 从输入中读取、分组和形成输出并不需要太多时间。但是内存占用远远超过可接受的范围。所以我们去了中间表。但这极大地影响了性能。听说过 java.nio.* 包。会这样探索。顺便说一句,你建议的设计看起来不错。第三个,猫或其他方式将它们连接在一起是否需要一个外部代理而不是纯java,对吗?此应用程序不会被监控,也不是纯粹的批处理用例。感谢您的精彩回答。
  • @nobody- cat 是一个操作系统级别的实用程序,但您可以在 Java 中调用它,而无需某种外部程序来驱动一切。您也可以使用java.nioChannel 功能自己实现cat 类似的功能。
【解决方案2】:

我看不出您的缓存需求如此奇特,以至于您不能使用现成的组件。 您可以尝试使用 Hibernate 来访问您的数据库。它支持缓存。

【讨论】:

  • Hibernate 有 if(缓存达到限制),然后写入 db 否则不写入 db 类型的缓存系统?
  • 不,最终这一切都在 db 中结束,但您的 get 操作有望获得大量缓存命中。
【解决方案3】:

您是否看过Spring Batch,它支持处理平面文件、按字段值拆分它们和并行处理结果。使用 Spring jdbc,您仍然可以将分组标准存储在数据库中,而无需使用中间表即可处理文件。

【讨论】:

  • 我们正在使用弹簧批处理。但问题是在读取输入数据的最后一个字节之前,我们无法将输出数据刷新出应用程序。因此,不可能阅读所有内容并记住该组以决定该组是否有更多的付款指令进入。所以必须进行分组。此外,即使我们拆分输入文件,内存占用也会或多或少相同,对吧?如果输入中的单个逻辑记录直接映射到输出中的记录,则可以使用 Springs 并行处理。重视您的进一步投入。谢谢
  • 我只是想知道您是否能够编写一个拆分器,可以将文件拆分为每个组的单个文件,然后单独处理它们,然后在最后引入一个合并步骤,这将创建输出文件。这样您就不必同时将所有信息保存在内存中。
【解决方案4】:

不,可能不值得努力进行缓存和回退到(临时?)表,主要是因为它会很复杂,增加风险和成本。

但是,有可能加快初始分组的速度,并且没有任何内容表明您需要为此使用 RDMS。

我建议您跳过自制缓存,并使用持久集合,即由本地磁盘上的文件支持的集合。这种方法很可能会加快小文件和大文件(与使用关系数据库相比)。

但是,您应该进行性能测试...我不确定一个半体面的 java b-tree 是否可以击败正确配置的数据库服务器。但是,如果典型的管理不善的数据库运行在一个蹩脚系统的一个片段上,在一个慢速网络的另一端,那么绝对有机会。

Google 用于持久性集合或 nosql 用于 java;以下是我知道的一些:

http://jdbm.sourceforge.net/ 可以用作“持久/可扩展”映射。 可能是http://code.google.com/p/pcollections/(不过我自己没试过)

您应该能够找到更多;尝试和测试:-)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-10-31
    • 1970-01-01
    • 2019-02-17
    • 2011-04-13
    • 2011-01-21
    • 2021-06-24
    • 2016-11-11
    相关资源
    最近更新 更多