我希望能够在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?
答案 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协议。很难预测它将如何演变,因此使用它需要您自担风险。