Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Datové toky v pipeline Lakeflow přenášejí data do streamovací tabulky nebo materializovaného pohledu. Následující příklady ukazují, jak definovat výchozí toky, definovat tok odděleně od cíle, zapisovat do streamované tabulky z několika témat Kafka, spustit jednorázové obnovení a nahradit UNION dotazy zpracováním přidávacího toku.
Přehled toků najdete v tématu Přírůstkové načítání a zpracování dat pomocí toků kanálu Lakeflow.
Příklad: Vytvoření výchozího toku
Při vytváření kanálu obvykle definujete tabulku nebo zobrazení spolu s dotazem, který ho podporuje. Například tento dotaz vytvoří streamovací tabulku pojmenovanou customers_silver čtením z customers_bronze. Streamovací tabulka a její výchozí tok se vytvoří společně v jednom kroku.
SQL
CREATE OR REFRESH STREAMING TABLE customers_silver
AS SELECT * FROM STREAM(customers_bronze)
Python
from pyspark import pipelines as dp
@dp.table()
def customers_silver():
return spark.readStream.table("customers_bronze")
Výchozí tok pro streamovací tabulku je přidávací tok, který přidává nové řádky s každou aktualizací a má stejný název jako cíl. Toto je nejběžnější způsob, jak používat kanály – vytvoření toku a jeho cíle v jednom kroku – a můžete ho použít k ingestování nebo transformaci dat. Další informace o konceptech toků najdete v tématu Přírůstkové načítání a zpracování dat pomocí toků kanálu Lakeflow.
Příklad: Definování toku odděleně od cíle
Můžete také vytvořit tok pro tabulku, kterou jste definovali samostatně. Výsledek je stejný jako vytvoření výchozího toku, včetně použití stejného názvu tabulky streamování a toku:
Python
from pyspark import pipelines as dp
# create streaming table
dp.create_streaming_table("customers_silver")
# add a flow
@dp.append_flow(
target = "customers_silver")
def customer_silver():
return spark.readStream.table("customers_bronze")
SQL
-- create a streaming table
CREATE OR REFRESH STREAMING TABLE customers_silver;
-- add a flow
CREATE FLOW customers_silver
AS INSERT INTO customers_silver BY NAME
SELECT * FROM STREAM(customers_bronze);
Definování toku odděleně od cíle umožňuje vytvořit více toků, které připojují data ke stejnému cíli. Použijte dekorátor @dp.append_flow v rozhraní jazyka Python nebo klauzuli CREATE FLOW...INSERT INTO v rozhraní SQL k přidání toků pro úlohy, jako jsou následující:
- Přidejte streamované zdroje, které připojují data k existující streamované tabulce bez nutnosti úplné aktualizace. Můžete mít například tabulku, která kombinuje regionální data z každé oblasti, ve které pracujete. Při nasazení nových oblastí můžete do tabulky přidat nová data oblasti, aniž byste provedli úplnou aktualizaci. Viz příklad: Zápis do streamované tabulky z několika témat Kafka.
- Aktualizujte streamovací tabulku přidáním chybějících historických dat (obnovení). Pomocí syntaxe
INSERT INTO ONCEmůžete vytvořit historické doplnění dat, které se spustí pouze jednou. Viz příklad: Spuštění jednorázového obnovení dat a obnovení historických dat pomocí kanálů. - Kombinovat data z více zdrojů a zapisovat do jedné streamovací tabulky místo použití
UNIONklauzule v dotazu. Pomocí zpracování přidávacího toku místoUNIONmůžete aktualizovat cílovou tabulku přírůstkově bez spuštění úplné aktualizace. Viz Příklad: MístoUNIONpoužijte zpracování toku přidáváním.
V případě dotazů v Pythonu vytvořte cílovou tabulku pomocí funkce create_streaming_table( ).
Important
- Pokud potřebujete definovat omezení kvality dat s očekáváními, definujte očekávání v cílové tabulce jako součást
create_streaming_table()funkce nebo v existující definici tabulky. V definici@append_flownelze definovat očekávání. - Toky jsou identifikovány názvem toku a tento název se používá k identifikaci kontrolních bodů streamování. Použití názvu toku k identifikaci kontrolního bodu znamená následující:
- Pokud se existující tok v kanálu přejmenuje, kontrolní bod se nepřenese a přejmenovaný tok je v podstatě zcela novým tokem.
- Název toku v kanálu nemůžete znovu použít, protože stávající kontrolní bod neodpovídá nové definici toku.
Příklad: Zápis do streamované tabulky z několika témat v Kafka
Následující příklad vytvoří tabulku streamování s názvem kafka_target a zapíše do této tabulky streamování ze dvou témat Kafka.
Python
from pyspark import pipelines as dp
dp.create_streaming_table("kafka_target")
# Kafka stream from multiple topics
@dp.append_flow(target = "kafka_target")
def topic1():
return (
spark.readStream
.format("kafka")
.option("kafka.bootstrap.servers", "host1:port1,...")
.option("subscribe", "topic1")
.load()
)
@dp.append_flow(target = "kafka_target")
def topic2():
return (
spark.readStream
.format("kafka")
.option("kafka.bootstrap.servers", "host1:port1,...")
.option("subscribe", "topic2")
.load()
)
SQL
CREATE OR REFRESH STREAMING TABLE kafka_target;
CREATE FLOW
topic1
AS INSERT INTO
kafka_target BY NAME
SELECT * FROM
read_kafka(bootstrapServers => 'host1:port1,...', subscribe => 'topic1');
CREATE FLOW
topic2
AS INSERT INTO
kafka_target BY NAME
SELECT * FROM
read_kafka(bootstrapServers => 'host1:port1,...', subscribe => 'topic2');
Další informace o read_kafka() tabulkové funkci použité v dotazech SQL najdete v read_kafka v referenční dokumentaci jazyka SQL.
V Pythonu můžete programově vytvořit více toků, které cílí na jednu tabulku. Následující příklad ukazuje tento vzor pro seznam témat Kafka.
Poznámka:
Tento model má stejné požadavky jako použití for smyčky k vytváření tabulek. Funkci definující tok musíte explicitně předat hodnotu typu Python. Viz Vytvoření tabulek ve smyčcefor.
from pyspark import pipelines as dp
dp.create_streaming_table("kafka_target")
topic_list = ["topic1", "topic2", "topic3"]
for topic_name in topic_list:
@dp.append_flow(target = "kafka_target", name=f"{topic_name}_flow")
def topic_flow(topic=topic_name):
return (
spark.readStream
.format("kafka")
.option("kafka.bootstrap.servers", "host1:port1,...")
.option("subscribe", topic)
.load()
)
Příklad: Spuštění jednorázového obnovení dat
Pokud chcete spustit dotaz pro připojení dat k existující streamované tabulce, použijte append_flow.
Po připojení sady existujících dat máte několik možností:
- Pokud chcete, aby dotaz připojil nová data, pokud dorazí do adresáře backfill, ponechte dotaz na místě.
- Pokud chcete, aby to bylo jednorázové obnovení a nikdy znovu nespustíte, odeberte dotaz po spuštění kanálu jednou.
- Pokud chcete, aby byl dotaz spuštěn jednou a znovu spuštěn jen v případech, kdy se data zcela obnovují, nastavte parametr
oncena tok přidáníTrue. V SQL použijteINSERT INTO ONCE.
Následující příklady spustí dotaz, který připojí historická data do streamované tabulky:
Python
from pyspark import pipelines as dp
@dp.table()
def csv_target():
return spark.readStream
.format("cloudFiles")
.option("cloudFiles.format","csv")
.load("path/to/sourceDir")
@dp.append_flow(
target = "csv_target",
once = True)
def backfill():
return spark.read
.format("cloudFiles")
.option("cloudFiles.format","csv")
.load("path/to/backfill/data/dir")
SQL
CREATE OR REFRESH STREAMING TABLE csv_target
AS SELECT * FROM
read_files(
"path/to/sourceDir",
"csv"
);
CREATE FLOW
backfill
AS INSERT INTO ONCE
csv_target BY NAME
SELECT * FROM
read_files(
"path/to/backfill/data/dir",
"csv"
);
Podrobnější příklad najdete v tématu Doplnění historických dat pomocí pipeline.
Příklad: Použijte zpracování toku přidání místo UNION
Místo použití dotazu s UNION klauzulí můžete pomocí přidávacích dotazů toku kombinovat více zdrojů a zapisovat do jedné streamovací tabulky. Použití dotazů přidávacího toku místo UNION toho umožňuje připojit se k streamované tabulce z více zdrojů bez spuštění úplné aktualizace.
Následující příklad Pythonu obsahuje dotaz, který kombinuje více zdrojů dat s klauzulí UNION :
@dp.create_table(name="raw_orders")
def unioned_raw_orders():
raw_orders_us = (
spark.readStream
.format("cloudFiles")
.option("cloudFiles.format", "csv")
.load("/path/to/orders/us")
)
raw_orders_eu = (
spark.readStream
.format("cloudFiles")
.option("cloudFiles.format", "csv")
.load("/path/to/orders/eu")
)
return raw_orders_us.union(raw_orders_eu)
Následující příklady nahrazují UNION dotaz přidávacími dotazy toku:
Python
dp.create_streaming_table("raw_orders")
@dp.append_flow(target="raw_orders")
def raw_orders_us():
return spark.readStream
.format("cloudFiles")
.option("cloudFiles.format", "csv")
.load("/path/to/orders/us")
@dp.append_flow(target="raw_orders")
def raw_orders_eu():
return spark.readStream
.format("cloudFiles")
.option("cloudFiles.format", "csv")
.load("/path/to/orders/eu")
# Additional flows can be added without the full refresh that a UNION query would require:
@dp.append_flow(target="raw_orders")
def raw_orders_apac():
return spark.readStream
.format("cloudFiles")
.option("cloudFiles.format", "csv")
.load("/path/to/orders/apac")
SQL
CREATE OR REFRESH STREAMING TABLE raw_orders;
CREATE FLOW
raw_orders_us
AS INSERT INTO
raw_orders BY NAME
SELECT * FROM
STREAM read_files(
"/path/to/orders/us",
format => "csv"
);
CREATE FLOW
raw_orders_eu
AS INSERT INTO
raw_orders BY NAME
SELECT * FROM
STREAM read_files(
"/path/to/orders/eu",
format => "csv"
);
-- Additional flows can be added without the full refresh that a UNION query would require:
CREATE FLOW
raw_orders_apac
AS INSERT INTO
raw_orders BY NAME
SELECT * FROM
STREAM read_files(
"/path/to/orders/apac",
format => "csv"
);
Příklad: transformWithState slouží k monitorování taktovacích signálů snímače.
Následující příklad ukazuje stavový procesor, který čte z Kafky a ověřuje, že senzory pravidelně vysílají tepy. Pokud není signál srdečního tepu přijat během 5 minut, procesor vygeneruje záznam do cílové tabulky Delta k analýze.
Další informace o vytváření vlastních stavových aplikací naleznete v tématu Vytvoření vlastní stavové aplikace.
Poznámka:
RocksDB je výchozí zprostředkovatel stavu počínaje modulem Databricks Runtime 17.2. Pokud dotaz selže kvůli nepodporované výjimce zprostředkovatele, přidejte následující konfigurace kanálu, proveďte úplnou aktualizaci nebo resetování kontrolního bodu a spusťte kanál znovu:
"configuration": {
"spark.sql.streaming.stateStore.providerClass": "com.databricks.sql.streaming.state.RocksDBStateStoreProvider",
"spark.sql.streaming.stateStore.rocksdb.changelogCheckpointing.enabled": "true"
}
from typing import Iterator
import pandas as pd
from pyspark import pipelines as dp
from pyspark.sql.functions import col, from_json
from pyspark.sql.streaming import StatefulProcessor, StatefulProcessorHandle
from pyspark.sql.types import StructType, StructField, LongType, StringType, TimestampType
KAFKA_TOPIC = "<your-kafka-topic>"
output_schema = StructType([
StructField("sensor_id", LongType(), False),
StructField("sensor_type", StringType(), False),
StructField("last_heartbeat_time", TimestampType(), False)])
class SensorHeartbeatProcessor(StatefulProcessor):
def init(self, handle: StatefulProcessorHandle) -> None:
# Define state schema to store sensor information (sensor_id is the grouping key)
state_schema = StructType([
StructField("sensor_type", StringType(), False),
StructField("last_heartbeat_time", TimestampType(), False)])
self.sensor_state = handle.getValueState("sensorState", state_schema)
# State variable to track the previously registered timer
timer_schema = StructType([StructField("timer_ts", LongType(), False)])
self.timer_state = handle.getValueState("timerState", timer_schema)
self.handle = handle
def handleInputRows(self, key, rows, timerValues) -> Iterator[pd.DataFrame]:
# Process one row from input and update state
pdf = next(rows)
row = pdf.iloc[0]
# Store or update the sensor information in state using current timestamp
current_time = pd.Timestamp(timerValues.getCurrentProcessingTimeInMs(), unit='ms')
self.sensor_state.update((
row["sensor_type"],
current_time
))
# Delete old timer if already registered
if self.timer_state.exists():
old_timer = self.timer_state.get()[0]
self.handle.deleteTimer(old_timer)
# Register a timer for 5 minutes from current processing time
expiry_time = timerValues.getCurrentProcessingTimeInMs() + (5 * 60 * 1000)
self.handle.registerTimer(expiry_time)
# Store the new timer timestamp in state
self.timer_state.update((expiry_time,))
# No output on input processing, output only on timer expiry
return iter([])
def handleExpiredTimer(self, key, timerValues, expiredTimerInfo) -> Iterator[pd.DataFrame]:
# Emit output row based on state store
if self.sensor_state.exists():
state = self.sensor_state.get()
output = pd.DataFrame({
"sensor_id": [key[0]], # Use grouping key as sensor_id
"sensor_type": [state[0]],
"last_heartbeat_time": [state[1]]
})
# Remove the entry for the sensor from the state store
self.sensor_state.clear()
# Remove the timer state entry
self.timer_state.clear()
yield output
def close(self) -> None:
pass
dp.create_streaming_table("sensorAlerts")
# Define the schema for the Kafka message value
sensor_schema = StructType([
StructField("sensor_id", LongType(), False),
StructField("sensor_type", StringType(), False),
StructField("sensor_value", LongType(), False)])
@dp.append_flow(target = "sensorAlerts")
def kafka_delta_flow():
return (
spark.readStream
.format("kafka")
.option("subscribe", KAFKA_TOPIC)
.option("startingOffsets", "earliest")
.load()
.select(from_json(col("value").cast("string"), sensor_schema).alias("data"), col("timestamp"))
.select("data.*", "timestamp")
.withWatermark('timestamp', '1 hour')
.groupBy(col("sensor_id"))
.transformWithStateInPandas(
statefulProcessor = SensorHeartbeatProcessor(),
outputStructType = output_schema,
outputMode = 'update',
timeMode = 'ProcessingTime'))