Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Asynchrone Verarbeitung mit
Important
Die asynchrone Verarbeitung für die zeilenbasierte transformWithState API von Python befindet sich in der Beta-Phase. Siehe Azure Databricks Preview-Versionen.
Asynchrone Verarbeitung ist ab Databricks Runtime 19 oder auf serverlosem Compute mit Umgebungsversion 6 oder höher verfügbar.
Python transformWithState unterstützt asynchrone Verarbeitung auf Basis von asyncio. Durch das gleichzeitige Ausführen von Zustandsoperationen und Benutzerlogik über Gruppierungsschlüssel hinweg und das Batching der Kommunikation zwischen Prozessen hat asynchrone Verarbeitung einen höheren Durchsatz als synchrone Verarbeitung mit nur geringfügigen Codeänderungen. Dieser Durchsatzgewinn erfordert keine asynchronen Drittanbieter-Bibliotheken. Fortgeschrittene Nutzer können ihre Anwendungen weiter mit asynchronen Programmiermustern und asynk-fähigen Bibliotheken optimieren.
Um asynchrone Verarbeitung zu verwenden, implementieren Sie AsyncStatefulProcessor anstelle des synchronen StatefulProcessor. Die AsyncStatefulProcessor API spiegelt die synchrone StatefulProcessor API wider, sodass die meisten Anwendungen nur geringe Änderungen benötigen, um die asynchrone API zu nutzen. Siehe Implementieren eines AsyncStatefulProcessor
Für die synchrone transformWithState API und Kernkonzepte siehe Erstellen einer benutzerdefinierten zustandsbehafteten Anwendung mit transformWithState.
Note
Asynchrone Verarbeitung ist nur für die zeilenbasierte transformWithState API von Python verfügbar. Wird weder für transformWithStateInPandas noch für die Scala transformWithState API unterstützt.
Implementiere ein AsyncStatefulProcessor
Um eine synchrone StatefulProcessor in eine AsyncStatefulProcessor umzuwandeln, nehmen Sie folgende Änderungen vor:
- Definiere die API-Methoden (
init,close,handleInputRows,handleExpiredTimer, undhandleInitialState) mit demasync defSchlüsselwort. - Lesen und aktualisieren Sie Zustands- und Timerwerte mit
await, oder verwenden Sie sie mit der Python-Bibliothekasyncio. Dies gilt für Zustandsoperationen wievalueState.get()und für Timer-Operationen wieregisterTimer. Das Erstellen von Zustandsobjekten, wiehandle.getValueState, bleibt synchron.
Die folgenden Überlegungen gelten für asynchrone Verarbeitung:
- Wenn Ihre Anwendung Daten in Mitgliedsvariablen oder in externen Systemen speichert, empfiehlt Databricks, die Logik so umzuschreiben, dass sie für gleichzeitige Ausführung sicher ist. Da
handleInputRowsundhandleExpiredTimergleichzeitig über Gruppierungsschlüssel hinweg ausgeführt werden können, dürfen interleaved-Läufe die gemeinsamen Daten nicht beschädigen. Die meisten Anwendungen erfüllen diese Anforderung bereits. - Databricks empfiehlt, Fehler bei Zustandsoperationen nicht abzufangen oder zu unterdrücken. Apache Spark behandelt diese Fehler für Sie. Wenn eine Zustandsoperation fehlschlägt, markiert Apache Spark die Aufgabe als fehlgeschlagen und führt sie erneut aus.
- In einem
AsyncStatefulProcessorstatusbehafteten Vorgang werden Betriebsfehler für Sie verwaltet und tauchen nie in Ihrem Code auf. - In einem synchronen
StatefulProcessorZustand werden Fehler bei Statusvorgängen in Ihrem Code ausgelöst, aber deren Unterdrückung kann die Datenkorrektheit beeinträchtigen.
- In einem
Beispiel: Zeilen für jeden Gruppierungsschlüssel zählen
Das folgende Beispiel definiert ein AsyncCountProcessor, der die Anzahl der Zeilen für jeden Gruppierungsschlüssel zählt. Die Variable value_schema definiert das Schema von der Tabelle ValueState, die die laufende Anzahl speichert. Im Vergleich zu einer synchronen StatefulProcessorMethode sind die Änderungen das async def Schlüsselwort bei jeder Methode sowie await bei den Zustandslese- und Aktualisierungsoperationen. Der Aufruf von getValueState in init bleibt synchron. Definieren Sie den Prozessor wie im folgenden Code:
from pyspark.sql import Row
from pyspark.sql.streaming import AsyncStatefulProcessor, AsyncStatefulProcessorHandle
from pyspark.sql.types import StructType, StructField, LongType
value_schema = StructType([StructField("count", LongType(), True)])
class AsyncCountProcessor(AsyncStatefulProcessor):
async def init(self, handle: AsyncStatefulProcessorHandle) -> None:
self.count = handle.getValueState("count", value_schema)
async def handleInputRows(self, key, rows, timerValues):
total = (await self.count.get() or (0,))[0]
for _ in rows:
total += 1
await self.count.update((total,))
yield Row(action=key[0], count=total)
async def close(self) -> None:
pass
Führe eine Abfrage mit einem asynchronen Prozessor aus
Um eine Abfrage mit einem asynchronen Prozessor auszuführen, übergebe dein AsyncStatefulProcessor an transformWithState. Die Abfrage verwendet dieselbe Syntax wie der synchrone Pfad. Die asynchronen und synchronen APIs teilen dasselbe Zustandsformat, sodass man eine bestehende Abfrage zwischen einer AsyncStatefulProcessor und einer synchronen StatefulProcessor wechseln kann und dabei denselben Checkpoint wiederverwenden.
Beispiel: Zähle Ereignisse im events Beispieldatensatz
Das folgende Beispiel führt AsyncCountProcessor für den events-Beispieldatensatz aus. Jeder Datensatz hat ein Feld time (Epochensekunden) und ein action Feld mit dem Wert Open oder Close. Die Abfrage gruppiert nach action und zählt die Ereignisse für jeden Aktionstyp. Weitere Beispieldatensätze finden Sie unter Beispieldatensätze.
Die Variable input_schema definiert das Schema der Quelldatensätze, und die Variable output_schema definiert das Schema der Zeilen, die der Prozessor ausgibt. Um den Beispieldatensatz als Strom auszulesen, definieren Sie beide Schemata und starten dann die Abfrage wie im folgenden Code:
from pyspark.sql.types import StructType, StructField, StringType, LongType
input_schema = StructType([
StructField("time", LongType(), True),
StructField("action", StringType(), True),
])
output_schema = StructType([
StructField("action", StringType(), True),
StructField("count", LongType(), True),
])
events = (
spark.readStream.schema(input_schema)
.option("maxFilesPerTrigger", 10)
.json("/databricks-datasets/structured-streaming/events")
)
q = (
events.groupBy("action")
.transformWithState(
statefulProcessor=AsyncCountProcessor(),
outputStructType=output_schema,
outputMode="Update",
timeMode="None",
)
.writeStream.format("memory")
.queryName("async_counts")
.trigger(availableNow=True)
.start()
)
q.awaitTermination()
Nachdem die Abfrage abgeschlossen ist, sehen Sie sich die laufende Anzahl für jeden Aktionstyp wie im folgenden Code an:
display(spark.sql("SELECT action, MAX(count) AS count FROM async_counts GROUP BY action ORDER BY action"))
Asynchrone Zustands- und Timeroperationen
In einem AsyncStatefulProcessor, sind Variablen- und Timeroperationen, die Werte lesen oder schreiben, asynchron. Die meisten dieser Operationen liefern ein einziges Ergebnis, das Sie mit awaitabrufen. Operationen, die eine Sammlung zurückgeben, geben stattdessen einen asynchronen Iterator zurück, den Sie mit async forkonsumieren. Eine Einführung in async/await und asynchrone Iteratoren in Python finden Sie in der Python asyncio-Dokumentation.
Die folgende Tabelle listet Operationen auf, die ein einzelnes Ergebnis zurückgeben, das Sie mit awaitabrufen können:
| Class | Operationen, die await verwenden |
|---|---|
AsyncValueState |
exists, get, update, clear |
AsyncMapState |
exists, getValue, containsKey, updateValue, , removeKey, clear |
AsyncListState |
exists, put, appendValue, appendList, , clear |
AsyncStatefulProcessorHandle |
registerTimer, deleteTimer |
Die folgende Tabelle listet Operationen auf, die einen asynchronen Iterator zurückgeben, den Sie mit async for abrufen können:
| Class | Operationen, die async for verwenden |
|---|---|
AsyncMapState |
iterator, keys, values |
AsyncListState |
get |
AsyncStatefulProcessorHandle |
listTimers |
Beispiel: async for
Zum Beispiel, um die Werte in einem AsyncListState zu lesen, iterieren Sie mit async for wie im folgenden Code:
total = 0
async for value in self.items.get():
total += value[0]
Die Methoden, die Zustandsobjekte erzeugen und Zustandsvariablen löschen, bleiben synchron: getValueState, getMapState, getListState und deleteIfExists.
Für eine Beschreibung jedes Zustandstyps siehe Benutzerdefinierte Zustandstypen.
Optimieren Sie mit asynchronen Programmierungsmustern
Asynchrone Verarbeitung ist nützlich, wenn Ihre Logik auf externe Operationen wie Netzwerkanfragen wartet. Anstatt auf jede Anfrage nacheinander zu warten, sollten Sie asyncio verwenden, um die Anfragen gleichzeitig auszuführen und die Leerlaufzeit zu reduzieren.
Beispiel: gleichzeitige Anfragen ausführen mit asyncio.gather
Das folgende Beispiel verwendet asyncio.gather , um alle HTTP-Anfragen pro Zeile gleichzeitig auszulösen und auf deren Abschluss zu warten, dann speichert es den maximalen Score im Zustand. Definieren Sie den Prozessor wie im folgenden Code:
import asyncio
import aiohttp
from pyspark.sql import Row
from pyspark.sql.streaming import AsyncStatefulProcessor
class HttpScoreRowGatherProcessor(AsyncStatefulProcessor):
async def init(self, handle):
self._score_state = handle.getValueState("last_score", "score double")
self._session = aiohttp.ClientSession()
async def _fetch_score(self, row) -> float:
async with self._session.get(
f"https://api.example.com/score/{row.event_id}"
) as resp:
return (await resp.json())["score"]
async def handleInputRows(self, key, rows, timerValues):
user_id = key[0]
scores = await asyncio.gather(*[self._fetch_score(row) for row in rows])
max_score = max(scores)
await self._score_state.update((max_score,))
yield Row(user_id=user_id, score=max_score)
async def close(self):
await self._session.close()