Использование 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. Трудно предсказать, как он будет развиваться, поэтому используйте его на свой страх и риск.