...

Nuhil Mehdy

Polyglot Programmer, White Hat Hacker, Tech Enthusiast by Choice!

নোড.জেএস (Node.js) কি এবং কিভাবে কাজ করে

ভূমিকা

ব্যাক এন্ড ডেভেলপমেন্টে Node.js এর উপর ভিত্তি করে বেশ কিছু ওয়েব ফ্রেমওয়ার্ক এর সফল ব্যবহার, প্রায় সব রকম ইউটিলিটি টুল হিসেবে রেডিমেড প্যাকেজের সহজ লভ্যতা এবং ফ্রন্ট এন্ডে অনেক গুলো ভালো ভালো ফ্রেমওয়ার্কের সাম্প্রতিক উন্নতি বেশ ভালো মতই ব্যাকএন্ড, ফ্রন্ট এন্ড বা হাইব্রিড অ্যাপ ডেভেলপমেন্টে জাভাস্ক্রিপ্ট এর প্রভাব বুঝিয়ে দিচ্ছে। আমি এই রাস্তায় একদম নতুন। আমার মনে হয়েছে যেহেতু এই স্ট্যাকে কাজ করতে গেলে সব খানেই জাভাস্ক্রিপ্ট লাগছে তাই আসলেই জাভাস্ক্রিপ্ট কিভাবে কাজ করে, বা এই টুল গুলোর কাজ করার ধরনটাই বা কেমন সেটা জানা খুবি জরুরী। নাহলে সঠিক ভাবে টুল গুলোর ইফিসিয়েন্সি ইউটিলাইজ করা হবে না অথবা উল্টো ভুল ভাবে ব্যবহার করা হয়ে যেতে পারে। তাই আমি গত কয়েক সপ্তাহের লার্নিং রিসোর্সকে একটা জায়গায় গুছিয়ে লিখেছি।

জাভাস্ক্রিপ্ট সম্বন্ধে কিছু ভুল ধারনা

সব ওয়েব ব্রাউজারের সাথে Javascript ইন্টিগ্রেটেড অবস্থায় থাকে। তাই, আমরা কিছু ফিচার দেখতে দেখতে অভ্যস্ত যেগুলোকে Javascript এর অংশ বলে মনে হয় যেমন - DOM Tree, setTimeout ফাংশন, AJAX রিকোয়েস্ট ইত্যাদি। কিন্তু আসলে জাভাস্ক্রিপ্ট যে এনভ্যায়র্নমেন্টে রান করছে সেখান থেকেই এগুলো প্রোভাইড করা হয়। এ ক্ষেত্রে ব্রাউজার এই API গুলো প্রোভাইড করে থাকে এবং window, document বা এরকম গ্লোবাল অবজেক্ট গুলোও ব্রাউজারই ইনিসিয়ালাইজ করে থাকে। তো আমরা যদি, ব্রাউজারের ফিচার গুলো বাদ দিয়ে Javascript কে চিন্তা করি তাহলেও এই ল্যাঙ্গুয়েজটির নিচের বৈশিষ্ট্য গুলো থেকেই যায় -

  • ফার্স্ট ক্লাস সিটিজেন হিসেবে ফাংশনকে ব্যবহার করে (ফাংশনকে আর্গুমেন্ট হিসেবে পাঠানো যায়, এক ফাংশনের মধ্যে থেকে আরেকটি ফাংশনকে রিটার্নও করা যায় ইত্যাদি
  • ইভেন্ট মডেল ব্যবহার করে (এর মাধ্যমে ইভেন্ট ড্রাইভেন প্রোগ্রামিং করা যায়, পক্ষান্তরে যার সাহায্যেই কিনা asynchronous অপারেশন করা সম্ভব হয়)

Node.js কি?

অফিসিয়াল সাইটের সংজ্ঞা অনুযায়ী - “It is a JavaScript runtime built on Chrome’s V8 JavaScript engine. It uses an event-driven, non-blocking I/O model that makes it lightweight and efficient” অর্থাৎ - ক্রোমের V8 জাভাস্ক্রিপ্ট ইঞ্জিনের উপর তৈরি একটি জাভাস্ক্রিপ্ট রানটাইম যা কিনা ইভেন্ট-ড্রাইভেন ইনপুট/আউটপুট মডেল ব্যবহার করে এবং এর কারনে এটি অনেক ইফিসিয়েন্ট। উইকিপিডিয়াতে বলা - “Node.js is an open-source, cross-platform JavaScript run-time environment for executing JavaScript code server-side.” এটি একটি ওপেন সোর্স, ক্রস প্ল্যাটফর্ম জাভাস্ক্রিপ্ট রানটাইম এনভ্যায়র্নমেন্ট যার মাধ্যমে সার্ভার সাইডে জাভাস্ক্রিপ্ট কোড এক্সিকউট করা যায়” আমার যে সংজ্ঞাটি পছন্দ সেটা হচ্ছে - এটি একটি ওপেন সোর্স, কোর্স প্ল্যাটফর্ম, ইভেন্ট ড্রাইভেন রানটাইম এনভ্যায়র্নমেন্ট যার মাধ্যমে জাভাস্ক্রিপ্ট ব্যবহার করে এবং নন-ব্লকিং ইনপুট/আউটপুট মডেল ফলো করে সার্ভার সাইড, হাইব্রিড বা নেটওয়ার্কিং অ্যাপ্লিকেশন ডেভেলপ করা যায় :)

কেন জাভাস্ক্রিপ্ট? - Node.js এর ক্রিয়েটর Ryan Dahl এর মতে -

“JavaScript has certain characteristics that make it very different than other dynamic languages, namely that it has no concept of threads. Its model of concurrency is completely based around events.”

এই পোস্টে আমরা ধাপে ধাপে Node.js এর আর্কিটেকচার সম্বন্ধে জানবো। এরপর সংজ্ঞায় ব্যবহৃত টার্ম গুলোর ব্যবচ্ছেদ করে এর ফিচার গুলো এবং কিভাবে কাজ করে সেটা বোঝার চেষ্টা করবো। সাথে Node.js এর সুবিধা অসুবিধা নিয়েও কথা বলবো।

Node.js আর্কিটেকচার

(4.2.1 ভার্সন অনুসারে)

সহজ ভাবে চিন্তা করলে, আপনার কাছে যদি কিছু জাভাস্ক্রিপ্ট কোড থাকে আর সাথে V8 ইঞ্জিন থাকে তাহলেই আপনি সেই কোড রান করাতে পারেন কোন সমস্যা এবং অন্য যেকোনো টুলের উপর নির্ভরশীলতা ছাড়াই। কিন্তু দুঃখের বিষয় হচ্ছে এরকম একটা সেটআপ এর মধ্যে থেকে আপনি কোন রকম সিস্টেম কল চালাতে পারতেন না অর্থাৎ ফাইল সিস্টেম অপারেশন, নেটওয়ার্ক কানেকশন এসব করতে পারতেন না। V8 এর কোন ক্ষমতাই নাই এসব কাজ করার। এর কাজ হচ্ছে সোজা সাপটা জাভাস্ক্রিপ্ট কোডকে এক্সিকিউট করা। এখানেই Node.js এর দরকার পরে যায়। V8 কে সাথে নিয়ে, Node.js মূলত তিনটি সাপোর্ট আমাদেরকে দেয় - ১) সিস্টেমের সাথে কাজ করার জন্য কিছু বাইন্ডিং ২) একটি ইভেন্ট লুপ ৩) একটি থ্রেড পুল (ইভেন্ট লুপ এবং থ্রেড পুলের প্রয়োজনীয়তা আমরা সামনেই পড়বো) তার আগে শুধু এটুকু মুখস্ত করে রাখি যে, এই তিনটি জিনিষের কম্বিনেশনের কারনেই আমরা একটি প্ল্যাটফর্ম পাই যার মাধ্যমে asynchronous প্রোগ্রামিং করে নন ব্লকিং ইনপুট/আউটপুট অপারেশন করা সম্ভব হয়। তাহলে ক্ষণিকের জন্য, একটি সিম্পল ডায়াগ্রাম চিন্তা করতে পারি নিচের মত।

Nodejs Architecture By Ryan Dahl

কিন্তু বাস্তবতা সিম্পল না। তাই দয়াকরে নিচের ফিগারে চোখ বুলান, Node.js এর আরেক্টূ ডিটেইল চেহারা দেখার জন্য -

Nodejs Architecture Detail

একদম উপরের লেয়ারে আছে Node.js API যেগুলোকে আমরা Node.js এর কোর API বলতে পারি। এগুলো Javascript এ লেখা এবং ডেভেলপাররা সরাসরি তাদের নোড অ্যাপ্লিকেশন থেকে এই API গুলো অ্যাক্সেস করতে পারে। এর নিচের লেয়ারে আছে Node.js বাইন্ডিংস যেগুলো বস্তুত Javascript কে C/C++ লাইব্রেরীর সাথে যুক্ত করে। সাথে আরও আছে C/C++ Addons যেগুলো মূলত ডাইনামিক্যালি লিঙ্কড কিছু শেয়ারড অবজেক্ট এবং যেগুলো থাকার কারনে চাইলে যেকোনো C/C++ লাইব্রেরীর মাধ্যমে Addon তৈরি করে Node.js এর মধ্যে ব্যবহার করা যেতে পারে।

V8 এঞ্জিন: C/C++ ল্যাঙ্গুয়েজ ব্যবহার করে গুগলের তৈরি একটি ওপেন সোর্স জাভাস্ক্রিপ্ট ইঞ্জিন যা মূলত ডেভেলপ করা হয়েছিল গুগলের ক্রোম ওয়েব ব্রাউজারের জন্য। বলে রাখা ভালো, শুধু ওয়েব ব্রাউজারের জাভাস্ক্রিপ্ট ইঞ্জিন হিসেবে না বরং এটি স্ট্যান্ডঅ্যালোন ভাবেও ব্যবহার করা যায় অথবা যেকোনো C/C++ অ্যাপ্লিকেশনের মধ্যে চাইলে এম্বেড করা যেতে পারে। এই ইঞ্জিনের কাজ হচ্ছে জাভাস্ক্রিপ্ট কোডকে মেশিন কোডে রূপান্তর করা। এই ইঞ্জিন JIT (Just in Time) কম্পাইলার ইমপ্লিমেন্টেশনের মাধ্যমে জাভাস্ক্রিপ্ট কোডকে সরাসরি মেশিন কোডে রূপান্তর করে। অন্যান্য ইঞ্জিনের মত কোন বাইটকোড বা ইন্টারমেডিয়েট কোডে রূপান্তর করে না, যেখান থেকে কিনা ইন্টারপ্রেট করার দরকার হতে পারতো। এই ইঞ্জিন আরও যে গুরুত্বপূর্ণ কাজ গুলো করে থাকে সেগুলো হল - আপনার লেখা জাভাস্ক্রিপ্ট প্রোগ্রামের মেমোরি ম্যানেজমেন্ট, গারবেজ কালেকশন ইত্যাদি।

LibUv: নোড জেএস এর কথা আসলেই অনেকেই শুধুমাত্র V8 ইঞ্জিনের কথাই বেশি করে ফোকাস করে থাকেন এবং অন্যান্য পার্ট গুলোর কথা বেমালুম এড়িয়ে জান। কিন্তু মনে রাখা উচিৎ যে, Node.js এর মধ্যে অনেক গুলো মডিউল আছে যার একটি হচ্ছে V8 ইঞ্জিন আর ঠিক একই রকম আরেকটি গুরুত্বপূর্ণ অংশ হচ্ছে libuv. আমার মতে Node.js এর মুল দুটো অংশ হচ্ছে V8 ইঞ্জিন এবং এই libuv লাইব্রেরী। libuv ছাড়া Node.js এনভ্যায়র্নমেন্টের মুল আকর্ষণী বৈশিষ্ট্য গুলোরই অস্তিত্ব থাকতো না। যা হোক, libuv হচ্ছে মাল্টি প্ল্যাটফর্ম সাপোর্ট করে এবং কম্পিউটার সিস্টেমে asynchronous ইনপুট/আউটপুট অপারেশন খুব সুন্দর ভাবে ম্যানেজ করে, এমন একটি লাইব্রেরী। বলে রাখা ভালো, Node.js প্রথম দিকে এই লাইব্রেরীকে মূলত libev এবং libio লাইব্রেরীর উপড়ে একটি আবস্ট্র্যাকশন লেয়ার হিসেবে ব্যবহার করতো। অর্থাৎ libuv এর নিচে মূলত কনকারেন্ট অপারেশনগুলো এই libio এবং libev হ্যান্ডেল করতো। কিন্তু আস্তে আস্তে libuv লাইব্রেরী নিজেই অনেক ডেভেলপ হয়েছে খুব ইফিসিয়েন্টলি কনকারেন্সি হ্যান্ডেল করার জন্য। এই অবস্থায় আরেকবার মনে করিয়ে দিতে চাই, V8 ইঞ্জিন যেখানে জাভাস্ক্রিপ্ট এক্সিকিউশনের দায়িত্ব নিয়ে থাকে সেখানে libuv হ্যান্ডেল করে Event Loop এবং asynchronous I/O বা ইনপুট/আউটপুট অপারেশন গুলোকে। এর কিছু গুরুত্বপূর্ণ বৈশিষ্ট্য যেগুলো না জানলেই নয় - ফুল ফিচার একটি ইভেন্ট লুপ (অপারেটিং সিস্টেমের উপর ভিত্তি করে এটি epoll, kqueue, IOCP, ইত্যাদির উপর নির্ভরশীল), Asynchronous TCP এবং UDP সকেট, Asynchronous ফাইল সিস্টেম অপারেশন, থ্রেড পুল, সিগনাল হ্যান্ডেলিং সহ আরও অনেক।

c-ares: ব্লকিং এড়িয়ে একাধিক DNS কুয়েরী প্যারালালি পারফর্ম করার জন্য একটি C লাইব্রেরী। Node.js যেহেতু অ্যাপাচি বা এরকম ওয়েব সার্ভারের উপর নির্ভরশীল নয় এবং নিজেই সার্ভার তৈরি করে তাই এই টুলটির প্রয়োজন তো পরছেই, তাই না?

http_parser: আবারও C তে লেখা একটি HTTP রিকোয়েস্ট এবং রেসপন্স পার্সার। এটার প্রয়োজনীয়তাও আগের পয়েন্টের কারনেই।

OpenSSL: SSL(Secure Sockets Layer) এবং TLS (Transport Layer Security) এর জন্য একটি ওপেন সোর্স ইমপ্লিমেন্টেশন। সাথে একে অনেকেই চিনে থাকেন ক্রিপ্টোগ্রাফি লাইব্রেরী হিসেবে। নেটওয়ার্কিং অ্যাপ্লিকেশন ডেভেলপ হবে যে প্ল্যাটফর্মে সেখানে এটা থাকবে না, তা তো হতে পারে না। তাই এই মডিউলের আগমন সঙ্গত কারনেই।

Zlib: এবং আবারও C দিয়ে তৈরি একটি জেনারেল পারপাজ ডাটা কমপ্রেশন লাইব্রেরী.

তো এই হচ্ছে আমাদের প্রয়োজনীয় মডিউল বা পার্ট গুলো যেগুলোর সমন্বয়ে সার্ভার সাইড বা নেটওয়ার্কিং অ্যাপ্লিকেশন ডেভেলপ করার জন্য Node.js তৈরি।

Node.js এর ফিচার

আমরা ইতোমধ্যে Node.js এর আর্কিটেকচার সম্পর্কে একটা ধারনা পেয়েছি। এ অবস্থায় আমরা এর বিভিন্ন ফিচার গুলো আলোচনা করবো এবং সেগুলো কিভাবে কাজ করে সেটাও বোঝার চেষ্টা করবো; সেগুলোর সঠিক ব্যবহার করে সংজ্ঞা অনুযায়ী ইফিসিয়েন্ট অ্যাপ্লিকেশন ডেভেলপ করা সম্ভব কিনা তা যাচাই করবো। তার আগে শুরুতেই জেনে নেই যে, ইভেন্ট ড্রাইভেন প্রোগ্রামিং আসলে কি জিনিষ -

ইভেন্ট ড্রাইভেন প্রোগ্রামিং
এটি একধরনের প্রোগ্রামিং স্টাইল বা ধরন যেখানে প্রোগ্রামের ফ্লো বা এগিয়ে চলার ধাপ গুলো কিছু ইভেন্টের উপর নির্ভর করে। ইভেন্ট মানে কিছু ঘটনা, যেমন - মাউস ক্লিক, কি প্রেস, নেটওয়ার্কে কানেকশনের জন্য রিকোয়েস্ট, সেই রিকোয়েস্টের সাপেক্ষে রেসপন্স, এমনকি সেন্সর আউটপুট বা অন্য একটি প্রোগ্রাম(প্রসেস/থ্রেড) থেকে ম্যাসেজ পাওয়া ইত্যাদি। এতোটুকু শুনে নিশ্চয়ই মনে হচ্ছে যে, এরকম কাহিনী তো যেকোনো GUI অ্যাপ্লিকেশনেই থাকে যেমন মোবাইল অ্যাপ, ডেক্সটপ অ্যাপ এমনকি ব্রাউজারেও তো ঠিক এভাবেই আমরা কাজ করে থাকি। ঠিক তাই, Node.js বস্তুত এই ওয়ার্কিং প্রিন্সিপালকেই সার্ভারে নিয়ে গেছে এবং দেখা গেছে এই টেকনিক নন ব্লকিং ইনপুট/আউটপুট ইউটিলাইজ করার মাধ্যমে অনেক বেশি পরিমাণে কনকারেন্ট কানেকশন হ্যান্ডেল করার ব্যবস্থা করে দেয়। তাহলে এখন একটু বোঝার চেষ্টা করি নন-ব্লকিং ইনপুট/আউটপুট কি জিনিষ এবং এটা কেনই বা দরকারি।

ব্লকিং এবং নন-ব্লকিং I/O
বেশিরভাগ সফটওয়্যার সিস্টেমে যেকোনো রকম সিস্টেম কল যেমন - ফাইল অপারেশন, ডাটাবেজ কুয়েরী এসব ব্লকিং অর্থাৎ এরকম অপারেশন চলা অবস্থায় মুল প্রোগ্রামের এক্সিকিউশন বন্ধ থাকে যতক্ষণ না পর্যন্ত ওই কলের অপর প্রান্তের কাজ শেষ হয় এবং একটা রেজাল্ট রিটার্ন হয়। অর্থাৎ একটা ভ্যারিয়েবল ডিক্লেয়ার করলেন এবং তার ডান পাশে একটা ফাংশন কল করলেন এবং ফাংশনটি অপর পাশে সিস্টেমের সাথে নানা রকম কাজ করে একটা ভ্যালু রিটার্ন করে। তো যতক্ষণ পর্যন্ত না ওই ফাংশনের ভ্যালু এই ভ্যারিয়েবলে এসে জমা হয় ততক্ষণ পর্যন্ত প্রোগ্রাম পরবর্তী স্টেটমেন্ট গুলো এক্সিকিউট করার সুযোগই পাবে না। এটাকে বলতে পারেন ব্লকিং এবং synchronous প্রোগ্রামিং। Node.js এর একটা অন্যতম উদ্দেশ্য হচ্ছে যতটা সম্ভব নন-ব্লকিং I/O অপারেশন ইউটিলাইজ করা। এর জন্য সে ইভেন্ট লুপ এবং আলাদা থ্রেড পুল ব্যবহার করে। যদিও Node.js রানটাইম নিজে সিঙ্গেল থ্রেডেড। এগুলো আমরা একটু পড়েই আলোচনা করবো। তার আগে তাহলে থ্রেডিং বিষয়টা একটু ঘুরে যাই।

যেহেতু সার্ভার সাইড এবং নেটওয়ার্কিং অ্যাপ্লিকেশনই আমাদের আলোচনা মুল উদ্দেশ্য তাহলে অ্যাপাচি ওয়েব সার্ভারের সাপেক্ষেই একটু আলোচনা করি। অ্যাপাচি হচ্ছে মাল্টি থ্রেডেড প্রোগ্রাম অর্থাৎ এর কাছে যখনই একটি রিকোয়েস্ট আসে তখনি সে একটি নতুন থ্রেড তৈরি করে। তাহলে কি দাঁড়ালো? যদি এরকম একটি থ্রেডে একটি ব্লকিং অপারেশন চলে তাহলে ওই থ্রেডে চলা প্রোগ্রামটি কিন্তু থেমে থাকবে এবং অযথাই সিস্টেমের মূল্যবান রিসোর্স দখল করে বসে থাকবে। যদিও আপাত দৃষ্টিতে এটিকে খুব বেশি সমস্যা মনে হয় না কিন্তু যদি ইউজারের সংখ্যা খুব বেশি বেড়ে যায় অর্থাৎ অনেক গুলো ক্লায়েন্ট যদি একটা অ্যাপাচি সার্ভারে রিকোয়েস্ট করে তাহলে কিন্তু অ্যাপাচি অনেক গুলো থ্রেড তৈরি করবে। আর প্রত্যেকটি থ্রেডে যদি কিছু ব্লকিং অপারেশন থাকে তাহলে সিস্টেমের সব রিসোর্স শেষ হতে সময় লাগবে না এবং ক্লায়েন্টরা রেসপন্সও পাবে না। এমনকি নতুন ক্লায়েন্ট রিকোয়েস্টও করতে পারবে না।
Node.js এই সমস্যা সমাধানের জন্য একটু আলাদা স্টাইল ফলো করে। এটি সব রিকোয়েস্টকে একটি থ্রেড থেকেই সার্ভ করে। এই মুল থ্রেডে চলা আপনার প্রোগ্রামের কোড কিন্তু ঠিকি synchronously এক্সিকিউট হয় কিন্তু যখনই সেখানে একটি সিস্টেম কল ঘটে তখনি Node.js সেটাকে ইভেন্ট লুপে পাঠিয়ে দেয় যার সাথে থাকে একটি কলব্যাক ফাংশনও। এতে করে মুল থ্রেড কখনই ফ্রিজ হয়ে থাকবে না এবং নতুন নতুন রিকোয়েস্ট নিতেই থাকবে। যখনি ইভেন্ট লুপের দায়িত্বে থাকা ব্লকিং অপারেশনের কাজ শেষ হয়ে যাবে তখনি ইভেন্ট লুপ সেই কলব্যাক ফাংশনকে এক্সিকিউট করার জন্য V8 এর কাছে পাঠিয়ে দিবে। এই কলব্যাকের কাছে অপারেশন এর রেজাল্টও থাকবে। এতে করে মুল থ্রেড বা মুল প্রোগ্রাম ব্লকিং অপারেশনের কারনে ব্লক হয় না। তাহলে এবার ইভেন্ট লুপের এই কাজকে আরও বিস্তারিত ভাবে জানতেই হচ্ছে এখন -

একটা বানীঃ

“Node.js আপনার কোডের জন্য একটি সিঙ্গেল থ্রেড ব্যবহার করে আর বাদ বাকি সব কিছু পাশপাশি একসাথে (simultaneously) রান করে।”

এই simultaneous আচরনটি ইমপ্লিমেন্ট করা হয় কনকারেন্ট অপারেশনের এর মাধ্যমে। মনে রাখা জরুরি যে, প্যারালাল অপারেশন এবং কনকারেন্ট অপারেশন কিন্তু এক না। প্যারালাল অপারেশন গুলো একদমই এক অপরের থেকে আলাদা ভাবে চলে যার জন্য বস্তুত আলাদা সিপিইউ কোর দরকার হয়। কিন্তু কনকারেন্ট অপারেশন গুলো সিঙ্গেল কোর সিপিইউ -এ চলতে পারে নিজেদের মধ্যে রিসোর্স শেয়ার করার জন্য শিডিউলিং বা সিমিলার টেকনিক ফলো করে। এখানে, আমি আরেকবার libuv এর কথা বলব কারন - থ্রেড পুলের ওয়ার্কার থ্রেড গুলোর মধ্যে এই যে জটিল কনকারেন্ট অপারেশনের ব্যবস্থা করে দেয়া, সেটা কিন্তু এই libuv -ই করে দেয়। আপনাকে কষ্ট করে অ্যালগরিদম লিখতে হয় না.

ইভেন্ট লুপ

ব্রাউজারে ইভেন্ট লুপ - Node.js এর ইভেন্ট লুপের কাহিনী বোঝার আগে চলুন আগে ওয়েব ব্রাউজারের ইভেন্ট লুপের সাথে একটু পরিচিত হই যা কিনা আমরা সবসময়ই অজান্তেই ব্যবহার করছি। এতে করে, Node.js এর ইভেন্ট লুপ বুঝতে সুবিধা হবে। যদি আপনি জীবনে একবারও এরকম কোড লিখে থাকেন “OnClick(alert(‘Hello’))” তার মানে ইতোমধ্যেই আপনি ইভেন্ট লুপ ব্যবহার করে ফেলেছেন। এক্ষেত্রে আপনি alert(‘Hello’) নামক কলব্যাক ফাংশনটিকে ব্রাউজারের ‘click’ ইভেন্টের সাথে রেজিস্টার করেছেন। এরপর যখনি ক্লিক করেছেন তখনি এই কলব্যাক ফাংশনটি এক্সিকিউট হয়েছে। Node.js এই প্রিন্সিপালকেই সার্ভার সাইডে নিয়ে গেছে libuv এর সাহায্যে। মনে আছে, আমরা যে জেনেছিলাম libuv এর মধ্যেই event loop ঘটে?

ব্রাউজারের জাভাস্ক্রিপ্ট রানটাইম এর মধ্যে একটি ম্যাসেজ কিউ বা কলব্যাক কিউ থাকে। এর কিউ এর মধ্যে থাকে কিছু ম্যাসেজ এবং তার সাথে সম্পৃক্ত কলব্যাক ফাংশন। যখনি কোন ইভেন্ট ঘটে যেমন মাউস ক্লিক, একটা রিকোয়েস্টের রেসপন্স বা এমনকি একটা সেটটাইম আউটের টাইমারের সমাপ্তি; তখনি একটা ম্যাসেজ বা কলব্যাক সেই কিউ এর মধ্যে জমা হয়। এরপর ইভেন্ট লুপ সেই কিউ থেকে প্রথম কলব্যাকটি তুলে নেয় এবং V8 এর কল স্ট্যাকে জম করে। আর যদি সেই ফাংশনটি ওই মুহূর্তে কল স্ট্যাকের উপড়ে থাকে তাহলে V8 সেটাকে সাধারণ ভাবে এক্সিকিউট করে ফেলে। কথা বাড়ানোর আগে যেহেতু কল স্ট্যাক টার্ম চলে আসলো তাহলে এটা নিয়ে দুটো লাইন বলে ফেলা ভালো। পরে কাজে দিবে। এখানে Call Stack হচ্ছে V8 ইঞ্জিনের মধ্যে থাকা একটা ডাটা স্ট্রাকচার যেখানে মূলত একটা ট্র্যাকিং থাকে যে এই মুহূর্তে আপনার প্রোগ্রামের কোন ফাংশন এক্সিকিউট হচ্ছে। বলে রাখা ভালো V8 ইঞ্জিনের দুটো মুল অংশের মধ্যে এই কল স্ট্যাক একটা আর মেমোরি ম্যানেজমেন্টের জন্য হিপ একটা।

এ অবস্থায় একটা উদাহরণ দেখে নেয়া যাক -

function init() { 
    var link = document.getElementById("foo"); 
  
    link.addEventListener("click", function changeColor() { 
        this.style.color = "burlywood"; 
    }); 
}

init();

এই প্রোগ্রাম অনুযায়ী একজন ইউজার যখন foo এলিমেন্টে ক্লিক করে অর্থাৎ click ইভেন্ট ফায়ার করে তখন ম্যাসেজ কিউয়ের মধ্যে একটি ম্যাসেজ এবং সাথে একটি কলব্যাক (changeColor) জমা হয়। এরপর ইভেন্ট লুপ (যা কিনা একটি সেমি ইনফাইনাইট লুপ) এই কিউয়ের মধ্যে থাকা ম্যাসেজকে ডিকিউ করে বা বের করে আনে এবং কল স্ট্যাক তথা মুল জাভাস্ক্রিপ্ট ইঞ্জিনের দায়িত্বে ছেড়ে দেয়। আর কল স্ট্যাক ফ্রি থাকলে সেটাকে পাওয়া মাত্রই এক্সিকিউট করে ফেলে।

Browser Event Loop{.img-fluid}

আরেকটি উদাহরণ দেখে আবারও ইভেন্ট লুপের দায়িত্ব সম্পর্কে একটু বুঝি। নিচের প্রোগ্রামটা খেয়াল করুন -

console.log(“Hello”);

setTimeout(function(){ 
    alert("JS"); 
}, 3000);

console.log(“World”);

এই প্রোগ্রাম যদি V8 ইঞ্জিনের কাছে যায় তাহলে কি ঘটবে? প্রথমের কনসোল লগ করার ফাংশনটি যেহেতু ব্লকিং অপারেশন নয় সেহেতু খুব দ্রুতই কল স্ট্যাক সেটাকে এক্সিকিউট করবে এবং আউটপুট Hello লগ হবে। এরপর, কল স্ট্যাকে setTimeou() ফাংশনটি জমা হবে। এর মধ্যে যখন ঢুকবে তখন সেখানে ব্রাউজারের প্রোভাইড করা একটি স্পেশাল ফাংশন বা এপিআই কল পাওয়া যাবে। এর মধ্যে আছে একটি কলব্যাক ফাংশন। কিন্তু এটি কোন ইভেন্টের সাথে জড়িত না। এক্ষেত্রে ইভেন্ট হিসেবে ধরা যেতে পারে ৩ সেকেন্ডের একটি টাইমারকে। তো, সেই ফাংশনকে ব্রাউজার একটি টাইমারের সাপেক্ষে অপেক্ষা করাবে। (উপরের ফিগারে ডান দিকে যে লালচে পার্ট টুকু দেখা যাচ্ছে সেখানে এটি ঘটবে। ওই এরিয়াকে Web API এরিয়া মনে করে নিতে পারেন যা জাভাস্ক্রিপ্ট রানটাইমের জন্য ব্রাউজারের পক্ষ থেকে একটি সুবিধা মূলক ফিচার।) ইতোমধ্যে কল স্ট্যাক থেক setTimeout ফাংশন সরে যাবে (যেহেতু ওটার মধ্যে আর কিছু নাই বা রিটার্নও নাই) এবং কনসোলে World প্রিন্ট করার জন্য log ফাংশন স্ট্যাকেজমা হবে এবং কাজ শেষ করে প্রিন্টও করবে। অন্যদিকে, ৩ সেকেন্ডের টাইমার শেষ হলে সেই কলব্যাক ফাংশনটি ম্যাসেজ কিউ -এ এসে জমা হবে। এরপর নিরলস ভাবে ঘুরতে থাকা ইভেন্ট লুপ যখনি এটাকে দেখতে পাবে তখনি ধরে কল স্ট্যাকে পুশ করবে আর কল স্ট্যাক এবার পাবে alert() ফাংশনকে এবং এটাকে এক্সিকিউট করে ফেলবে। আর তাই পুরো প্রোগ্রাম রান করার সময় প্রথমে Hello প্রিন্ট হবে, এরপর একটি ব্লকিং বা স্লো অপারেশন থাকা সত্ত্বেও এক্সিকিউশন না থেমে World প্রিন্ট করবে। অতঃপর JS প্রিন্ট করবে। এভাবে আমরা ইভেন্ট লুপের মাধ্যমে asynchronous প্রোগ্রামিং করে কিভাবে নন-ব্লকিং এক্সিকিউশন করা যায় সেটা দেখলাম।

Node.js এ ইভেন্ট লুপ - যেহেতু ব্রাউজারের মধ্যেকার ইভেন্ট লুপের কাজের ধরন সম্পর্কে আমরা একটু ধারনা পেয়েছি এখন আসি নোড জেএস -এ এর প্রভাব এবং কাজের স্টাইলে। মূলত libuv এর মধ্যকার এই ইভেন্ট লুপ এবং সাথে থাকা থ্রেড পুলই Node.js কে সেই ক্ষমতাটা পেতে সাহায্য করে যার মাধ্যমে Node.js নিজে সিঙ্গেল থ্রেডে চলেও অনেক অনেক কনকারেন্ট রিকোয়েস্ট হ্যান্ডেল করতে পারে।

Nodejs Event Loop

Node.js এর ইভেন্ট লুপের সাথে মূলত একটি ইভেন্ট কিউ এবং একটি থ্রেড পুল সম্পর্কিত। এক্ষেত্রে Node.js বাইন্ডিং এর মাধ্যমে Node.js এর কোর API গুলো এই libuv এর সাথে যোগাযোগ মেইন্টেইন করে (যখনি নন-ব্লকিং I/O অপারেশন করার দরকার পরে)। libuv মূলত বিভিন্ন ইভেন্টের সাথে ফাংশনের রেজিস্ট্রেশন করে এবং সেই সম্পর্কিত কলব্যাক ফাংশনকে একটি নন-ব্লকিং ওয়ার্কার পুল বা থ্রেড পুলে পাঠিয়ে দেয়; যখন একটি ইভেন্ট ঘটে।

এবার একটি Node.js প্রোগ্রাম দেখি -

var http = require('http');
var server = http.createServer().listen(3000);

server.on('request', function (request, response) {
    response.end('Hello World!');
});

console.log("Listening on port 3000...")

// Note: যদিও এভাবে এই কোডটি লেখা হয়ে থাকে না কিন্তু request ইভেন্টের অস্তিত্ব বোঝাতে এভাবে লেখা

প্রথমত এই কোডটিকে রান করালে কনসোলে শুধুমাত্র “Listening on port 3000…” দেখা যাবে কারন এর আগের স্টেটমেন্ট গুলো ইভেন্ট ড্রাইভেন এবং এখন পর্যন্ত সেরকম কোন ইভেন্ট ঘটে নি। প্রথমবার যখন এই কোডকে এক্সিকিউট করা হয় তখন Node.js একটি রিকোয়েস্ট (request) ইভেন্টকে রেজিস্টার করে ফেলে (প্রোগ্রামের ধরন অনুযায়ী একাধিক ইভেন্ট রেজিস্টার হতেই পারে)। যখন এই স্ক্রিপ্ট এর এক্সিকিউশন শেষ হয়ে যায় তখন Node.js ইভেন্ট লুপে চলে যায়। ইতোমধ্যে এর ইভেন্ট লুপে একটি ইভেন্ট জমা পরে যেটা হচ্ছে “on request” এবং যার সাথে একটি কলব্যাকও অ্যাটাচ করা আছে। অর্থাৎ এই ইভেন্ট লুপ V8 এর কল স্ট্যাককে কাজে লাগিয়ে সেই কলব্যাককে এক্সিকিউট করবে তখনি, যখন “request” ইভেন্টটি ঘটবে। আর রিকোয়েস্ট ইভেন্ট ঘটানোর জন্য আমরা ব্রাউজারে বা টার্মিনালে (curl দিয়ে) http://localhost:3000 ইউআরএল হিট করতে পারি। তখনি সার্ভারে একটি রিকোয়েস্ট ইভেন্ট ঘটবে আর ইভেন্ট কিউ -এ এর কলব্যাকটি জমা পরবে। অতঃপর ইভেন্ট লুপ সেখান থেকে ওই কলব্যাককে পিক করে এক্সিকিউট করার জন্য V8 এর কাছে পাঠাবে। এক্ষেত্রে কলব্যাক ফাংশনটি একটি রেসপন্স ম্যাসেজ লিখে থেমে যাবে।

তো, এই সার্ভারে এখন যতই রিকোয়েস্ট আসুক না কেন, সেগুলো ‘request’ ইভেন্টকে ট্রিগার করবে এবং এর কলব্যাক ফাংশন সহ ইভেন্ট কিউ-এ জমা হবে। আবার ইভেন্ট লুপ এখান থেকে এই কলব্যাককে নিয়ে এক্সিকিউশনের জন্য পাঠাবে। এভাবে চলতে থাকবে।

এবার নিচের প্রোগ্রামটা দেখি -

var http = require('http'),
    fs = require('fs');

var server = http.createServer().listen(3000);

server.on('request', function (request, response) {
    fs.readFile('file.txt', 'utf-8', function (err, data) {
        response.end(data);
    });
});

console.log("Listening on port 3000...");

এটার ক্ষেত্রেও আগের মতই ঘটনা ঘটবে কিন্তু এখানে রিকোয়েস্ট ইভেন্টের কলব্যাক ফাংশনের মধ্যে একটি ব্লকিং অপারেশন (ফাইল রিড) আছে। আর তাই যখন রিকোয়েস্ট ইভেন্টের কলব্যাক এক্সিকিউট হবে তখন ইভেন্ট লুপ, এর মধ্যে থাকা ফাইল অপারশনকে থ্রেড পুলে ডেলিগেট করে দিবে। থ্রেড পুলে থাকা একটি থ্রেড তখন যথাযথ ভাবে ফাইল অপারেশন সম্পন্ন করে সেই ফাইল অপারেশনের সাথে যে আরেকটি কলব্যাক জড়িত আছে সেটিকে ইভেন্ট কিউ-এ পুশ করে দিবে (ফাইল ডাটা সহ)। এরপর আগের মতই এই ভিতরের কলব্যাকটি ইভেন্ট-কিউ থেকে বের হলে এটির এক্সিকিউশনও সম্পন্ন হবে।

এখানে একটা প্রশ্ন আসতে পারে যে, তাহলে Node.js ও তো শেষ নাগাদ মাল্টিপল থ্রেড ব্যবহার করছেই তাহলে এটা আলাদা হল কিভাবে? আসলে, Node.js সিঙ্গেল একটা থ্রেড থেকেই সব রিকোয়েস্ট হ্যান্ডেল করছে, আর ব্লকিং অপারেশন গুলোকে আলাদা আলাদা থ্রেডে ভাগ করে দিয়ে কাজ করিয়ে নিচ্ছে। এখানে কোন থ্রেড অলস বসে থাকছে না আর তার অলস বসে থাকার জন্য মুল থ্রেডও ফ্রিজ হয়ে বসে থাকছে না।

একটা জিনিষ মনে রাখা জরুরী, Node.js এর সিঙ্গেল এবং মেইন থ্রেডকে যদি ইচ্ছা করেই ব্লকিং কোড ব্যবহার করে ফ্রিজ করে দেয়া হয় তাহলে কিন্তু শুরুতেই বাধা। এই থ্রেড নতুন রিকোয়েস্ট নিতে পারবে না।

আমার পছন্দের একটি ডায়াগ্রাম

নিচের ডায়াগ্রামটি আমার কাছে অনেকটা বোধগম্য মনে হয়েছে। যেখানে দেখা যাচ্ছে ইউজার অ্যাপ্লিকেশন থেকে V8 ইঞ্জিনের যোগাযোগ। এরপর Node API এর মাধ্যমে libuv তথা ইভেন্ট লুপের সাথে সম্পর্ক। আবার libuv এর মধ্যেকার ইভেন্ট কিউ এবং থ্রেড পুল / ওয়ার্কার থ্রেডের মধ্যের যোগাযোগ। অতঃপর সব ঘুরে ইউজার অ্যাপ্লিকেশনের কাছে রিটার্ন পাথ। এখানে সমগ্র Node.js এর কোন প্রধান মডিউলকেই এড়িয়ে যাওয়া হয় নি।

Nodejs Diagram

Node.js ব্যবহারের সুবিধা অসুবিধা

সুবিধা

  • Asynchronous I/O ব্যবহারের মাধ্যমে অনেক বেশি পরিমাণে ইউজার রিকোয়েস্ট নির্ভর অ্যাপ্লিকেশন ডেভেলপমেন্ট সম্ভব।
  • যেহেতু Node.js তৈরি হয়েছে জাভাস্ক্রিপ্ট এর উপর ভিত্তি করেই তাই ব্যাক এন্ড ফ্রন্ট এন্ড সবখানেই একটা ল্যাঙ্গুয়েজ ব্যবহার করেই খুব দ্রুত ডেভেলপমেন্ট সম্ভব। টেকনোলজি সুইচের দরকার পরে না।
  • অন্যান্য স্ট্যাকের মত আলাদা করে সার্ভার টুল, তার সঙ্গে ডেপ্লয়মেন্ট ভিত্তিক কনফিগারেশন জানার প্রয়োজন পরে না কারণ Node.js এর সাথেই সার্ভার টেকনোলজি বিল্ট ইন এবং ডেপ্লয়মেন্টও সহজ।
  • অ্যাক্টিভ কমিউনিটির অবদানের কারনে npm এর মত রিপজিটরিতে যুক্ত হচ্ছে অনেক অনেক রেডি প্যাকেজ যেগুলো ব্যবহার করে প্রায় সব রকম টুলস, সফটওয়্যার ডেভেলপ করা সম্ভব

অসুবিধা

  • CPU ইন্টেন্সিভ টাস্ক যেমন রিপোর্ট জেনারেট বা অ্যানালাইটিকস ভিত্তিক সফটওয়্যার ডেভেলপমেন্টে অসুবিধা (যদিও থ্রেড গুলোকে মাল্টিপল কোরে ডিস্ট্রিবিউট করে এই সমস্যা সমাধান করা যায়)
  • ইভেন্ট ড্রাইভেন মেথডোলজি নিয়ে স্পষ্ট ধারনা না থাকলে এবং এর জন্য খারাপ মানের কোডিং এর কারনে উল্টো নিম্ন মানের সফটওয়্যার তৈরি হতে পারে। যেমন - সঠিকভাবে কলব্যাক হেল ম্যানেজ করতে না পারা।

Share With World
comments powered by Disqus