Node.js Mimari Yapı – Node Internal, Event-loop ve Libuv hakkında

Node.JS 

Node için yapılabilecek en kısa tanım sanırım :

server-side/ sunucu tarafında javascript kullanımına imkan veren “javascript execution environment”  yada “javascript run-time environment”  özelde ise javascript ile uygulama yazmaniza imkan taniyan non-blocking networking application framework

diyebiliriz.

Node.js i güçlü ve farklı kılan en önemli özelliklere geçmeden önce, Node un kendi sayfasından alıntı olan şu tanıma bakalım;

Node.js® is a platform built on Chrome’s JavaScript runtime for easily building fast, scalable network applications. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient, perfect for data-intensive real-time applications that run across distributed devices.

Yukardaki, tanımdan

  1. Node, Google’s V8 JavaScript engine üzerine (chrome browserın da kullandıgı engine) ve Joyent’s libuv  asynchronous I/O library üzerine inşa edilmiş bir platform. Libuv’un , non-blocking IO ,  işlemleri gerçekleştirdiğini, V8 in de JavaScript kısmından sorumlu olduğunu not etmekde fayda var.
  2. Node, bir platform,
    1. Apache yada NGiNX gibi sadece bir web/uyguluma sunucusu değil.(Uygulamalarimizin onundeki, network trafigini yoneten ara katman degil.)
    2. Network uygulamaları için güçlü bir alt yapı sağlıyor, ister bir API sunucusu olarak, ister Web sunucusu olarak , isterseniz low-level/alt-seviye bir embeded device / gömülü donanım da Network uygulamaları için kullanabileceğiniz bir platform.
    3. ASP.NET MVC, Rails yada Laravel gibi bir web framework değil, üzerine Frameworkler inşa edilebilecek bir alt yapı. Örneğin,  .NET/CLR ve ASP.NET MVC yi düşünelim, yada JVM / JDK ve Play Freamework ü , Node u burda .NET/CLR’e yada JDK/JVM ye benzetebiliriz, ASP.NET de .NET üzerinde çalışan bir web framework tıpkı Node üzerinde çalışan Express.js yada Hapi frameworkleri gibi. Burada bir fark daha var, .NET ve JVM çok büyük alt yapılar ve sadece Network temelli uygulamara odaklanmış değiller. Node ise temelde tamamen “Ölçeklenebilir, Hızlı, Network temelli uygulamalar(Özelliklede Web ve API sunucuları, Real-Time data-sensetive Web uygulamaları) ” geliştirmek için özelleştirilmiş.
  3. Node, %100 Async ve non-blocking bir yapıda tasarlanmış(Bunun dışına çıkabilsek’de , Node u güçlü kılan en temel özellik bu “event-driven, non-blocking I/O” )
    1. Yine .NET /CLR üzerinden bakacak olursak, .NET hem synchronous hem asynchronous alternatifleri sunarken, (await le başlayan Async le biten methodları hatırlayın) , Node.js tamamen asynchronous  bir yapı sunmakta(evet synchronous alternatiiflerimiz de var ama node tasarım olarak %100 asynchronous  perspektife sahip.)
  4. Uygulama geliştirme dili “JavaScript” böylece ASYNC ve Event-driven yapısına mükemmel uyum sağlıyor.
  5. Event-Driven yada based on event driven model  ; bir event / olay olur ve bu olay için yazdığımız call-back çalışır.
    1. bir ajax isteğini / requestini  düşünün , arka planda JavaScriptle sunucuya bir request göndeririz, isteğe / request’e verilen cevabı beklemek yerine, cevap/response geldiğinde çalışmasını istediğimiz call-back’i bu requeste atamış oluruz, böylece sunucudan cevap gelene kadar uygulamayı block etmek yerine Async ve event-driven modelini kullanarak uygulamızı block etmemiş oluruz. Sunucudan, cevap geldiğinde atanan call-back çalışır ve işlemimizi gerçekleştirir. Tıpkı bu yapı’da olduğu gibi, uygulamanın tamamı event-based olarak yazılabilir.
    2. Node içinde dilerseniz bu yapının dışına çıkabilirsiniz, ama tekrar belirtmek’de fayda var node.js i güçlü kılan yapı bu ; “event-driven, non-blocking I/O ve ASYNC 
  6. Single Thread with event loop ; Node, tasarım olarak single-thread ; birim zamanda sadece bir işlem /  one command at a time ve paralel çalıştırma ya izin vermeyen bir yapı / can’t do any parallel code execution . İlk bakışta bir zafiyet gibi gözüksede aslında Node’u en güçlü kılan özelliklerin başında “Node runs in only one thread / birim zamanda tek bir işlem” . geliyor diyebilriz.
    1. Bu konu , yazının devamında biraz daha detaylandırılacak.

Node.js internal  / Node.js mimarisi

Birinci maddeyi açarak devam edelim , aşağıdaki görsele baktığımızda ;

Node.js internal

 

Node’un en üstündeki ilk iki katmanın da;

  1. Standart JavaScript kütüphaneleri
  2. yine standart ama  C/C++ ile yazılmış kütüphaneleri görmekteyiz. (C/C++ bindings to JavaScript)

Daha, low level / alt seviye ( Genelde Libuv tarafından görülen işlemlerin, javascript’e bağlanması) kısımlarının C/C++ la yazıldığını(Http, socket vb) , diğer standart kütüphanelerinde(node-core ) JavaScriptle yazılıdığını söyleyebiliriz.

Biz, Node üzerinde çalışacak, örneğin bir web uygulaması yazacağımızda kullanacağımız dil tabiki JavaScript, ama yukarıdaki resimden de anlaşılabileceği gibi, C/C++ ile node içinde kullanabileceğimiz bindigler yazarak, JavaScript le bunlara erişebilmekteyiz.

İlk iki katmanın altındaki kısım ise V8 , google tarafından geliştirilmiş olan, javascript runtime . Yazdığımız Javascipt i çalıştırmamıza   olanak sağlayan runtime engine. (Not javascript yorumlanan /interpreted  bir  dil diye tanımlansada, aslında burda , javascript ve diğer interpreted diller arasında ciddi bir ayrım söz konusu, yorumlanan yada line by line / satır satır çalışan dil lerden farklı olarak, çalışmadan önce bir kismi compile olan / derlenen sonra line by line çalışan bir dil javascript!  Evet, derleme işlemi sonucunda bir .exe yada binary elde etmiyoruz, ama line by line çalışan kod + run-time da hafızaya yüklenen derlenmiş kod! ile Javascript in farklı bir çalışma mantığı sözkonusu. Bu konu ile ilgili “run-time” yazısına bakabilirsiniz! )  .

V8′in altında ise Libuv  bulunmakta, Libuv, asynchronous I/O işlemlerinden sorumlu kısım, node.js için özel olarak geliştirilsede, gittikçe populerleşen bir kütüphane. Operating System Level / İşletim sistemi seviyesinde Non-Blocking I/O işlemlerine olanak sağlamakta. 

Non-blocking I/O ve single thread meselesi

Burada bir liste yapacak olursak;

  1. İşletim sistemleri, klasik blocking I/O ve threading  sorunlarını aşmak için;  Event Demultiplexer diye adlandırılan (nasıl türkçeye çevireceğimi bilemedimconcurrent, non-blocking – eş zamanlı olan, fakat bir birini engellemeyen bir I/O (I/O dan kastımız genelde disk üzerine yazma ve okuma) işlemleri için native bir alt yapıyı sunmakta (En azından yeni nesil işletim sistemleri)
  2. Bu yapı çok genel olarak, bir dizi kaynağı izlemeye alarak, bu kaynaklar için oluşan eventleri / olayları bir veri yapısında tutmakta, ve sırayla her bir kaynağı / resource kontrol edip bir sonraki I/O işlemine geçip geçmeye karar vermekde.
  3. her bir işletim sistemi kendine has bir   Event Demultiplexer uyarlamasına sahip. Windows(IOCP), Linux(epoll) ve MacOsx(kqueue).   Bu yüzden, Node.js takımı , alt seviye non-blocking I/O işlemlerini farklı Event Demultiplexer uyarlamalarına sahip , farklı işletim sistemleri üzerinde gerçekleştirmek için, Libuv diye isimlendirilen ve reactor pattern i uyarlamış bir nomalleştirme kütüphanesi geliştirmiş.
  4. reactor pattern ile event-loop uyarlanmış

Yukarıdaki liste çok ama çok genel ve üste seviye bir anlatım ama genel olarak bir fikir vermiştir umarım.

Bu konuyu da bir kaç görsel kullanarak detaylandırmaya çalışırsak,

Node.js mimari
Java multi thread

Yukardaki resim ,klasik bir multi threaded Java Web Uygulamasını temsil etmekte, konumuz, “Node ve single-threaded event-loop mimarisi” olduğu için burda çok detaya girmeyeceğim, sadece ;

  1. Multi-threading alt yapısı kullanılarak uygulama seviyesinde gelen istekleri , olabildiğince Non-Blocking olarak handle etmeye çalışan bir yapı gördüğümüzü
  2. Ama sonrasında, sistemin non-blocking çalışmaya devam edemeyeceğini (Örneğin DB erişimi, dosya yazma/okuma işlemleri için) blocking den kurtulamadığını
  3. Ve, aynı client/istemciden gelen diğer isteklerde, Multi threading bir yapı(Thread pool) olsada,request / istek döngüsünde bile “blocking” i yine önleyemediğimizi

belirtmek istedim .

Şimdi, bir de Node.js tarafından bakalım,

Node.js ve Non-blocking
  1. İlk olarak, gelen istekler, bir thread havuzunda değil, Event loop mimarisinde / yapısında karşılanıyor.
  2. Gelen tüm istekler, anında bir callback referansına dönüştürülmekte (Libuv ve C/C++ la yazılan standart kütüphaneleri hatırlayın, burda her bir call-backimiz için bir C++ pointeri hafızıya kaydediliyor) ve bu istekler POSIX ASYNC havuzuna delegate ediliyor.(İşletim sistemi seviyesinde, NT mimarisine benzer bir yapıda ASYNC bir thread pool) sonra eğer gerek varsa DB ve Dosya işlemleri yine Non-blocking olarak yapılıyor sonrasında referansı saklanan (C++ pointerimiz) “call backimiz” çalışiyor işlem gerçekleşiyor ve istemciye cevap iletiliyor.
  3. Görüldüğü gibi , Hem request / isteklerin karşılanmasında(Event-loop sayesinde, single thread ile) hem de işletim sistemi seviyesinde ki islemleri (Libuv sayesinde, multi-threaded olarak)  yine non-blocking olarak handle edebilmemize olanak sağlanıyor. (Not :  LibUv in yönettiği POSIX threads havuzu, multi-thread bir yapı, Node.js in Posix e kadar olan kısmı single -threaded )

şimdi bir de aşağıdaki göresele bakalım ,

Bu  sistemin(Non-Blocking I/O, ASYNC ve event-loop ) çok ciddi bir performans /  ve esneklik kazandırdığını yukardaki grafikden de görebilrisiniz.

Yukarıdaki grafik’de , PayPal tarafından yayınlanan bir bench-mark ı görmekteyiz.  , 6 Çekirdikli , güçlü bir makinade çalışan, Java ve populer bir Java web framework ile yazılmış uygulamanın , çok daha zayıf bir makinede çalışan node ile yazılmış bire bir aynı versiyonunun karşılaştırmasını görüyoruz.

  1. gördüğünüz gibi , Node ile ; %50 daha kısa sürede(geliştirme süresi), %33 daha az kod ile ciddi bir verim artışı
  2. Java uygulamasının çalışdığı server çok daha güçlü olmasına rağmen, node ile yazılan uygulama minumum % 33 daha hızlı çalıştığı ve en az iki kat daha hızlı request handle ettiğini görmekteyiz.

Hemen belirtelim, Node.js in CPU intesive / Yüksek işlem gücü gerektiren durumlarda ki zaafiyeti; C/C++ yada Rust gibi çok hızlı dillerle yazılan bindigler ile giderilebilmektedir.

Çok basit ama etkili bir cluster yeteneği ile bir kaç satır kod ile, aynı yapı içinde concurent / eşzamanlı çalışan kopyalar / instances oluşturabilir ve single threading e ciddi esneklik kazandırabilirsiniz.

Bir sonraki yazıda, önce genel olarak event loop mimarisine ve sonrasında event loop un Node.js ve Javascript ile yazdığımız uygulamalardaki yerine bakmaya çalışalım.

 

1 thought on “Node.js Mimari Yapı – Node Internal, Event-loop ve Libuv hakkında

Leave a Reply

Your email address will not be published. Required fields are marked *