使用SparkR JVM从Scala jar文件

时间:2015-10-23 20:55:46

标签: r scala apache-spark apache-spark-sql sparkr

我希望能够在Scala jar文件中打包DataFrames并在R中访问它们。最终目标是创建一种方法来访问Python,R和Scala中特定且经常使用的数据库表,而无需编写不同的内容每个库。

为此,我在Scala中创建了一个jar文件,其中的函数使用SparkSQL库来查询数据库并获取我想要的DataFrame。我希望能够在R中调用这些函数而不创建另一个JVM,因为Spark已经在R中的JVM上运行。但是,JVM Spark使用的内容未在SparkR API中公开。为了使其可访问并使Java方法可调用,我修改了SparkR包中的“backend.R”,“generics.R”,“DataFrame.R”和“NAMESPACE”并重新构建了包:

在“backend.R”中我制作了“callJMethod”和“createJObject”正式方法:

  setMethod("callJMethod", signature(objId="jobj", methodName="character"), function(objId, methodName, ...) {
  stopifnot(class(objId) == "jobj")
  if (!isValidJobj(objId)) {
    stop("Invalid jobj ", objId$id,
         ". If SparkR was restarted, Spark operations need to be re-executed.")
  }
  invokeJava(isStatic = FALSE, objId$id, methodName, ...)
})


  setMethod("newJObject", signature(className="character"), function(className, ...) {
  invokeJava(isStatic = TRUE, className, methodName = "<init>", ...)
})

我修改了“generics.R”也包含这些功能:

#' @rdname callJMethod
#' @export
setGeneric("callJMethod", function(objId, methodName, ...) { standardGeneric("callJMethod")})

#' @rdname newJobject
#' @export
setGeneric("newJObject", function(className, ...) {standardGeneric("newJObject")})

然后我将这些函数的导出添加到NAMESPACE文件中:

export("cacheTable",
   "clearCache",
   "createDataFrame",
   "createExternalTable",
   "dropTempTable",
   "jsonFile",
   "loadDF",
   "parquetFile",
   "read.df",
   "sql",
   "table",
   "tableNames",
   "tables",
   "uncacheTable",
   "callJMethod",
   "newJObject")

这允许我在不启动新JVM的情况下调用我编写的Scala函数。

我编写的scala方法返回DataFrames,返回时是R中的“jobj”,但SparkR DataFrame是一个环境+一个jobj。为了将这些jobj DataFrames转换为SparkR DataFrames,我在“DataFrame.R”中使用了dataFrame()函数,我也可以按照上述步骤访问它。

然后我可以从R中访问我在Scala中“构建”的DataFrame,并使用该DataFrame上的所有SparkR函数。我想知道是否有更好的方法来制作这样的跨语言库,或者是否有任何理由不应该公开Spark JVM?

1 个答案:

答案 0 :(得分:4)

  

Spark JVM不应该公开的任何原因?

可能不止一个。 Spark开发人员正在努力提供稳定的公共API。实现的低细节,包括客户语言与JVM的通信方式,根本不是合同的一部分。它可以在任何时候完全重写,而不会对用户产生任何负面影响。如果您决定使用它,并且您可以自行进行向后不兼容的更改。

保持内部私密性可减少维护和支持软件的工作量。您根本没有打扰用户可以滥用这些内容的所有可能方式。

  

制作这样一个跨语言库的更好方法

如果不了解您的用例,很难说。我看到至少有三个选择:

  • 对于初学者,R仅提供弱访问控制机制。如果API的任何部分是内部的,您始终可以使用:::函数来访问它。聪明人说:

      

    从那以后在代码中使用:::通常是一个设计错误   相应的对象可能已经保留在内部   很有道理。

    但有一点可以肯定它比修改Spark源要好得多。作为奖励,它清楚地标记了代码中特别脆弱且可能不稳定的部分。

  • 如果您只想创建DataFrames,最简单的方法就是使用原始SQL。它干净,便携,无需编译,包装,只需工作。假设您在名为q

    的变量中存储了类似下面的查询字符串  
    CREATE TEMPORARY TABLE foo
    USING org.apache.spark.sql.jdbc
    OPTIONS (
        url "jdbc:postgresql://localhost/test",
        dbtable "public.foo",
        driver "org.postgresql.Driver"
    )
    

    可以在R:

    中使用    
    sql(sqlContext, q)
    fooDF <- sql(sqlContext, "SELECT * FROM foo")
    

    的Python:

       
    sqlContext.sql(q)
    fooDF = sqlContext.sql("SELECT * FROM foo")
    

    Scala的:

       
    sqlContext.sql(q)
    val fooDF = sqlContext.sql("SELECT * FROM foo")
    

    或直接在Spark SQL中。

  • 最后,您可以使用Spark Data Sources API进行一致且受支持的跨平台访问。

在这三个中我更喜欢原始SQL,其次是针对复杂案例的Data Sources API,并留下内部作为最后的手段。

修改 (2016-08-04)

如果您对JVM的低级访问感兴趣,则会有相对较新的包rstudio/sparkapi,它会公开内部SparkR RPC协议。很难预测它将如何演变,因此使用它需要您自担风险。