登录Apache Spark的功能方法

登录Apache Spark非常容易,因为Spark可以直接访问日志对象。 仅需要完成一些配置设置。 在上一篇文章中 ,我们研究了如何执行此操作,同时确定了可能出现的一些问题。 但是,当您准备好收集日志时,提出的解决方案可能会引起一些问题,因为它们分布在整个群集中。 即使您使用YARN日志聚合功能,也会有一些争执可能影响性能,或者最终可能导致日志交错,从而破坏了日志本身的性质。

在此博客文章中,我将演示如何通过采用其他更实用的方法来解决这些问题。

莫纳德作家

我无意讨论有关monad或Monad Writer的详细信息,因此,如果您想了解更多信息,请阅读“ Functors,Applicatives和Monads In Pictures ”,它对这个主题很有帮助

只是为了将上下文放在上下文中,假设monad writer( writer )是一个容器,除了值的历史记录(对数)(值的转换集)之外,还包含计算的当前值。

由于作者具有单调属性,因此它使我们能够进行功能转换,并且很快我们将看到所有内容如何结合在一起。

一个简单的日志

下面的代码演示了一个简单的日志。

object app {
  def main(args: Array[String]) {
    val log = LogManager.getRootLogger
    log.setLevel(Level.WARN)

    val conf = new SparkConf().setAppName("demo-app")
    val sc = new SparkContext(conf)

    log.warn("Hello demo")

    val data = sc.parallelize(1 to 100000)

    log.warn("I am done")
  }
}

唯一需要注意的是,日志记录实际上是在Spark驱动程序上进行的,因此我们没有同步或争用问题。 但是,一旦开始分配计算,一切都会变得复杂。

以下代码无效(请阅读上一篇文章以了解原因)

val log = LogManager.getRootLogger
val data = sc.parallelize(1 to 100000)

data.map { value => 
    log.info(value)
    value.toString
}

解决的办法,还介绍在以前的帖子 ,但它需要额外的工作来管理日志。

一旦开始在群集的每个节点上登录,就需要转到每个节点并收集每个日志文件,以便了解日志中的内容。 希望您正在使用某种工具来帮助您完成此任务,例如Splunk,Datalog等。但是,您仍然需要知道如何将这些日志输入系统。

我们的数据集

我们的数据集是“ Person”类的集合,该类将进行转换,同时保持对我们数据集上操作的统一记录。

假设我们要加载我们的数据集,过滤每个不到20岁的人,最后提取他/她的名字。 这是一个非常愚蠢的示例,但是它将演示如何生成日志。 您可以替换这些计算,但是构建统一日志的想法将保留。

获得作家

为了使用TypeLevel / Cats库导入monad writer,我们将以下行添加到build.sbt文件中。

libraryDependencies += "org.typelevel" %% "cats" % "0.6.1"

玩我们的数据

现在,让我们定义我们将要使用的转换。 首先,让我们加载数据。

def loadPeopleFrom(path: String)(implicit sc: SparkContext) = 
  s"loading people from $path" ~> sc.textFile(path)
                                    .map(x => User(x.split(",")(0), x.split(",")(1).toInt))

在这里,〜>操作是通过隐式转换定义的,如下所示:

implicit class toW(s: String) {
  def ~>[A](rdd: RDD[A]): Writer[List[String], RDD[A]] = Writer(s :: Nil, rdd)
}

如果您仔细观察,我们的加载操作不会返回RDD。 实际上,它返回跟踪记录的monad编写器。

让我们定义要应用于用户集合的过滤器。

def filter(rdd: RDD[User])(f: User => Boolean) = "filtering users" ~> rdd.filter(f)

同样,我们将应用相同的函数(〜>)来跟踪此转换。

最后,我们定义映射,该映射遵循我们刚刚看到的相同模式。

def mapUsers(rdd: RDDUser])(prefix: String): Writer[List[String], RDD[String]] = 
  "mapping users" ~> rdd.map(p => prefix + p.name)

把它放在一起

到目前为止,我们仅定义了转换,但是我们需要将它们结合在一起。 Scala for是处理单子结构的一种非常方便的方法。 让我们看看如何。

val result = for {
  person          <- loadPeopleFrom("~/users_dataset/")(sc)
  filtered        <- filter(person)(_.age < 20)
  namesWithPrefix <- mapUsers(filtered)("hello")
} yield namesWithPrefix

val (log, rdd) = result.run

请注意,结果的类型为: Writer[List[String], RDD[String]]

全部result.run将给我们log: List[String] ,最终的计算由rdd: RDD[String]表示rdd: RDD[String]

在这一点上,我们可以使用Spark logger记录由转换链生成的日志。 请注意,此操作将在Spark主数据库上执行,这意味着将创建一个包含所有日志信息的日志文件。 我们还在日志写入期间消除了潜在的争用问题。 此外,我们没有锁定日志文件,这可以通过以串行方式创建和写入该文件来避免性能问题。

结论

在此博客文章中,我向您展示了如何使用Monad Writer改进如何登录Apache Spark。 这种功能性方法使您可以将日志的创建与计算一起分发,这是Spark可以很好完成的事情。 但是,不是将日志写在每个工作程序节点上,而是将它们收集回主数据库以进行记录。

与先前的实现相比,此机制具有某些优势。 现在,您可以精确控制日志的记录方式和记录时间,可以通过删除工作节点上的IO操作来提高性能,可以通过以串行方式写入日志来消除同步问题,并且可以避免危害您整个集群中的捕鱼日志。 如果您对这种登录Apache Spark的功能方法有任何疑问,请在下面的评论部分中提问。

翻译自: https://www.javacodegeeks.com/2016/08/functional-approach-logging-apache-spark.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值