Node.js Azure İşlevleri için performansı ve ölçeklendirmeyi optimize edin

TypeScript ve Node.jskullanarak Azure İşlevleri için geliştirirken, fonksiyonlarınızın nasıl çalıştığını ve bu performansın fonksiyon uygulamanızın ölçeklenme şeklini nasıl etkilediğini anlamanız gerekir. Bu anlayış, yüksek performanslı uygulamalar tasarlarken daha önemlidir. Fonksiyon uygulamalarınızı tasarlarken, yazarken ve yapılandırırken dikkate alınması gereken başlıca faktörler yatay ölçeklendirme, performans performans yapılandırmaları ve Node.jsözel optimizasyonlardır.

Yatay ölçeklendirme

Varsayılan olarak, Azure İşlevleri uygulamanızdaki yükü otomatik olarak izler ve gerektiğinde Node.js için daha fazla konak örneği oluşturur. Azure İşlevleri, farklı tetikleyici türleri için örneklerin ne zaman ekleneceğine karar vermek amacıyla yerleşik eşikler kullanır; örneğin QueueTrigger için iletilerin yaşı ve kuyruk boyutu gibi. Bu eşikleri yapılandırmak mümkün değil. Daha fazla bilgi için bkz. Azure İşlevleri'nde olay temelli ölçeklendirme.

Node.js fonksiyon uygulamanızın ölçeklendirmesini geliştirmek için şu yaklaşımı sırayla izleyin:

  1. Varsayılan ayarlarla başlayın ve önce asenkron I/O kalıplarını kullanarak kodunuzu optimize edin.
  2. İşçi işlem sayısını artırmadan önce ölçeklendirin (daha fazla örnek ekleyin).
  3. FUNCTIONS_WORKER_PROCESS_COUNT öğesini yalnızca yük testi her bir örnekte CPU’nun doyuma ulaştığını gösterdikten sonra artırın.
  4. I/O-bağlı iş yükleri için çalışan sayısını muhafazakar tutun ve engelleme çağrılarını ve bağımlılık gecikmesini azaltmaya odaklanın.
  5. Her değişiklikten sonra tekrar test edin ve iyileştirmeleri doğrulamak için verimlilik, gecikme ve hata oranlarını izleyin.

Aktarım hızı performansını iyileştirme

Varsayılan yapılandırmalar çoğu Azure İşlevleri uygulaması için uygundur. Ancak, iş yükü profilinize göre yapılandırmalar kullanarak uygulamalarınızın aktarım hızının performansını geliştirebilirsiniz. İlk adım, çalıştırdığınız iş yükünün türünü anlamaktır.

İş yükü türü İşlev uygulaması özellikleri Examples
G/Ç bağlı • Uygulamanın birçok eşzamanlı çağrıyı işlemesi gerekir.
• Uygulama, ağ çağrıları ve disk okuma/yazma işlemleri gibi çok sayıda G/Ç olayını işler.
• Web API'leri
• Veritabanı işlemleri
• Dosya işleme
CPU'ya bağlı • Uygulama, görüntü işleme gibi uzun süreli hesaplamalar yapar.
• Uygulama veri dönüşümü veya karmaşık hesaplamalar yapar.
• Veri işleme
• İçerik dönüşümü
• Algoritma yürütmesi

Gerçek dünyadaki işlev iş yükleri genellikle G/Ç’ye ve CPU’ya bağımlı iş yüklerinin bir karışımı olduğundan, uygulamanın profilini gerçekçi üretim yükleriyle çıkarın. Aşağıdaki bölümler belirli optimizasyonları kapsar:

Asenkron/await kalıpları

Node.js, olay odaklı, bloklamayan bir G/Ç modeli üzerine inşa edilmiştir; bu da onu G/Ç bağlantılı işlemler için hafif ve verimli kılar. Çok sayıda I/O olayını işleyen veya I/O ile bağlı uygulamalar için, doğru asenkron/bekleme kalıpları kullanarak performansı önemli ölçüde artırabilirsiniz:

  • Paralel işlemler için kullanımPromise.all(): Birden fazla bağımsız asenkron işlem olduğunda, bunları ardışık olarak değil, paralel çalıştırın.
  • Olay döngüsünü engellemekten kaçının: Asenkron fonksiyonlarda senkron işlemler kullanmayın.
  • Büyük veri için akış kullanın: Her şeyi belleğe yüklemek yerine akışlar kullanarak büyük veri kümelerini işleyin.
  • Uygun hata işleme uygulayın: try/catch bloklarını kullanın ve Promise reddetmelerini yönetin.

Aşağıdaki örnek, paralel asenkron işlemleri şu Promise.all()ile gösterir:

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();
}

Birden fazla dil işleme süreçleri kullanımı

Varsayılan olarak, her İşlev ana bilgisayar örneğinin tek bir dil çalışanı işlemi vardır. Uygulama ayarını kullanarak konak başına çalışan işlemlerinin sayısını artırabilirsiniz (10'a FUNCTIONS_WORKER_PROCESS_COUNT kadar). Azure İşlevleri daha sonra eş zamanlı işlev çağrılarını bu çalışanlara eşit olarak dağıtmaya çalışır.

Warning

FUNCTIONS_WORKER_PROCESS_COUNT Ayarı dikkatli kullanın. Aynı örnekte çalışan birden çok işlem öngörülemeyen davranışlara yol açabilir ve işlev yükleme sürelerini artırabilir. Bu ayarı kullanırsanız, bir paket dosyasından çalıştırmak bu dezavantajları dengeleyebilir.

CPU bağımlısı uygulamalar için, dil çalışanı sayısını, işlev uygulaması başına mevcut çekirdek sayısıyla aynı veya ondan daha yüksek olarak ayarlayın. Daha fazla bilgi edinmek için bkz. Kullanılabilir örnek SKU'ları.

I/O-bağlantılı uygulamalar, mevcut çekirdek sayısının ötesine çalışan süreçlerin sayısını artırmaktan da fayda sağlayabilir. Gerekli bağlam anahtarlarının sayısının artması nedeniyle çalışan sayısını çok yüksek ayarlamanın genel performansı etkileyebileceğini unutmayın.

Olay döngüsü optimizasyonu

Node.js G/Ç işlemlerini yönetmek için tek iş parçacıklı bir olay döngüsü kullanır. Kodunuzun olay döngüsüyle nasıl etkileşime girdiğini optimize etmek, performans için çok önemlidir.

Etkinlik döngüsünü serbest tutun

Yoğun hesaplama yapan senkron döngüler, olay döngüsünü engeller ve diğer çağrıların işlenmesini engeller. Bunun yerine, büyük veri kümelerini toplu işlemlere bölün ve setImmediate ile toplu işlemler arasında denetimi bırakın:

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));
    }
}

Olay döngüsü gecikmesini izle

Olay döngüsündeki kuyruğunda görevlerin ne kadar beklediğini ölçmek için aşağıdaki deseni kullanı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`);
    });
}

Bellek yönetimi

Verimli bellek kullanımı, özellikle soğuk başlatma ve ölçeklendirme açısından Azure İşlevleri performansı için kritik öneme sahiptir. Bellek kullanımınızı optimize ederken şu genel yönergelere uyun:

  • Küresel değişkenleri minimize edin: Küresel kapsamdaki ağır nesneler soğuk başlatma sürelerini etkiler.
  • Bağlantı havuzu kullanın: Veritabanı bağlantılarını ve HTTP istemcilerini yeniden kullanın.
  • Kaynakları temizleyin: Bağlantıları düzgün kapatın ve zamanlayıcıları temizleyin.
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
});

Kod iyileştirmesi

Uygulamanızı paketleyerek, tembel bir yükleme stratejisi kullanarak ve veri yapılarınızı optimize ederek kodunuzu daha da optimize edebilirsiniz.

Bağımlılıklarınızı birleştirin

Fonksiyon uygulamanızı daha az dosyaya birleştirmek için esbuild, webpack veya rollup gibi paketleme araçlarını kullanın. Paketleme, soğuk başlatma sırasında çalıştırma zamanında yüklenen dosya sayısını azaltır; bu da özellikle büyük bağımlılık ağaçlarına sahip uygulamalar için başlatma süresini doğrudan iyileştirir. Daha fazla bilgi için Dağıtımdan önce Paketleme bölümüne bakınız.

Modül yükleme süresini en aza indirin

Küresel kapsamda ağır modüllerin yüklenmesi, soğuk başlatma süresini artırır çünkü her itiş ilk çağrıdan önce çalışır. Modüle gerçekten ihtiyaç duyulana kadar yüklemeyi ertelemek için dinamik içe aktarma kullanın:

async function processData(data: any) {
    const { heavyFunction } = await import('heavy-library');
    return heavyFunction(data);
}

Verimli veri yapıları kullanın

Sık sık öğe aramanız gerekiyorsa, Map yerine Array.find() kullanın. A, Map dizi taraması için O(n) ile karşılaştırıldığında O(1) sorgulamaları sağlar:

const userMap = new Map();
const users = await fetchUsers();
users.forEach(user => userMap.set(user.id, user));

const user = userMap.get(userId); // O(1) lookup

İzleme ve profilleme

Fonksiyon yöneticinizdeki uygulama zamanlamasını takip etmek için kullanın context.log() . Özel metrikler ve bağımlılık takibi için doğrudan Application Insights Node.js SDK'yı kullanın. Daha fazla bilgi için Özel verileri takip et.

Aşağıdaki örnek, yürütme süresi ve bellek kullanımını kaydetmektedir:

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
});

Bu önerilere uyarak, Node.js Azure İşlevleri uygulamalarınızın performansını ve ölçeklenebilirliğini önemli ölçüde artırabilirsiniz.