我有两个版本(旧/新)的数据库表,大约有100,000,000条记录。它们在文件中:
trx-old
trx-new
结构是:
id date amount memo
1 5/1 100 slacks
2 5/1 50 wine
id是简单的主键,其他字段是非键。我想生成三个文件:
trx-removed (ids of records present in trx-old but not in trx-new)
trx-added (records from trx-new whose ids are not present in trx-old)
trx-changed (records from trx-new whose non-key values have changed since trx-old)
我需要每天在短批处理窗口中执行此操作。实际上,我需要为多个表和多个模式(为每个模式生成三个文件)执行此操作,因此实际的应用程序更加复杂。但我认为这个例子捕捉到了问题的关键。
这感觉就像mapreduce的一个明显的应用。从未编写过mapreduce应用程序,我的问题是:
PS我看到了diff between tables问题,但那里的解决方案看起来并不具备可扩展性。
PPS这是一个演示算法的小玩具:Ruby dbdiff
答案 0 :(得分:2)
我认为编写自己的工作最简单,主要是因为当典型的reducer只写入一个文件时,你会想要使用MultipleOutputs从单个reduce步骤写入三个单独的文件。您需要使用MultipleInputs为每个表指定映射器。
答案 1 :(得分:1)
我想到的是:
考虑你的表是这样的:
Table_old
1 other_columns1
2 other_columns2
3 other_columns3
Table_new
2 other_columns2
3 other_columns3
4 other_columns4
追加table_old的元素“a”和table_new的元素“b”。
合并两个文件时,如果第一个文件中存在元素而第二个文件中没有元素,则会将其删除
table_merged
1a other_columns1
2a other_columns2
2b other_columns2
3a other_columns3
3b other_columns3
4a other_columns4
通过该文件,您可以轻松完成操作。
另外,假设你的id是n位数,你有10个簇+ 1个主。您的密钥将是id的第一位数,因此,您将数据均匀地划分为群集。您可以进行分组+分区,以便对数据进行排序。
示例,
table_old
1...0 data
1...1 data
2...2 data
table_new
1...0 data
2...2 data
3...2 data
您的密钥是第一个数字,您根据该数字进行分组,并且您的分区是根据id的其余部分。然后您的数据将作为
进入您的群集worker1
1...0b data
1...0a data
1...1a data
worker2
2...2a data
2...2b data and so on.
请注意,a,b不必排序。
修改强> 合并将是这样的:
FileInputFormat.addInputPath(job, new Path("trx-old"));
FileInputFormat.addInputPath(job, new Path("trx-new"));
MR将获得两个输入,两个文件将合并,
对于附加部分,您应该在Main MR之前再创建两个作业,它们只有Map
。第一个Map
将append "a"
将append "b"
添加到第一个列表中的每个元素,第二个Map-Map-Reduce
将//you have key:Text
new Text(String.valueOf(key.toString()+"a"))
添加到第二个列表的元素中。第三个工作(我们现在使用的那个/主要地图)只会减少工作以收集它们。所以你将{{1}}。
可以像那样进行追加
{{1}}
但我认为可能有不同的追加方式,其中一些可能更有效率 (text hadoop)
希望它会有所帮助,
答案 2 :(得分:1)
这似乎是级联中要解决的完美问题。您已经提到您从未编写过MR应用程序,如果想要快速入门(假设您熟悉Java),那么Cascading就是去恕我直言的方法。我会在一秒钟内对此进行更多的了解。
可以使用Pig或Hive,但是如果您想对这些列执行额外的分析或更改模式,这些可能不够灵活,因为您可以通过读取列标题或从列标题在Cascading中动态构建模式您创建的映射文件,用于表示架构。
在Cascading
中你会:
Taps
:点击trxOld并点击trxNew(这些指向您的源文件)Pipes
:管道oldPipe和Pipe newPipe Taps
:点击trxRemoved,点按trxAdded并点按trxChanged trx-removed: TRX-加入强>
Pipe trxOld = new Pipe ("old-stuff");
Pipe trxNew = new Pipe ("new-stuff");
//smallest size Pipe on the right in CoGroup
Pipe oldNnew = new CoGroup("old-N-new", trxOld, new Fields("id1"),
trxNew, new Fields("id2"),
new OuterJoin() );
外连接为我们提供了NULLS,其他管道(您的源数据)中缺少ID,因此我们可以在后面的逻辑中使用FilterNotNull
或FilterNull
来获取我们的最终管道然后连接到Tap trxRemoved并相应地点击trxAdded。
<强> TRX-改变强>
在这里,我首先使用FieldJoiner
连接您正在寻找更改的字段,然后使用ExpressionFilter
向我们提供僵尸(因为它们已更改),例如:
Pipe valueChange = new Pipe("changed");
valueChange = new Pipe(oldNnew, new Fields("oldValues", "newValues"),
new ExpressionFilter("oldValues.equals(newValues)", String.class),
Fields.All);
这样做是过滤掉具有相同值的字段并保留差异。而且,如果上面的表达式为真,它就会删除该记录。最后,将您的valueChange管道连接到Tap trxChanged,您将拥有三个输出,其中包含您要查找的所有数据,并允许进行一些额外的分析。
答案 3 :(得分:1)
正如@ChrisGerken建议的那样,您必须使用MultipleOutputs
和MultipleInputs
来生成多个输出文件,并将自定义映射器关联到每个输入文件类型(旧/新)。
映射器将输出:
reducer将迭代每个键的所有记录R
并输出:
R
中的记录不同。由于此算法会根据reducer的数量进行扩展,因此您很可能需要第二个作业,这会将结果合并到单个文件中以进行最终输出。