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.
Beim Entwickeln für Azure Functions mit TypeScript und Node.jsmusst du verstehen, wie deine Funktionen funktionieren und wie diese Leistung die Skalierung deiner Funktions-App beeinflusst. Dieses Verständnis ist besonders wichtig, wenn man leistungsstarke Apps entwirft. Die wichtigsten Faktoren beim Entwerfen, Schreiben und Konfigurieren Ihrer Funktions-Apps sind horizontale Skalierung, Durchsatzleistungskonfigurationen und Node.js-spezifische Optimierungen.
Horizontale Skalierung
Standardmäßig überwacht Azure Functions die Last ihrer Anwendung automatisch und erstellt bei Bedarf weitere Hostinstanzen für Node.js. Azure Functions verwendet eingebaute Schwellenwerte für verschiedene Triggertypen, um zu entscheiden, wann Instanzen hinzugefügt werden, wie das Alter der Nachrichten und die Warteschlangengröße für QueueTrigger. Du kannst diese Schwellenwerte nicht konfigurieren. Weitere Informationen finden Sie unter ereignisgesteuerte Skalierung in Azure Functions.
Um die Skalierung Ihrer Node.js-Funktions-App zu verbessern, folgen Sie diesem Ansatz in folgender Reihenfolge:
- Beginne mit den Standardeinstellungen und optimiere zuerst deinen Code mit asynchronen I/O-Mustern.
- Skalieren Sie hoch (fügen Sie mehr Instanzen hinzu), bevor Sie die Anzahl der Workerprozesse erhöhen.
- Erhöhen Sie
FUNCTIONS_WORKER_PROCESS_COUNTerst, nachdem Lasttests gezeigt haben, dass die CPU auf jeder Instanz ausgelastet ist. - Bei I/O-gebundenen Workloads sollten Sie die Anzahl der Arbeiter konservativ halten und sich darauf konzentrieren, Blockanrufe und Abhängigkeitslatenzen zu reduzieren.
- Testen Sie nach jeder Änderung erneut und überwachen Sie Durchsatz, Latenz und Fehlerraten, um Verbesserungen zu validieren.
Verbessern der Durchsatzleistung
Die Standardkonfigurationen sind für die meisten Azure Functions-Anwendungen geeignet. Sie können jedoch die Leistung des Durchsatzes Ihrer Anwendungen verbessern, indem Sie Konfigurationen basierend auf Ihrem Workloadprofil verwenden. Daher besteht der erste Schritt darin, zu verstehen, welcher Typ von Workload ausgeführt wird.
| Workload-Typ | Eigenschaften der Funktions-App | Beispiele |
|---|---|---|
| E/A-gebunden | • Die App muss viele gleichzeitige Aufrufe verarbeiten. • Die App verarbeitet eine große Anzahl von E/A-Ereignissen, z. B. Netzwerkaufrufe und Lese-/Schreibzugriff auf Datenträger. |
• Web-APIs • Datenbankoperationen • Dateiverarbeitung |
| CPU-gebunden | • Die App führt langlaufende Berechnungen durch, wie zum Beispiel Bildverarbeitung. • Die App führt Datentransformationen oder komplexe Berechnungen durch. |
• Datenverarbeitung • Inhaltstransformation • Algorithmusausführung |
Da Funktionsworkloads in der Praxis üblicherweise eine Mischung aus E/A- und CPU-gebundenen Workloads sind, sollten Sie das Profil der App unter realistischen Produktionslasten erstellen. Die folgenden Abschnitte behandeln spezifische Optimierungen:
- Async/await-Muster
- Mehrere Sprachworkerprozesse
- Ereignisschleifenoptimierung
- Speicherverwaltung
- Codeoptimierung
Async/Wait-Muster strukturieren
Node.js basiert auf einem ereignisgesteuerten, nicht blockierenden I/O-Modell, das es für I/O-gebundene Operationen leicht und effizient macht. Für Anwendungen, die eine große Anzahl von I/O-Ereignissen verarbeiten oder I/O-gebunden sind, kann man die Leistung durch die Verwendung geeigneter asynchroner/abwartender Muster erheblich verbessern:
-
Verwendung
Promise.all()für parallele Operationen: Wenn du mehrere unabhängige asynchrone Operationen hast, führe sie parallel statt nacheinander aus. - Vermeiden Sie es, die Ereignisschleife zu blockieren: Verwenden Sie keine synchronen Operationen in asynchronen Funktionen.
- Nutzen Sie Streaming für große Daten: Verarbeiten Sie große Datensätze mit Streams, anstatt alles in den Speicher zu laden.
- Implementiere die richtige Fehlerbehandlung: Benutze Try/Catch-Blöcke und behandle Promise-Ablehnungen.
Das folgende Beispiel demonstriert parallele asynchrone Operationen mit Promise.all():
const { app } = require('@azure/functions');
app.http('optimizedHttpTrigger', {
methods: ['GET', 'POST'],
handler: async (request, context) => {
// Good: Using async/await for HTTP calls
const response = await fetch('https://api.example.com/data');
const data = await response.json();
// Good: Parallel async operations
const [user, orders] = await Promise.all([
fetchUser(data.userId),
fetchOrders(data.userId)
]);
return {
jsonBody: { user, orders }
};
}
});
async function fetchUser(userId) {
const response = await fetch(`https://api.example.com/users/${userId}`);
return await response.json();
}
async function fetchOrders(userId) {
const response = await fetch(`https://api.example.com/orders?userId=${userId}`);
return await response.json();
}
module.exports = async function (context, req) {
// Good: Using async/await for HTTP calls
const response = await fetch('https://api.example.com/data');
const data = await response.json();
// Good: Parallel async operations
const [user, orders] = await Promise.all([
fetchUser(data.userId),
fetchOrders(data.userId)
]);
context.res = {
body: { user, orders }
};
};
async function fetchUser(userId) {
const response = await fetch(`https://api.example.com/users/${userId}`);
return await response.json();
}
async function fetchOrders(userId) {
const response = await fetch(`https://api.example.com/orders?userId=${userId}`);
return await response.json();
}
Verwenden von Arbeitsprozessen in mehreren Sprachen
Standardmäßig verfügt jede Funktionen-Hostinstanz über einen einzelnen Sprach-Arbeitsprozess. Sie können die Anzahl der Arbeitsprozesse pro Host (bis zu 10) mithilfe der FUNCTIONS_WORKER_PROCESS_COUNT Anwendungseinstellung erhöhen. Azure Functions versucht dann, gleichzeitige Funktionsaufrufe über diese Mitarbeiter hinweg gleichmäßig zu verteilen.
Warning
Verwenden Sie die FUNCTIONS_WORKER_PROCESS_COUNT-Einstellung mit Bedacht. Mehrere Prozesse, die in derselben Instanz ausgeführt werden, können zu unvorhersehbarem Verhalten führen und die Ladezeiten von Funktionen erhöhen. Wenn du diese Einstellung nutzt, kann das Ausführen von einer Paketdatei diese Nachteile ausgleichen.
Für CPU-gebundene Anwendungen stellen Sie die Anzahl der Spracharbeiter so ein, dass sie gleich oder höher ist als die Anzahl der verfügbaren Kerne pro Funktionsapp. Weitere Informationen finden Sie unter "Verfügbare Instanz-SKUs".
E/A-gebundene Apps können eventuell auch davon profitieren, eine höhere Anzahl von Workerprozessen als die der verfügbaren Kerne festzulegen. Denken Sie daran, dass eine zu hohe Anzahl von Workern die Gesamtleistung aufgrund der höheren Anzahl der erforderlichen Kontextwechsel beeinträchtigen kann.
Ereignisschleifenoptimierung
Node.js verwendet eine Einzelthread-Ereignisschleife zur Bearbeitung von I/O-Operationen. Die Optimierung, wie Ihr Code mit der Ereignisschleife interagiert, ist entscheidend für die Leistung.
Event-Loop freihalten
Synchrone Schleifen, die schwere Berechnungen ausführen, blockieren die Ereignisschleife und verhindern, dass andere Aufrufe verarbeitet werden. Stattdessen werden große Datensätze in Chargen aufgeteilt und die Kontrolle zwischen den Chargen mit setImmediateerzielt:
async function processDataNonBlocking(data: any[]) {
const batchSize = 100;
for (let i = 0; i < data.length; i += batchSize) {
const batch = data.slice(i, i + batchSize);
await Promise.all(
batch.map(item => processItem(item))
);
// Allow event loop to process other tasks
await new Promise(resolve => setImmediate(resolve));
}
}
Überwachen Sie die Verzögerung der Ereignisschleife
Verwenden Sie folgendes Muster, um zu messen, wie lange Aufgaben in der Ereignisschleifen-Warteschlange warten:
import { performance } from 'perf_hooks';
function measureEventLoopDelay(context: any) {
const start = performance.now();
setImmediate(() => {
const delay = performance.now() - start;
context.log(`Event loop delay: ${delay}ms`);
});
}
Speicherverwaltung
Effiziente Speichernutzung ist entscheidend für die Leistung von Azure Functions, insbesondere in Bezug auf Cold Starts und Skalierung. Befolgen Sie diese allgemeinen Richtlinien bei der Optimierung Ihres Gedächtnisverbrauchs:
- Globale Variablen minimieren: Schwere Objekte im globalen Umfang beeinflussen die Kaltstartzeiten.
- Verwenden Sie Connection Pooling: Verwenden Sie Datenbankverbindungen und HTTP-Clients wieder.
- Ressourcen aufräumen: Verbindungen richtig schließen und Timer freimachen.
import { app, InvocationContext, Timer } from '@azure/functions';
// Good: Connection pooling
class DatabaseManager {
private static instance: DatabaseManager;
private connectionPool: any;
private constructor() {
this.connectionPool = createConnectionPool({
max: 10,
min: 2,
idleTimeoutMillis: 30000
});
}
public static getInstance(): DatabaseManager {
if (!DatabaseManager.instance) {
DatabaseManager.instance = new DatabaseManager();
}
return DatabaseManager.instance;
}
public async query(sql: string): Promise<any> {
const client = await this.connectionPool.connect();
try {
return await client.query(sql);
} finally {
client.release();
}
}
}
async function timerTrigger(myTimer: Timer, context: InvocationContext): Promise<void> {
const db = DatabaseManager.getInstance();
const results = await db.query('SELECT * FROM users');
context.log(`Processed ${results.length} users`);
}
app.timer('timerTrigger', {
schedule: '0 */5 * * * *',
handler: timerTrigger
});
Codeoptimierung
Sie können Ihren Code weiter optimieren, indem Sie Ihre App bündeln, eine Lazy-Loading-Strategie verwenden und Ihre Datenstrukturen optimieren.
Bündeln Sie Ihre Abhängigkeiten
Nutze Bündelungstools wie esbuild, webpack oder rollup, um deine Funktions-App in weniger Dateien zu kombinieren. Das Bündeln reduziert die Anzahl der Dateien, die die Laufzeit beim Kaltstart lädt, was die Startzeit direkt verbessert, insbesondere bei Anwendungen mit großen Abhängigkeitsbäumen. Weitere Informationen finden Sie unter Vor der Bereitstellung bündeln.
Modulladezeit minimieren
Das Laden schwerer Module im globalen Umfang erhöht die Kaltstartzeit, weil jeder Import vor dem ersten Aufruf läuft. Verwenden Sie dynamische Importe, um die Last zu verschieben, bis das Modul tatsächlich benötigt wird:
async function processData(data: any) {
const { heavyFunction } = await import('heavy-library');
return heavyFunction(data);
}
Verwenden Sie effiziente Datenstrukturen
Wenn du häufig Elemente nachschlagen musst, verwende ein Map anstelle eines Array.find(). Ein Map bietet O(1)-Suchvorgänge im Vergleich zu O(n) bei der Array-Durchsuchung:
const userMap = new Map();
const users = await fetchUsers();
users.forEach(user => userMap.set(user.id, user));
const user = userMap.get(userId); // O(1) lookup
Überwachung und Profilierung
Nutze es, context.log() um die Ausführungszeit in deinem Funktionshandler zu verfolgen. Für benutzerdefinierte Kennzahlen und Abhängigkeitsverfolgung verwenden Sie direkt das Application Insights Node.js SDK . Weitere Informationen finden Sie unter Benutzerdefinierte Daten verfolgen.
Das folgende Beispiel protokolliert die Ausführungsdauer und den Speicherverbrauch:
import { app, HttpRequest, HttpResponseInit, InvocationContext } from '@azure/functions';
async function trackedFunction(request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> {
const startTime = Date.now();
const memoryBefore = process.memoryUsage().heapUsed;
try {
const result = await processData(request);
const duration = Date.now() - startTime;
const memoryDelta = (process.memoryUsage().heapUsed - memoryBefore) / 1024 / 1024;
context.log(`Execution time: ${duration}ms, Memory delta: ${memoryDelta.toFixed(2)}MB`);
return { jsonBody: result };
} catch (error) {
context.error(`Function failed after ${Date.now() - startTime}ms`, error);
throw error;
}
}
app.http('trackedFunction', {
methods: ['GET', 'POST'],
handler: trackedFunction
});
Indem Sie diese Empfehlungen befolgen, können Sie die Leistung und Skalierbarkeit Ihrer Node.js Azure Functions Anwendungen erheblich verbessern.