Menü Kapat

Güneş Paneli Projesi – NodeJS

Bir projede kullanılacak teknolojiler seçilirken; ihtiyaçlar, maliyet, geliştirme ekibinin deneyimi gibi birçok parametre devreye girmektedir. Bu parametrelerin önem sırası projeyi gerçekleştirecek kuruma göre değişiklik gösterebilir. Bu yazıda “Güneş Paneli Projesi” özelinde NodeJS tercihi ihtiyaçlar açısından değerlendirilecektir.

Hedef ve İhtiyaçlar

Güneş paneli projesindeki amaç güneş panellerinden alınan anlık verilerin bluetooth ile bilgisayara, oradan da sunuculara aktarılabilmesi ve bu anlık verilerin hem depolanması hem de bir arayüz ile takip edilebilmesidir. Bu hedef doğrultusunda ihtiyaçlar aşağıdaki şekilde belirlenebilir;

  1. Anlık veri transferine uygun yapıda olmalı.
  2. Tıkanmasız (non-blocking) olmalı.
  3. Ölçeklenebilir olmalı.

Anlık (Geçek Zamanlı) Veri Transferi

SolarPanelSocket

Panellerden alınan veriler sunuculara gönderilmektedir. Sunucuya gelen veriler sisteme bağlanan istemciler tarafından anlık takip edilebilmelidir. Öncelikle anlık veri takibi için istemcinin sunucuya belirli aralıklara sürekli talep göndermesi (polling) düşünülebilir. Fakat bu yapının bazı dezavantajları bulunmaktadır;

  • Her bir talepte paket başlığı gibi ek bilgilerin sunucu ile istemci transferi bandwith’ in  verimsiz kullanılmasına sebep olacaktır.
  • İstemci sayısının arttığı durumlarda sunucu performansı gereksiz çağrılardan dolayı düşecektir.
  • Talep periyotunun belirlenmesi her zaman için kolay olmayacaktır.
  • Ve en önemlisi ise sonuçların gözlenmesinde periyota bağlı olarak gecikmeler yaşanacaktır.

 

Bu konuda temel problem çift taraflı iletişimin (full duplex) olmamasıdır. Sunucuda bir değişiklik olduğunda sunucunun istemiciyi bilgilendirmesi yerine istemci sürekli olarak “değişiklik var mı?” diye sunucuya talepte bulunmaktadır. Bu problem için long polling çözümüne alternatif olarak düşünülebilecek -HTML 5 ile gelen yeniliklerden olan- websocket protokolünden yararlanılmıştır. Önceki yazıda bu protokolden ve iletişimin nasıl sağlandığından bahsedildi fakat kabaca tekrarlarsak istemci ve sunucu arasında bir kere bağlantı kurulmaktadır ve çift taraflı iletişim, tekrar bağlantı kurulmasını gerektirmeden sağlanabilmektedir. Hatta Node.JS için geliştirilmiş olan Socket.IO kütüphanesi sunucunun bir şekilde bağlantıdan kopması durumunda bile belirli süreyle sunucuyu yoklayarak -aktifse- herhangi bir işleme gerek duymadan tekrar bağlantıyı sağlayabilmektedir.

Fakat bu ihtiyaç, tek başına NodeJS tercihi için yeterli değildir çünkü diğer dillerde de (örn java, c#) websocket protokolünü kullanarak uygulamanın geliştirilmesi (benzer performansta ve kod kalitesinde) mümkündür.

Tıkanmasız (non-blocking)

Bir işin tıkanmasız nasıl yürütüldüğünü açıklamak için aşağıdaki metafor [2] yardımcı olacaktır;

  1. Garson masa 1’in siparişini alır.
  2. Garson mutfağa gider ve masa 1’in siparişini iletir.
  3. Aşçı masa 1’in siparişini hazırlarken garson masa 2’nin siparişini alır.
  4. Aşçı masa 1’in siparişini hazırlarken garson, masa 2’nin siparişini iletir.
  5. Masa 1, henüz siparişi gelmemişken garsona yeni sipariş verir.
  6. Garson masa 1’in yeni siparişini iletir.
  7. Masa 1’in ilk siparişi hazırlanınca, garson siparişi masaya götürür.
  8. Masa 2’nin siparişi hazırlanınca, garson siparişi masaya götürür.
  9. Masa 1’in ikinci siparişi hazırlanınca, garson siparişi masaya götürür.

Aynı işin tıkanmalı yürütüldüğü düşünülürse; masa 1′ in siparişi teslim edilene kadar masa 2 sipariş dahi veremeyecektir. Bu durum 2 masalı bir işletme için çok problem olmasa da 100 masalı işletme için ciddi problemler doğuracaktır.

Yukarıdaki tıkanmasız örneğe istinaden Node.JS, uygulama kodlarını garsonun çalıştığı gibi çalıştıracaktır. Masalar istemcilerdir, aşçı ise I/O taleplerin işleyen işletim sistemleridir.

Tıkanmasız kodlama yöntemini açıklamak için öncelikle tıkanmalı örneği incelemek faydalı olacaktır. Güneş paneli projesinde, panellerden gelen veriler veri tabanına kaydedilmektedir ve web socket protokolü ile kullanıcılara iletilmektedir. Bu senaryo aşağıdaki şekilde kodlanabilir;

var newPanelData = new PanelData({ //rastgele veri oluşturuluyor (panelden verinin gelmesi)
panelId: key,
current: parseInt(Math.random()*1000),
voltage: parseInt(Math.random()*1000),
light: parseInt(Math.random()*100),
temperature: parseInt(Math.random()*100),
moisture: parseInt(Math.random()*100),
date: new Date()
});
newPanelData.save(); //verinin veri tabanına kaydedilmesi
console.log('Veri kaydedildi. Panel id : ' + newPanelData.panelId); //log
io.emit('retrievePanelData', newPanelData); //gelen verinin kullanıcılara gönderilmesi
view raw
blocking.js
hosted with ❤ by GitHub

11. satırda veri tabanına kaydetme işlemi yapılmaktadır. 13. satırda işlem kayıt defterine kaydedilip sonrasında veri kullanıcılara iletilmektedir. Bu kod yukarıda verilen metaforla ilişkilendirilirse; garson masa 1’in talebini tamamlamadan masa 2’nin talebini alamayacaktır. Anlık veri aktarımının önemli olduğu bu senaryoda sistemin performansı, 11. ve 13. satırlar sebebiyle doğrudan I/O işlemlerinin (veri tabanı ve log) performansına bağlanmıştır çünkü 15. satıra geçilmeden 11 ve 13 işletilmelidir ve bu işlemler yapılırken sunucu hiçbir şey yapmamaktadır.

Node.js’de I/O işlemleri için kullanılan fonksiyonlar function(data, callback) gibi bir imzaya sahiptir. Bu imzada “callback” parametresi de bir fonksiyondur. Bu fonksiyon ana fonksiyonun içinden çağrılabilir. callback parametresi alan fonksiyonlar olay döngüsü içinde asenkron çalışmaktadır ve bu sayede sunucu I/O işlemini beklemek zorunda kalmaksızın kodu kaldığı yerden çalıştırmaya devam edebilir. I/O işlemi tamamlandığında ise fonksiyona gönderilen callback fonksiyonu (parametresi) çalıştırılacaktır.

var newPanelData = new PanelData({ //rastgele veri oluşturuluyor (panelden verinin gelmesi)
panelId: key,
current: parseInt(Math.random()*1000),
voltage: parseInt(Math.random()*1000),
light: parseInt(Math.random()*100),
temperature: parseInt(Math.random()*100),
moisture: parseInt(Math.random()*100),
date: new Date()
});
newPanelData.save(function(){ //verinin veri tabanına kaydedilmesi
console.log('Veri kaydedildi. Panel id : ' + newPanelData.panelId); //log
});
io.emit('retrievePanelData', newPanelData); //gelen verinin kullanıcılara gönderilmesi
view raw
nonblocking.js
hosted with ❤ by GitHub

Yukarıdaki kod tıkanmasız çalışma için bir örnektir. 11. satırda yapılan veri tabanı çağrısı olay döngüsüne alınarak bir sonraki kodun bekletmeden çalışması sağlanmaktadır. Bu durumda 15. satır çalışmak için 11. satırın tamamlanmasını beklemek zorunda kalmamaktadır. Son olarak “save” fonksiyonuna gönderilen anonim fonksiyon, “save” fonksiyonu tamamlandığında “save” fonksiyonundan otomatik olarak çağrılacaktır. Yukarıdaki kodun şu şekilde çalışması muhtemeldir;

  1. Veri al.
  2. Veriyi veri tabanına kayıt için gönder.
  3. Veriyi istemciye gönder.
  4. “Veri kaydedildi.” logunu yaz.

Tıkanmasız kodlama ihtiyacı Node.JS kullanımı için bir gerekçedir. Bu yapıda bir proje Java ile de oluşturulabilir fakat yapılacak her asenkron çağrı için “thread” yaratılması ve fonksiyonların parametre olarak gönderilebilmesi için de düzgün bir ara yüz (interface) mimarisinin kodlanması gerekmektedir. Bu kaynakların tüketimi ve sürdürülebilir kod konusunda ciddi problemler doğuracaktır.

Ölçeklenebilir [3]

Bir uygulama yüksek yük altında da performanstan ödün vermeden çalışabiliyorsa, o uygulama için ölçeklenebilir denilmektedir. Buradaki yüksek yük kavramını işlemci ve bellek kullanımı açısından değerlendirebiliriz. Uygulamanın daha çok ihtiyaç duyacağı kaynak, teknoloji seçiminde rol oynayacaktır.

Güneş paneli projesi için bellek kullanımı işlemci kullanımına göre ön plana çıkmaktadır çünkü bu projede işlemcinin yoğun kullanılacağı (matematiksel hesaplamalar gibi) senaryolar bulunmamaktadır. Node.js, geleneksel uygulama sunucularına göre belleği daha efektif kullanabilmektedir. Bunda baş rolü oynayan şey, tüm işlerin tek “thread”  üzerinden yürütülmesidir. Geleneksel uygulama sunucuları her bir talep (request) için limitli bir havuzdan yeni bir “thread” oluşturmaktadır, Node.JS ise yukarıda belirtildiği gibi tüm talepleri tek bir “thread” ile karşılamaktadır.

toptal-blog-1_B

Kabaca bir hesap yapılırsa: her bir prosesin (thread) 2MB boyutuna ulaşması muhtemeldir ve kabul edilebilir. 8 GB bellekli bir geleneksel uygulama sunucusu teorik olarak en fazla 4.000 eş zamanlı bağlantıya izin verebilecektir. Aynı senaryoda Node.JS ise 1.000.000 eş zamanlı bağlantıya hizmet verebilmektedir. [4]

Bu projede Node.JS i öne çıkaran bir diğer nokta ise veri tabanı seçimidir. Node.JS’ in ilişkisel veri tabanının tercih edileceği projelerde kullanılması önerilmemektedir. Projede JSON tipinde veriyi saklayabilen veri tabanı (mongo db) kullanılmaktadır. Ek olarak bu projede, sunucu ve istemci arasındaki veri alışverişi tamamen JSON tipinde gerçekleştirilmektedir. Her iki tarafta javascript kullanılması sebebiyle veri tipini dönüşüme tabii tutmak gerekmemiştir. Bu da gereksiz eforu ve zaman kaybını önlemektedir.

[1] Finding the right Node.js WebSocket implementation, https://medium.com/@denizozger/finding-the-right-node-js-websocket-implementation-b63bfca0539#.ju238qfth, Erişim Tarihi : 20.04.2016

[2] Understanding Node.js, https://www.codeschool.com/blog/2014/10/30/understanding-node-js/, Erişim Tarihi : 23.04.2016

[3] Why The Hell Would I Use Node.js? A Case-by-Case Tutorial, https://www.toptal.com/nodejs/why-the-hell-would-i-use-node-js, Erişim Tarihi : 29.04.2016

[4] Node.js w/1M concurrent connections!, http://blog.caustik.com/2012/08/19/node-js-w1m-concurrent-connections/, Erişim Tarihi : 29.04.2016

Bir yorum yazınız. Yorumlarınız bizim için değerlidir.