Использование SparkR JVM для вызова методов из файла Scala jar
Я хотел иметь возможность упаковать DataFrames в jar файл Scala и получить к ним доступ в R. Конечной целью является создание способа доступа к определенным и часто используемым таблицам базы данных в Python, R и Scala без написания другой библиотеки для каждого.
Чтобы сделать это, я создал файл jar в Scala с функциями, которые используют библиотеку SparkSQL для запроса базы данных и получения требуемых DataFrames. Я хотел иметь возможность называть эти функции в R без создания другой JVM, поскольку Spark уже работает на JVM в R. Однако использование JVM Spark не отображается в SparkR API. Чтобы сделать его доступным и сделать Java-методы вызываемыми, я изменил "backend.R", "generics.R", "DataFrame.R" и "NAMESPACE" в пакете SparkR и перестроил пакет:
В "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")
Это позволило мне вызвать функции Scala, которые я написал, не запустив новую JVM.
В методах Scala я написал возвращаемые DataFrames, которые при возврате являются "jobj" s в R, но SparkR DataFrame - это среда + jobj. Чтобы превратить эти данные DataFrames в SparkR DataFrames, я использовал функцию dataFrame() в "DataFrame.R", которую я также сделал доступной, следуя приведенным выше шагам.
Затем я смог получить доступ к DataFrame, который я "построил" в Scala из R, и использовать все функции SparkR на этом DataFrame. Мне было интересно, есть ли лучший способ сделать такую кросс-язычную библиотеку, или если есть какая-то причина, что Spark JVM не должен быть общедоступным?
Ответы
Ответ 1
по какой-либо причине Spark JVM не должен быть общедоступным?
Возможно, более одного. Разработчики Spark прилагают серьезные усилия для обеспечения стабильного публичного API. Низкие детали реализации, включая способ взаимодействия гостевых языков с JVM, просто не являются частью контракта. Он может быть полностью переписан в любой момент без какого-либо негативного воздействия на пользователей. Если вы решите использовать его, и есть обратные несовместимые изменения, вы сами по себе.
Сохранение внутренних компонентов снижает затраты на поддержку и поддержку программного обеспечения. Вы просто не беспокоитесь о всех возможных способах злоупотребления ими.
лучший способ сделать такую кросс-язычную библиотеку
Трудно сказать, не зная больше о вашем случае использования. Я вижу по крайней мере три варианта:
-
Для начала R предоставляет только слабые механизмы контроля доступа. Если какая-либо часть API является внутренней, вы всегда можете использовать функцию :::
для ее доступа. Как говорят умные люди:
Как правило, ошибка дизайна использует :::
в вашем коде, поскольку соответствующий объект, вероятно, был внутренним для хорошая причина.
но одна вещь наверняка намного лучше, чем изменение источника искры. В качестве бонуса он четко отмечает части вашего кода, которые являются особенно хрупкими, потенциально нестабильными.
-
Если вы хотите создать DataFrames, проще всего использовать raw 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, а затем API Data Sources для сложных случаев и оставить внутренности в качестве последнего средства.
Изменить (2016-08-04):
Если вам интересен доступ к JVM на низком уровне, существует относительно новый пакет rstudio/sparkapi, который предоставляет внутренний протокол SparkR RPC. Трудно предсказать, как он будет развиваться, поэтому используйте его на свой страх и риск.