kralhakan2009 1
kralhakan2009
Vahsi Uzman 1
Vahsi Uzman
Bvural41 1
Bvural41
emirhanHCL 1
emirhanHCL
Hikaye Ekle

[NODE JS] Graceful Shutdown Nasıl Yapılır?

Türkiye'de ilk Mobil & PC Aynı Anda Metin2 Oyna. Triarchonline kalıcı ve uzun ömürlü yapısı ile 24 Temmuz'da açılıyor. | 1-99 Mobil Metin2 Triarch HEMEN TIKLA!

nodejs.png


[NODE JS] Graceful Shutdown Nasıl Yapılır?​


Bir sabah production sunucusunu güncellemek için yeniden başlattım. Ctrl+C'ye bastım ve sunucu anında kapandı. Sorun şu ki, o anda 47 aktif istek vardı ve hepsi yarım kaldı. Veritabanına yazılmakta olan siparişler kayboldu, dosya yükleme işlemleri bozuldu, WebSocket bağlantıları koptu. O günden sonra graceful shutdown'un ne kadar kritik olduğunu anladım.

Graceful shutdown, bir uygulamanın düzgün ve kontrollü bir şekilde kapanması demektir. Yeni istekleri kabul etmeyi durdurur, mevcut isteklerin tamamlanmasını bekler, açık kaynakları temizler ve ardından süreci sonlandırır. Bu, veri kaybını önler ve kullanıcı deneyimini korur.

Node.js'te süreç kapanma sinyalleri alır. En yaygın olanları SIGTERM (kill komutu), SIGINT (Ctrl+C) ve SIGHUP (terminal kapanması) sinyalleridir. Bu sinyalleri yakalayıp kendi kapanma mantığınızı çalıştırabilirsiniz. Ancak SIGKILL (kill -9) yakalanamaz, bu yüzden uygulamanızı her zaman SIGTERM ile kapatmaya çalışın.

Graceful shutdown implementasyonunda sıralama önemlidir. İlk adım, yeni bağlantıları reddetmektir. HTTP sunucusu için server.close() çağrısı yaparsınız. Bu, yeni bağlantıları kabul etmeyi durdurur ama mevcut bağlantıları açık tutar. İkinci adım, sağlık kontrolü endpoint'ini uygunsuz olarak işaretlemektir. Load balancer'ınız bu endpoint'i kontrol ediyorsa, sunucuyu havuzdan çıkaracaktır. Üçüncü adım, mevcut isteklerin tamamlanmasını beklemektir. Dördüncü adım, veritabanı bağlantılarını, mesaj kuyruklarını ve diğer kaynakları temizlemektir.

Zaman aşımı mekanizması da şarttır. Bazı istekler hiçbir zaman tamamlanmayabilir. Sonsuz bir WebSocket bağlantısı veya çok uzun süren bir işlem olabilir. Bu yüzden maksimum bir bekleme süresi belirleyin. 30 saniye makul bir değerdir. Bu süre dolduğunda kalan bağlantıları zorla kapatın.

Docker ve Kubernetes ortamlarında graceful shutdown özellikle önemlidir. Kubernetes bir pod'u sonlandırmadan önce SIGTERM sinyali gönderir ve terminationGracePeriodSeconds kadar bekler. Bu süre içinde uygulamanız kapanmazsa SIGKILL gönderilir. Varsayılan süre 30 saniyedir. Uygulamanızın kapanma süresine göre bu değeri ayarlamanız gerekir.

Cluster modülü ile birlikte graceful shutdown biraz daha karmaşıktır. Master sürecin her worker'a kapanma sinyali göndermesi, worker'ların mevcut isteklerini tamamlaması ve ardından çıkması gerekir. Bu süreci düzgün yönetmek, sıfır kesinti güncellemesi için zorunludur.

Veritabanı bağlantıları açısından düşünürsek, bağlantı havuzunuzu düzgün şekilde kapatmanız gerekir. Mongoose kullanıyorsanız mongoose.connection.close(), Sequelize kullanıyorsanız sequelize.close(), Redis kullanıyorsanız client.quit() çağırmalısınız. Bu kaynakları kapatmadan süreçten çıkmak, veritabanı tarafında yetim bağlantılara yol açar.

Event listener temizliği de göz ardı edilmemeli. setInterval ve setTimeout ile oluşturduğunuz zamanlayıcıları temizlemelisiniz. Aksi takdirde süreç kapanmayabilir çünkü Node.js aktif zamanlayıcıları olan bir süreci canlı tutar.

JavaScript:
// ==============================
// Graceful Shutdown Tam Örneği
// ==============================
const http = require('http');
const { Pool } = require('pg'); // PostgreSQL bağlantı havuzu

// Veritabanı bağlantı havuzu
const veritabaniHavuzu = new Pool({
    host: 'localhost',
    port: 5432,
    database: 'uygulama_db',
    user: 'admin',
    password: 'gizli_sifre',
    max: 20
});

// Uygulama durumu
let kapaniyorMu = false;
let aktifIstekSayisi = 0;

// HTTP Sunucusu
const sunucu = http.createServer(async (req, res) => {
    // Kapanma modundayken yeni istekleri reddet
    if (kapaniyorMu) {
        res.writeHead(503, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify({ hata: 'Sunucu bakımda, lütfen tekrar deneyin.' }));
        return;
    }

    aktifIstekSayisi++;

    try {
        if (req.url === '/saglik') {
            // Sağlık kontrolü endpoint'i
            res.writeHead(kapaniyorMu ? 503 : 200, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({
                durum: kapaniyorMu ? 'kapanıyor' : 'sağlıklı',
                aktifIstekler: aktifIstekSayisi,
                bellekKullanimi: process.memoryUsage(),
                calisma_suresi: process.uptime()
            }));
        } else if (req.url === '/veri') {
            // Veritabanı sorgusu simülasyonu
            const sonuc = await veritabaniHavuzu.query('SELECT NOW() as zaman');
            res.writeHead(200, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({ zaman: sonuc.rows[0].zaman }));
        } else {
            // Normal istek işleme
            await new Promise(resolve => setTimeout(resolve, 1000));
            res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
            res.end('İstek başarıyla işlendi!');
        }
    } catch (hata) {
        console.error('İstek işlenirken hata:', hata.message);
        res.writeHead(500, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify({ hata: 'Sunucu hatası' }));
    } finally {
        aktifIstekSayisi--;
    }
});

sunucu.listen(3000, () => {
    console.log('Sunucu 3000 portunda çalışıyor');
});

// Periyodik görevler
const saglikKontrolZamanlayici = setInterval(() => {
    console.log(`Aktif istek sayısı: ${aktifIstekSayisi}`);
}, 15000);

// ==============================
// Graceful Shutdown Fonksiyonu
// ==============================
async function duzgunKapat(sinyal) {
    console.log(`
${sinyal} sinyali alındı. Düzgün kapanma başlatılıyor...`);

    // Kapanma modunu aktifle
    kapaniyorMu = true;

    // Zamanlayıcıları temizle
    clearInterval(saglikKontrolZamanlayici);
    console.log('Zamanlayıcılar temizlendi.');

    // Yeni bağlantıları kabul etmeyi durdur
    sunucu.close(() => {
        console.log('HTTP sunucusu yeni bağlantı kabul etmiyor.');
    });

    // Mevcut isteklerin tamamlanmasını bekle (maksimum 30 saniye)
    const MAKS_BEKLEME = 30000;
    const KONTROL_ARALIGI = 500;
    let beklenenSure = 0;

    while (aktifIstekSayisi > 0 && beklenenSure < MAKS_BEKLEME) {
        console.log(`${aktifIstekSayisi} aktif istek bekleniyor...`);
        await new Promise(resolve => setTimeout(resolve, KONTROL_ARALIGI));
        beklenenSure += KONTROL_ARALIGI;
    }

    if (aktifIstekSayisi > 0) {
        console.warn(`Zaman aşımı! ${aktifIstekSayisi} istek zorla sonlandırılıyor.`);
    } else {
        console.log('Tüm aktif istekler tamamlandı.');
    }

    // Veritabanı bağlantılarını kapat
    try {
        await veritabaniHavuzu.end();
        console.log('Veritabanı bağlantı havuzu kapatıldı.');
    } catch (hata) {
        console.error('Veritabanı kapatılırken hata:', hata.message);
    }

    console.log('Uygulama başarıyla kapatıldı.');
    process.exit(0);
}

// Kapanma sinyallerini yakala
process.on('SIGTERM', () => duzgunKapat('SIGTERM'));
process.on('SIGINT', () => duzgunKapat('SIGINT'));

// Yakalanmamış hataları ele al
process.on('uncaughtException', (hata) => {
    console.error('Yakalanmamış hata:', hata);
    duzgunKapat('UNCAUGHT_EXCEPTION');
});

process.on('unhandledRejection', (sebep, promise) => {
    console.error('İşlenmemiş Promise reddi:', sebep);
});

console.log('Graceful shutdown mekanizması aktif.');
console.log('Kapatmak için Ctrl+C tuşlayın veya SIGTERM gönderin.');

Graceful shutdown, production ortamında çalışan her Node.js uygulaması için zorunlu bir özelliktir. Veri kaybını önler, kullanıcı deneyimini korur ve güncelleme süreçlerini sorunsuz hale getirir. Her yeni projeye başladığınızda, graceful shutdown mekanizmasını ilk günden implemente edin. Sonradan eklemek çok daha zordur.
 

Şu an konuyu görüntüleyenler (Toplam : 0, Üye: 0, Misafir: 0)

Geri
Üst