基于流的解析和编写JSON

Gua*_*Joe 6 ruby memory io parsing json

我从1,000个批次的服务器中获取大约20,000个数据集.每个数据集都是JSON对象.坚持这使得大约350 MB的未压缩明文.

我的内存限制为1GB.因此,我将每个1,000个JSON对象作为数组写入附加模式的原始JSON文件中.

结果是一个包含20个JSON数组的文件,需要进行聚合.无论如何我需要触摸它们,因为我想添加元数据.通常Ruby Yajl Parser可以这样做:

raw_file = File.new(path_to_raw_file, 'r')
json_file = File.new(path_to_json_file, 'w')

datasets = []
parser = Yajl::Parser.new
parser.on_parse_complete = Proc.new { |o| datasets += o }

parser.parse(datasets)

hash = { date: Time.now, datasets: datasets }
Yajl::Encoder.encode(hash, json_file)
Run Code Online (Sandbox Code Playgroud)

这个解决方案的问题在哪里?问题是仍然将整个JSON解析为内存,我必须避免.

基本上我需要的是一个解决方案,它从IO对象解析JSON并同时将它们编码到另一个IO对象.

我假设Yajl提供了这个,但我没有找到方法,它的API也没有给出任何提示,所以我猜不是.是否有支持此功能的JSON Parser库?还有其他解决方案吗?


我能想到的唯一解决方案就是使用这些IO.seek功能.写的所有数据集阵列此起彼伏[...][...][...],每个数组后,我找回到开始并覆盖][,,有效手动连接的阵列.

the*_*Man 5

为什么不能一次从数据库中检索单个记录,根据需要处理它,将其转换为JSON,然后使用尾随/分隔逗号发出它?

如果你开始使用一个只包含的文件[,然后附加你所有的JSON字符串,那么,在最后的条目中没有附加逗号,而是使用了一个结束],你有一个JSON的哈希数组,并且只有一次处理一行的价值.

它会慢一些(可能)但不会影响你的系统.如果您使用阻塞/分页一次检索合理数量的记录,则DB I/O可以非常快.

例如,这里是一些Sequel示例代码和代码的组合,用于将行提取为JSON并构建更大的JSON结构:

require 'json'
require 'sequel'

DB = Sequel.sqlite # memory database

DB.create_table :items do
  primary_key :id
  String :name
  Float :price
end

items = DB[:items] # Create a dataset

# Populate the table
items.insert(:name => 'abc', :price => rand * 100)
items.insert(:name => 'def', :price => rand * 100)
items.insert(:name => 'ghi', :price => rand * 100)

add_comma = false

puts '['
items.order(:price).each do |item|
  puts ',' if add_comma
  add_comma ||= true
  print JSON[item]
end
puts "\n]"
Run Code Online (Sandbox Code Playgroud)

哪个输出:

[
{"id":2,"name":"def","price":3.714714089426208},
{"id":3,"name":"ghi","price":27.0179624376119},
{"id":1,"name":"abc","price":52.51248221170203}
]
Run Code Online (Sandbox Code Playgroud)

请注意,订单现在是"价格".

验证很简单:

require 'json'
require 'pp'

pp JSON[<<EOT]
[
{"id":2,"name":"def","price":3.714714089426208},
{"id":3,"name":"ghi","price":27.0179624376119},
{"id":1,"name":"abc","price":52.51248221170203}
]
EOT
Run Code Online (Sandbox Code Playgroud)

结果如下:

[{"id"=>2, "name"=>"def", "price"=>3.714714089426208},
 {"id"=>3, "name"=>"ghi", "price"=>27.0179624376119},
 {"id"=>1, "name"=>"abc", "price"=>52.51248221170203}]
Run Code Online (Sandbox Code Playgroud)

这将验证JSON并证明原始数据是可恢复的.从数据库中检索的每一行应该是您要构建的整个JSON结构的最小"bitesized"部分.

在此基础上,这里是如何读取数据库中的传入JSON,操作它,然后将其作为JSON文件发出:

require 'json'
require 'sequel'

DB = Sequel.sqlite # memory database

DB.create_table :items do
  primary_key :id
  String :json
end

items = DB[:items] # Create a dataset

# Populate the table
items.insert(:json => JSON[:name => 'abc', :price => rand * 100])
items.insert(:json => JSON[:name => 'def', :price => rand * 100])
items.insert(:json => JSON[:name => 'ghi', :price => rand * 100])
items.insert(:json => JSON[:name => 'jkl', :price => rand * 100])
items.insert(:json => JSON[:name => 'mno', :price => rand * 100])
items.insert(:json => JSON[:name => 'pqr', :price => rand * 100])
items.insert(:json => JSON[:name => 'stu', :price => rand * 100])
items.insert(:json => JSON[:name => 'vwx', :price => rand * 100])
items.insert(:json => JSON[:name => 'yz_', :price => rand * 100])

add_comma = false

puts '['
items.each do |item|
  puts ',' if add_comma
  add_comma ||= true
  print JSON[
    JSON[
      item[:json]
    ].merge('foo' => 'bar', 'time' => Time.now.to_f)
  ]
end
puts "\n]"
Run Code Online (Sandbox Code Playgroud)

哪个产生:

[
{"name":"abc","price":3.268814929005337,"foo":"bar","time":1379688093.124606},
{"name":"def","price":13.871147312377719,"foo":"bar","time":1379688093.124664},
{"name":"ghi","price":52.720984131655676,"foo":"bar","time":1379688093.124702},
{"name":"jkl","price":53.21477190840114,"foo":"bar","time":1379688093.124732},
{"name":"mno","price":40.99364022416619,"foo":"bar","time":1379688093.124758},
{"name":"pqr","price":5.918738444452265,"foo":"bar","time":1379688093.124803},
{"name":"stu","price":45.09391752439902,"foo":"bar","time":1379688093.124831},
{"name":"vwx","price":63.08947792357426,"foo":"bar","time":1379688093.124862},
{"name":"yz_","price":94.04921035056373,"foo":"bar","time":1379688093.124894}
]
Run Code Online (Sandbox Code Playgroud)

我添加了时间戳,以便您可以看到每行都是单独处理的,并且可以让您了解行的处理速度.当然,这是一个微小的内存数据库,它没有内容的网络I/O,但通过切换到合理数据库主机上的数据库的正常网络连接也应该非常快.告诉ORM以块的形式读取数据库可以加快处理速度,因为DBM将能够返回更大的块以更有效地填充数据包.您需要尝试确定所需的块大小,因为它会根据您的网络,主机和记录大小而有所不同.

在处理企业级数据库时,您的原始设计并不好,尤其是当您的硬件资源有限时.多年来,我们已经学会了如何解析BIG数据库,这使得20,000个行表显得微不足道.VM切片现在很常见,我们使用它们进行处理,所以它们通常是昔日的PC:单CPU,内存占用少,驱动器很小.我们无法击败他们或者他们将成为瓶颈,所以我们必须将数据分解成我们能够做到的最小的原子碎片.

强调数据库设计:将JSON存储在数据库中是一个值得怀疑的做法.DBM现在可以显示行的JSON,YAML和XML表示,但是强制DBM在存储的JSON,YAML或XML字符串中搜索是处理速度的主要因素,因此除非您还具有等效的查找数据,否则不惜一切代价避免它在单独的字段中编制索引,以便您的搜索速度达到最高速度.如果数据在单独的字段中可用,那么执行良好的数据库查询,在DBM或您选择的脚本语言中进行调整,以及发送按摩数据变得更加容易.