Kommentar
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
När du utvecklar för Azure Functions med TypeScript och Node.jsbehöver du förstå hur dina funktioner fungerar och hur den prestandan påverkar hur din funktionsapp skalar. Denna förståelse är viktigare när man designar högpresterande appar. De viktigaste faktorerna att ta hänsyn till när du designar, skriver och konfigurerar dina funktionsappar är horisontell skalning, genomströmningsprestandakonfigurationer och Node.js-specifika optimeringar.
Horisontell skalning
Som standard övervakar Azure Functions automatiskt belastningen på ditt program och skapar fler värdinstanser för Node.js efter behov. Azure Functions använder inbyggda tröskelvärden för olika triggertyper för att avgöra när instanser ska läggas till, såsom meddelandenas ålder och köstorlek för QueueTrigger. Du kan inte konfigurera dessa trösklar. Mer information finns i Event-driven skalning i Azure Functions.
För att förbättra skalningen av din Node.js funktionsapp, följ denna metod i ordning:
- Börja med standardinställningarna och optimera din kod först genom att använda asynkrona I/O-mönster.
- Skala ut (lägg till fler instanser) innan du skalar upp antalet arbetarprocesser.
- Öka
FUNCTIONS_WORKER_PROCESS_COUNTförst efter att belastningstester visar att processorn är fullt belastad på varje instans. - För I/O-bundna arbetsbelastningar, håll antalet arbetare konservativt och fokusera på att minska blockeringssamtal och beroendelatens.
- Testa om efter varje ändring och övervaka genomströmning, latens och felfrekvenser för att validera förbättringar.
Förbättra genomströmningsprestanda
Standardkonfigurationerna är lämpliga för de flesta Azure Functions-applikationer. Du kan dock förbättra prestandan för dina programs dataflöde genom att använda konfigurationer baserat på din arbetsbelastningsprofil. Det första steget är att förstå vilken typ av arbetslast du hanterar.
| Typ av arbetsbelastning | Egenskaper för funktionapp | Examples |
|---|---|---|
| I/O-begränsad | • Appen måste hantera många samtidiga anrop. • Appen bearbetar ett stort antal I/O-händelser, till exempel nätverksanrop och diskläsning/skrivningar. |
• Webb-API:er • Databasoperationer • Filbehandling |
| CPU-bundet | • Appen utför långvariga beräkningar, såsom bildbehandling. • Appen gör datatransformation eller komplexa beräkningar. |
• Databehandling • Innehållstransformation • Körning av algoritm |
Eftersom verkliga funktionsarbetsbelastningar vanligtvis är en blandning av I/O och CPU-bundna, profilera appen under realistiska produktionsbelastningar. Följande avsnitt täcker specifika optimeringar:
- async/await-mönster
- Arbetsprocesser för flera språk
- Optimering av händelseloopar
- Minneshantering
- Kodoptimering
Async/await-mönster
Node.js är byggt på en händelsestyrd, icke-blockerande I/O-modell som gör den lättviktig och effektiv för I/O-bundna operationer. För applikationer som bearbetar ett stort antal I/O-händelser eller är I/O-bundna kan du förbättra prestandan avsevärt genom att använda korrekta asynka/väntemönster:
-
Användning
Promise.all()för parallella operationer: När du har flera oberoende asynkrona operationer, kör dem parallellt istället för sekventiellt. - Undvik att blockera händelseloopen: Använd inte synkrona operationer i asynkrona funktioner.
- Använd streaming för stora datamängder: Bearbeta stora datamängder med strömmar istället för att ladda in allt i minnet.
- Implementera korrekt felhantering: Använd try/catch-block och hantera löftesavvisningar.
Följande exempel demonstrerar parallella asynkrona operationer med 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();
}
Använda flera språkarbetsprocesser
Som standard har varje Functions-värdinstans en enda språkarbetsprocess. Du kan öka antalet arbetsprocesser per värd (upp till 10) med hjälp av programinställningen FUNCTIONS_WORKER_PROCESS_COUNT . Azure Functions försöker sedan fördela samtidiga funktionsanrop jämnt mellan dessa arbetare.
Varning
Använd inställningen FUNCTIONS_WORKER_PROCESS_COUNT med försiktighet. Flera processer som körs i samma instans kan leda till oförutsägbart beteende och öka funktionsbelastningstiderna. Om du använder denna inställning kan körning från en paketfil kompensera dessa nackdelar.
För CPU-bundna appar, sätt antalet språkarbetare till att vara lika med eller högre än antalet tillgängliga kärnor per funktionsapp. Mer information finns i Tillgängliga instans-SKU:er.
I/O-bundna appar kan också dra nytta av att öka antalet arbetsprocesser utöver antalet tillgängliga kärnor. Tänk på att inställningen av antalet arbetare som är för högt kan påverka den övergripande prestandan på grund av det ökade antalet nödvändiga kontextväxlar.
Optimering av händelseloopar
Node.js använder en enkeltrådad event loop för att hantera I/O-operationer. Att optimera hur din kod interagerar med händelseloopen är avgörande för prestandan.
Håll händelseloopen fri
Synkrona loopar som utför tung beräkning blockerar händelseloopen och förhindrar att andra anrop bearbetas. Dela istället upp stora datamängder i batcher och ge kontroll mellan batcher med setImmediate:
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));
}
}
Övervaka fördröjning i händelseslingan
Använd följande mönster för att mäta hur länge uppgifter väntar i händelseloop-kön:
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`);
});
}
Minneshantering
Effektiv minnesanvändning är avgörande för Azure Functions prestanda, särskilt när det gäller kallstart och skalning. Följ dessa allmänna riktlinjer när du optimerar din minnesanvändning:
- Minimera globala variabler: Tunga objekt med global räckvidd påverkar tider för kallstart.
- Använd anslutningspooling: Återanvänd databasanslutningar och HTTP-klienter.
- Rensa resurser: Stäng anslutningar ordentligt och rensa timers.
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
});
Kodoptimering
Du kan optimera din kod ytterligare genom att paketera din app, använda en lat laddningsstrategi och optimera dina datastrukturer.
Bunta dina beroenden
Använd paketeringsverktyg som esbuild, webpack eller rollup för att kombinera din funktionsapp till färre filer. Paketering minskar antalet filer som körtiden laddar under kallstart, vilket direkt förbättrar starttiden, särskilt för appar med stora beroendeträd. För mer information, se Bundle before deployment.
Minimera modulens laddningstid
Att ladda tunga moduler vid global scope ökar kallstartstiden eftersom varje import körs före första anropet. Använd dynamiska importer för att skjuta upp belastningen tills modulen faktiskt behövs:
async function processData(data: any) {
const { heavyFunction } = await import('heavy-library');
return heavyFunction(data);
}
Använd effektiva datastrukturer
När du behöver leta upp föremål ofta, använd en Map istället för Array.find(). En Map ger O(1)-uppslagningar jämfört med O(n) för genomsökning av arrayer:
const userMap = new Map();
const users = await fetchUsers();
users.forEach(user => userMap.set(user.id, user));
const user = userMap.get(userId); // O(1) lookup
Övervakning och profilering
Använd context.log() för att spåra exekveringstidpunkten inom din funktionshanterare. För anpassade mätvärden och beroendespårning, använd Application Insights Node.js SDK direkt. Mer information finns i Spåra anpassade data.
Följande exempel loggar exekveringstid och minnesanvändning:
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
});
Genom att följa dessa rekommendationer kan du avsevärt förbättra prestandan och skalbarheten i dina Node.js Azure Functions applikationer.