我有一份由Antonios Chalkiopoulos编写的MapReduce with Scalding的副本。在书中,他讨论了Scalding代码的外部操作设计模式。你可以在他的网站here上看到一个例子。我已经选择使用Type Safe API。当然,这引入了新的挑战,但我更喜欢它而不是Fields API,这是我之前提到的书和网站中大量讨论的内容。
我想知道人们如何使用Type Safe API实现外部操作模式。我的初步实施如下:
我创建了一个扩展com.twitter.scalding.Job的类 作为我的Scalding工作班,我将管理论点,定义 点击,并使用外部操作来构建数据处理 管道'
我创建了一个对象,我在其中定义要在Type中使用的函数 安全管道。因为Type Safe管道将一个函数作为参数, 然后我可以将对象中的函数作为参数传递给 管道
这会创建如下所示的代码:
class MyJob(args: Args) extends Job(args) {
import MyOperations._
val input_path = args(MyJob.inputArgPath)
val output_path = args(MyJob.outputArgPath)
val eventInput: TypedPipe[(LongWritable, Text)] = this.mode match {
case m: HadoopMode => TypedPipe.from(WritableSequenceFile[LongWritable, Text](input_path))
case _ => TypedPipe.from(WritableSequenceFile[LongWritable, Text](input_path))
}
val eventOutput: FixedPathSource with TypedSink[(LongWritable, Text)] with TypedSource[(LongWritable, Text)] = this.mode match {
case m: HadoopMode => WritableSequenceFile[LongWritable, Text](output_path)
case _ => TypedTsv[(LongWritable, Text)](output_path)
}
val validatedEvents: TypedPipe[(LongWritable, Either[Text, Event])] = eventInput.map(convertTextToEither).fork
validatedEvents.filter(isEvent).map(removeEitherWrapper).write(eventOutput)
}
object MyOperations {
def convertTextToEither(v: (LongWritable, Text)): (LongWritable, Either[Text, Event]) = {
...
}
def isEvent(v: (LongWritable, Either[Text, Event])): Boolean = {
...
}
def removeEitherWrapper(v: (LongWritable, Either[Text, Event])): (LongWritable, Text) = {
...
}
}
如您所见,传递给Scalding Type Safe操作的函数与作业本身保持独立。虽然这不像“干净”。作为外部操作模式,这是编写此类代码的快速方法。另外,我可以使用JUnitRunner进行工作级集成测试,使用ScalaTest进行功能级单元测试。
这篇文章的要点是问人们如何做这种事情?互联网上用于Scalding Type Safe API的文档很少。是否有更多功能Scala友好的方式来做到这一点?我在这里错过了设计模式的关键组件吗?我对此感到紧张,因为使用Fields API,您可以使用ScaldingTest在管道上编写单元测试。据我所知,你不能用TypedPipes做到这一点。如果Scalding Type Safe API存在普遍认可的模式,或者您如何创建可重用,模块化和可测试的Type Safe API代码,请告知我们。谢谢你的帮助!
感谢您的回复。这基本上就是我要找的答案。我想继续谈话。我在回答的过程中看到的主要问题是,这个实现需要特定的类型实现,但如果类型在整个作业中发生变化会怎么样?我已经探索过这段代码,它似乎有效,但它似乎被黑了。
def self: TypedPipe[Any]
def testingPipe: TypedPipe[(LongWritable, Text)] = self.map(
(firstVar: Any) => {
val tester = firstVar.asInstanceOf[(LongWritable, Text)]
(tester._1, tester._2)
}
)
这方面的好处是我声明了一个自我的实现,但缺点是这种丑陋的类型转换。另外,我没有用更复杂的管道深入测试这个。所以基本上,你对如何处理类型的想法是什么,只有一个自我实现清洁/简洁?
答案 0 :(得分:2)
Scala extension methods是使用隐式类实现的。 您向编译器添加了将TypedPipe转换为包含外部操作的(包装器)类的功能:
import com.twitter.scalding.TypedPipe
import com.twitter.scalding._
import cascading.flow.FlowDef
class MyJob(args: Args) extends Job(args) {
implicit class MyOperationsWrapper(val self: TypedPipe[Double]) extends MyOperations with Serializable
val pipe = TypedPipe.from(TypedTsv[Double](args("input")))
val result = pipe
.operation1
.operation2(x => x*2)
.write(TypedTsv[Double](args("output")))
}
trait MyOperations {
def self: TypedPipe[Double]
def operation1(implicit fd: FlowDef): TypedPipe[Double] =
self.map { x =>
println(s"Input: $x")
x / 100
}
def operation2(datafn:Double => Double)(implicit fd: FlowDef): TypedPipe[Double] =
self.map { x=>
val result = datafn(x)
println(s"Result: $result")
result
}
}
import org.apache.hadoop.util.ToolRunner
import org.apache.hadoop.conf.Configuration
object MyRunner extends App {
ToolRunner.run(new Configuration(), new Tool, (classOf[MyJob].getName :: "--local" ::
"--input" :: "doubles.tsv" ::
"--output":: "result.tsv" :: args.toList).toArray)
}
关于如何跨管道管理类型,我的建议是尝试找出一些有意义的基本类型和用例类。要使用您的示例,我会将方法convertTextToEither
重命名为extractEvents
:
case class LogInput(l : Long, text: Text)
case class Event(data: String)
def extractEvents( line : LogInput ): TypedPipe[Event] =
self.filter( isEvent(line) )
.map ( getEvent(line.text) )
然后你会
LogInputOperations
代表LogInput
个类型EventOperations
代表Event
个类型答案 1 :(得分:1)
我不确定您在显示的代码段中看到的问题是什么,以及为什么您认为它“不太干净”。它看起来很好。
对于使用类型化API问题的单元测试工作,请查看JobTest,它似乎正是您正在寻找的。