Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Esta página descreve como criar funções definidas pelo utilizador (UDFs) em Scala e Java, registá-las no Catálogo Unity e partilhá-las entre ambientes de computação. Os UDFs do Unity Catalog permitem reutilizar lógica JVM existente com governação e controlos de acesso do Unity Catalog.
Ao contrário dos UDFs Scala com âmbito de sessão, que estão limitados a um único bloco de notas ou cluster, os UDFs registados no Unity Catalog são:
- Governado: Gerido com permissões e controlos de acesso do Unity Catalog.
- Reutilizável: Partilhado entre equipas, cadernos, trabalhos e armazéns SQL.
- Descobrível: Visível no Explorador de Catálogos e nas tabelas do sistema.
- Isolado: Executado em ambientes isolados com um custo único de arranque inicial por sessão. As chamadas seguintes são rápidas.
Requisitos
Seu espaço de trabalho deve estar habilitado para o Catálogo Unity. Aplicam-se os seguintes requisitos adicionais.
Processamento: São suportados todos os tipos de processamento, incluindo notebooks e tarefas sem servidor, armazéns SQL e Pipelines Declarativos do Spark no Lakeflow. A computação clássica requer o Databricks Runtime 18.2 ou superior. Na computação sem servidor e nos armazéns de dados SQL, a definição da UDF tem de especificar a Versão 4 do Ambiente ou superior no campo environment_version. Este requisito aplica-se à definição da UDF, não ao notebook nem ao job que a invoca. Consulte as versões do ambiente sem servidor .
Desenvolvimento:
- Scala: 2.13.16. O Scala 2.12 não é suportado.
- JDK: 17.
- Embalagem: Um JAR grosso contendo todas as dependências de terceiros usadas pela UDF.
Permissões:
- Crie um UDF:
USAGEeCREATE FUNCTIONno esquema, eUSAGEno catálogo. - Executa um UDF:
EXECUTEna função, eUSAGEno esquema e catálogo. - Acede ao ficheiro JAR:
READ VOLUMEno volume onde o JAR está armazenado.
Consulte Gerenciar privilégios no Catálogo Unity para mais informações sobre permissões do Catálogo Unity.
Constrói o teu JAR UDF
Empacota o teu código compilado como JAR e carrega-o para um volume do Unity Catalog antes de registares o UDF. Escolha um método de construção:
Construir localmente
Siga estes passos para construir um JAR gordo utilizando um ambiente de desenvolvimento local.
Configura o teu ambiente
Instale as ferramentas necessárias na sua máquina local. Os seguintes comandos são para macOS. Para outras plataformas, instala JDK 17 e sbt (Scala) ou Maven (Java) usando o gestor de pacotes da tua plataforma.
Scala
Instale o JDK 17 e o sbt:
brew install openjdk@17
brew install sbt
Verifique a sua instalação:
java -version # Should show Java 17
sbt --version # Should show sbt version
Java
Instale o JDK 17 e o Maven:
brew install openjdk@17
brew install maven
Verifique a sua instalação:
java -version # Should show Java 17
mvn --version # Should show Maven version
Crie o seu projeto
Configura um projeto em Scala ou Java.
Scala
Crie um novo projeto Scala usando sbt:
sbt new scala/scala-seed.g8
Quando solicitado, introduza um nome de projeto (por exemplo, my-udf-project).
Configurar build.sbt
Substitua o conteúdo do seu build.sbt ficheiro pela seguinte configuração:
scalaVersion := "2.13.16"
ThisBuild / organization := "com.example"
lazy val myUDF = (project in file("."))
.settings(
name := "my-udf"
)
Ativar o plugin sbt-assembly
Criar ou editar project/assembly.sbt e adicionar:
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "2.0.0")
Este plugin cria um fat JAR que contém todas as tuas dependências.
Java
Crie um novo projeto Maven usando o arquétipo de início rápido:
mvn archetype:generate \
-DgroupId=com.example \
-DartifactId=my-udf \
-DarchetypeArtifactId=maven-archetype-quickstart \
-DinteractiveMode=false
Este comando cria a estrutura padrão do projeto Maven com src/main/java diretórios e src/test/java diretórios.
Configurar pom.xml
No ficheiro pom.xml gerado, dentro das etiquetas <project></project>, adiciona um bloco <properties> com a seguinte configuração:
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
Também dentro das <project></project> tags, adicione um <build> bloco com a seguinte configuração:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
O maven-shade-plugin cria um JAR autónomo que contém todas as suas dependências.
Escreve o teu UDF
Ao escrever o seu UDF, consulte os tipos de dados para tipos de dados suportados e mapeamentos de linguagem para ver como os tipos Scala e Java correspondem aos tipos SQL.
O seu handler UDF deve cumprir os seguintes requisitos:
-
Scala: Defina o handler como um método num
object(não umclass). O valorHANDLERcorresponde a um método numaobjectde Scala. -
Java: Defina o handler como um
public staticmétodo. -
Assinatura: Os tipos dos parâmetros, a ordem e o tipo de retorno do método devem corresponder à lista de argumentos e ao tipo
RETURNSna sua instruçãoCREATE FUNCTION. - Apenas escalar: O processador deve devolver um único valor escalar. Os tipos de retorno de tabela não são suportados.
- Autónomo: O manipulador deve operar apenas com os seus argumentos de entrada. Não pode usar APIs do Spark nem depender dos pacotes core do Spark. Consulte Limitações.
Note
Para Scala, um handler com um tipo de parâmetro primitivo (como Int) é ignorado e retorna NULL quando qualquer argumento de entrada é SQL NULL. Para receber e tratar valores NULL, envolva o parâmetro em Option, por exemplo, Option[Int].
Scala
Criar um objeto Scala em src/main/scala/com/example/MyUDF.scala e definir a sua função UDF.
Exemplo básico
package com.example
object MyUDF {
def addOne(x: Int): Int = x + 1
}
Exemplo com dependência externa
Para usar bibliotecas externas, adicione-as ao seu build.sbt ficheiro:
scalaVersion := "2.13.16"
ThisBuild / organization := "com.example"
lazy val myUDF = (project in file("."))
.settings(
name := "currency-udf",
libraryDependencies ++= Seq(
"org.apache.commons" % "commons-lang3" % "3.12.0"
)
)
Depois usa a dependência no teu UDF:
package com.example
import org.apache.commons.lang3.StringUtils
object CurrencyUDF {
private val rates: Map[String, Double] = Map(
"USD" -> 1.0,
"EUR" -> 1.1,
"GBP" -> 1.3,
"JPY" -> 0.007
)
def convertToUSD(price: Double, currency: String): Double = {
require(currency != null, "Currency must not be null")
val normalizedCurrency = StringUtils.upperCase(currency)
rates.get(normalizedCurrency) match {
case Some(rate) => price * rate
case None => throw new IllegalArgumentException(s"Unsupported currency: $currency")
}
}
}
Teste o seu UDF com testes unitários antes de o implementar. Consulte Testar UDFs localmente.
Java
Crie uma classe Java em src/main/java/com/example/MyUDF.java e defina a UDF como um método público estático.
Exemplo básico
package com.example;
public class MyUDF {
public static int addOne(int x) {
return x + 1;
}
}
Exemplo com dependência externa
Para usar bibliotecas externas, adicione-as à <dependencies> secção do seu pom.xml ficheiro:
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
</dependencies>
Em seguida, utiliza a dependência no teu UDF:
package com.example;
import org.apache.commons.lang3.StringUtils;
import java.util.Map;
import java.util.HashMap;
public class CurrencyUDF {
private static final Map<String, Double> rates = new HashMap<>();
static {
rates.put("USD", 1.0);
rates.put("EUR", 1.1);
rates.put("GBP", 1.3);
rates.put("JPY", 0.007);
}
public static double convertToUSD(double price, String currency) {
if (currency == null) {
throw new IllegalArgumentException("Currency must not be null");
}
String normalizedCurrency = StringUtils.upperCase(currency);
if (!rates.containsKey(normalizedCurrency)) {
throw new IllegalArgumentException("Unsupported currency: " + currency);
}
return price * rates.get(normalizedCurrency);
}
}
Teste o seu UDF com testes unitários antes de o implementar. Consulte Testar UDFs localmente.
Note
O seu UDF corre num sandbox isolado sem uma sessão Spark ativa, por isso não pode usar APIs Spark dentro do corpo da função. Por exemplo, não pode criar nem trabalhar com DataFrames ou Datasets, executar spark.sql(...) nem aceder a SparkSession ou SparkContext. A UDF deve ser uma lógica autónoma sobre os seus argumentos de entrada. Também não pode depender dos pacotes core do Spark.
Constrói o teu JAR gordo
Constrói o teu projeto para criar um JAR gordo contendo todas as dependências.
Scala
A partir do diretório raiz do seu projeto, execute:
sbt clean assembly
O fat JAR é criado em target/scala-2.13/ com um nome do tipo my-udf-assembly-0.1.0-SNAPSHOT.jar.
Java
A partir do diretório raiz do seu projeto, execute:
mvn clean package
O fat JAR é criado em target/ com um nome do tipo my-udf-1.0-SNAPSHOT.jar.
Carregue o seu JAR para um volume do Unity Catalog
Se ainda não tem um volume do Catálogo Unity, crie um:
CREATE VOLUME IF NOT EXISTS my_catalog.my_schema.udf_jars
COMMENT 'Storage for UDF JAR files';
Se outros utilizadores precisarem de executar o UDF, conceda-lhes READ VOLUME no volume:
GRANT READ VOLUME ON VOLUME my_catalog.my_schema.udf_jars TO `user@example.com`;
Carregue o seu ficheiro JAR para o volume usando o Explorador de Catálogos:
- No seu espaço de trabalho do Azure Databricks, clique no
Catálogo para abrir o Catalog Explorer.
- Selecione o catálogo e depois selecione o esquema que contém o seu volume.
- Clique no nome do volume.
- Clique em Carregar para este volume e selecione o seu ficheiro JAR.
- Clique em Carregar.
- Depois de concluído o carregamento, clique no nome do seu ficheiro JAR.
-
Clique em Copiar caminho para copiar o caminho do volume para a sua prancheta. Por exemplo,
/Volumes/my_catalog/my_schema/udf_jars/my-udf-assembly-0.1.0-SNAPSHOT.jar(Scala) ou/Volumes/my_catalog/my_schema/udf_jars/my-udf-1.0-SNAPSHOT.jar(Java). Precisas deste caminho quando registas o UDF.
Construir num caderno
Pode compilar um UDF, empacotá-lo como JAR e carregá-lo diretamente para um volume do Unity Catalog a partir de um notebook Azure Databricks. Esta abordagem funciona para UDFs pequenas e sem dependências. Para UDFs com bibliotecas de terceiros, use Build locally.
A célula Python seguinte escreve um UDF Java que limpa uma string (elimina espaços em branco, colapsa espaços repetidos e minúsculas), compila-a com JDK 17, empacota-a como JAR e copia-a para um volume do Unity Catalog. Atualiza o volume_path para apontar para um volume existente para o qual tens WRITE VOLUME permissão.
import os
import subprocess
import shutil
build_dir = "/tmp/udf_build"
package_dir = f"{build_dir}/src/com/databricks/udf"
classes_dir = f"{build_dir}/classes"
os.makedirs(package_dir, exist_ok=True)
os.makedirs(classes_dir, exist_ok=True)
# The UDF handler: a public static method on a plain Java class.
# The doubled backslashes produce a single backslash in the Java source (\\s+).
udf_code = """package com.databricks.udf;
public class StringCleanUDF {
public static String clean(String input) {
if (input == null) return null;
return input.trim().replaceAll("\\\\s+", " ").toLowerCase();
}
}
"""
with open(f"{package_dir}/StringCleanUDF.java", "w") as f:
f.write(udf_code)
# Compile with JDK 17 to match Environment Version 4.
subprocess.run(
["javac", "--release", "17", "-d", classes_dir, f"{package_dir}/StringCleanUDF.java"],
check=True,
)
# Package the compiled class into a JAR.
jar_path = f"{build_dir}/string_clean_udf.jar"
subprocess.run(["jar", "cf", jar_path, "-C", classes_dir, "."], check=True)
# Copy the JAR to a Unity Catalog volume.
volume_path = "/Volumes/my_catalog/my_schema/udf_jars/string_clean_udf.jar"
os.makedirs(os.path.dirname(volume_path), exist_ok=True)
shutil.copy2(jar_path, volume_path)
print(f"JAR uploaded to: {volume_path}")
Depois de o JAR estar no volume, registar o UDF. Use LANGUAGE JAVA e defina o HANDLER para o método totalmente qualificado, como com.databricks.udf.StringCleanUDF.clean.
Registe o teu UDF no Catálogo Unity
Depois de compilar e carregar o seu JAR, use a CREATE FUNCTION instrução para registar o seu UDF no Unity Catalog.
Scala
CREATE OR REPLACE FUNCTION my_catalog.my_schema.add_one(x INT)
RETURNS INT
LANGUAGE SCALA
DETERMINISTIC
ENVIRONMENT (
java_dependencies = '["/Volumes/my_catalog/my_schema/udf_jars/my-udf-assembly-0.1.0-SNAPSHOT.jar"]',
environment_version = '4'
)
HANDLER 'com.example.MyUDF.addOne';
Java
CREATE OR REPLACE FUNCTION my_catalog.my_schema.add_one(x INT)
RETURNS INT
LANGUAGE JAVA
DETERMINISTIC
ENVIRONMENT (
java_dependencies = '["/Volumes/my_catalog/my_schema/udf_jars/my-udf-1.0-SNAPSHOT.jar"]',
environment_version = '4'
)
HANDLER 'com.example.MyUDF.addOne';
A CREATE FUNCTION instrução utiliza os seguintes parâmetros:
LANGUAGE: A linguagem da UDF.HANDLER: Caminho totalmente qualificado para o método, no formato'package.Object.method'(Scala) ou'package.ClassName.method'(Java).DETERMINISTIC: Declara que a função devolve sempre a mesma saída para a mesma entrada, permitindo a otimização da consulta.Note
Remova
DETERMINISTICse a sua função chama APIs externas ou tem qualquer outro comportamento não determinístico.ENVIRONMENT: Define o ambiente de execução para o UDF.-
java_dependencies: Um array JSON de caminhos de ficheiros JAR nos volumes do Unity Catalog. Este é o caminho do ficheiro que copiaste na etapa anterior. Utilize aspas simples à volta da matriz e aspas duplas à volta dos caminhos. -
environment_version: Deve ser'4'ou superior para UDFs de Scala e de Java. A versão 4 do ambiente especifica o Scala 2.13.16 e o JDK 17. Consulte as versões do ambiente sem servidor .
-
Chame a sua UDF em SQL e notebooks
Após o registo, pode chamar a UDF em consultas SQL, cadernos e vistas:
-- Simple select
SELECT my_catalog.my_schema.add_one(5) AS result;
-- With table data
SELECT
id,
price,
currency,
my_catalog.my_schema.convert_to_usd(price, currency) AS price_usd
FROM my_catalog.my_schema.transactions;
-- Filtering
SELECT *
FROM my_catalog.my_schema.products
WHERE my_catalog.my_schema.convert_to_usd(price, currency) > 100;
-- Aggregation
SELECT
category,
SUM(my_catalog.my_schema.convert_to_usd(price, currency)) AS total_usd
FROM my_catalog.my_schema.sales
GROUP BY category;
Governação e partilha
Use permissões do Unity Catalog para controlar quem pode executar o seu UDF e torná-lo detectável em toda a sua organização.
Conceder permissões
Use o Explorador de Catálogos ou SQL para conceder as permissões necessárias a outros utilizadores para executarem os seus UDFs.
Explorador de Catálogos
- Na barra lateral, clique no
Catálogo.
- Selecione o catálogo e depois selecione o esquema que contém a sua função.
- Clica no nome da função.
- No separador Permissões, clique em Conceder.
- Selecione as entidades às quais pretende conceder acesso e selecione a permissão
EXECUTE. - Clique em Confirmar.
SQL
Execute o comando seguinte num caderno ou no editor SQL do Databricks para conceder EXECUTE permissões a um utilizador ou grupo.
-- Grant to a specific user
GRANT EXECUTE ON FUNCTION my_catalog.my_schema.add_one TO `user@example.com`;
-- Grant to a group
GRANT EXECUTE ON FUNCTION my_catalog.my_schema.add_one TO `data-engineers`;
Revogar permissões
Use o Explorador de Catálogos ou SQL para revogar permissões a outros utilizadores.
Explorador de Catálogos
- Na barra lateral, clique no
Catálogo.
- Selecione o catálogo e depois selecione o esquema que contém a sua função.
- Clica no nome da função.
- No separador Permissões, selecione a caixa de seleção junto da entidade à qual pretende revogar o acesso. Clique Revogar.
- Na notificação, clique em Revoke.
SQL
Execute o seguinte comando num caderno ou no editor SQL do Databricks para revogar EXECUTE permissões a um utilizador ou grupo.
-- Revoke from specific user
REVOKE EXECUTE ON FUNCTION my_catalog.my_schema.add_one FROM `user@example.com`;
-- Revoke from a group
REVOKE EXECUTE ON FUNCTION my_catalog.my_schema.add_one FROM `data-engineers`;
Descubra as UDFs
Para encontrar os UDFs geridos no Unity Catalog, consulta a tabela information_schema.routines, substituindo os valores my_catalog e my_schema:
SELECT
routine_catalog,
routine_schema,
routine_name,
routine_definition,
created
FROM system.information_schema.routines
WHERE routine_catalog = 'my_catalog'
AND routine_schema = 'my_schema';
Atualize a sua UDF
Para atualizar um UDF existente do Catálogo Unity com novo código:
- Faça alterações ao seu código localmente.
- Reconstruir o JAR com um novo número de versão.
- Scala:
sbt clean assembly(por exemplo,my-udf-assembly-0.2.0-SNAPSHOT.jar) - Java:
mvn clean package(por exemplo,my-udf-2.0-SNAPSHOT.jar)
- Scala:
- Carregar o novo JAR no volume do Unity Catalog.
- Use
CREATE OR REPLACE FUNCTIONcom o mesmo nome de função para atualizar o UDF. Verifique que faz referência ao JAR mais recente no seu ficheirojava_dependencies.
O Azure Databricks usa o novo código na próxima invocação. Não precisas de reiniciar o teu cluster.
Otimização do desempenho
Latência de arranque a frio
A primeira chamada UDF numa sessão inicializa o sandbox isolado, o que adiciona latência. As chamadas subsequentes na mesma sessão são mais rápidas. Tenha isto em conta ao fazer benchmarks ou desenhar cargas de trabalho sensíveis à latência.
Armazenamento em cache de cálculos dispendiosos
Se o seu UDF realizar inicializações ou cálculos dispendiosos, armazene o resultado em cache para o calcular apenas uma vez.
Scala
Use um val campo no objeto Scala para armazenar em cache o resultado:
package example
object CachedUDF {
// Computed once and cached
val expensiveData: Map[String, Double] = {
// Load data from somewhere expensive
Map("key1" -> 1.0, "key2" -> 2.0)
}
def lookup(key: String): Double = {
expensiveData.getOrElse(key, 0.0)
}
}
Java
Use um static campo com um bloco inicializador estático para armazenar o resultado em cache:
package example;
import java.util.Map;
import java.util.HashMap;
public class CachedUDF {
// Computed once and cached
private static Map<String, Double> expensiveData;
static {
// Load data from somewhere expensive
expensiveData = new HashMap<>();
expensiveData.put("key1", 1.0);
expensiveData.put("key2", 2.0);
}
public static double lookup(String key) {
return expensiveData.getOrDefault(key, 0.0);
}
}
Use DETERMINISTIC quando apropriado
Marque o seu UDF como DETERMINISTIC se ele produzisse sempre a mesma saída para a mesma entrada. Isto permite ao otimizador de consultas armazenar em cache os resultados e melhorar o desempenho.
Limitations
- Apenas UDFs escalares são suportados. Funções agregadas definidas pelo utilizador (UDAFs) e funções de tabela definidas pelo utilizador (UDTFs) não são suportadas.
- Os UDFs funcionam num sandbox isolado sem nenhuma sessão Spark ativa. As APIs do Spark (
SparkSession,SparkContext,spark.sql(...), DataFrame e operações de conjunto de dados) não estão disponíveis. - Os UDFs não podem depender dos pacotes core do Spark.
- Os UDFs não têm acesso a ficheiros de workspace ou volumes do Unity Catalog em tempo de execução.
Melhores práticas
A Databricks recomenda as seguintes práticas:
- Atribua versões aos seus ficheiros JAR. Por exemplo,
my-udf-0.1.0.jar,my-udf-0.2.0.jar. - Valide os mapeamentos de tipos SQL antes da implementação. Ver Mapeamentos linguísticos.
- Conceda
READ VOLUMEeEXECUTEconceda permissões apenas a utilizadores que precisam de executar o UDF. Use a propriedade de grupo para UDFs partilhados entre equipas.
Testar UDFs localmente
Teste o seu UDF com testes unitários antes de o implementar em produção.
Scala
Para testar src/main/scala/example/MyUDF.scala, crie um ficheiro de teste em src/test/scala/example/MyUDFTest.scala:
package example
import org.scalatest.funsuite.AnyFunSuite
class MyUDFTest extends AnyFunSuite {
test("addOne should add 1 to input") {
assert(MyUDF.addOne(5) == 6)
}
test("addOne should handle negative numbers") {
assert(MyUDF.addOne(-1) == 0)
}
}
Adicione a dependência do teste a build.sbt:
libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.15" % Test
Para realizar os testes:
sbt test
Java
Para testar src/main/java/com/example/MyUDF.java, crie um ficheiro de teste em src/test/java/com/example/MyUDFTest.java:
package com.example;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class MyUDFTest {
@Test
public void testAddOne() {
assertEquals(6, MyUDF.addOne(5));
}
@Test
public void testAddOneWithNegativeNumbers() {
assertEquals(0, MyUDF.addOne(-1));
}
}
Adicione a dependência do JUnit à secção <dependencies> do seu pom.xml:
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.0</version>
<scope>test</scope>
</dependency>
Para realizar os testes:
mvn test