Tổng quan về node js

Giới thiệu chung

Javascript ngày càng trở nên phổ biến hơn với nhiều tính năng và các thư viện được hỗ trỡ cho developer, điều đó khiến cho các giao diện web càng trở nên sinh động hơn. Mọi thứ mà chúng ta có thể làm được trên web ngày nay là Javascript có thể chạy được trên server, cũng như chạy được trên browser, điều này là khó tưởng tượng trong những năm trở lại đây, hoặc nó chỉ đóng gói trong môi trường sandboxed như Flash hoặc JavaApplets. Thật vậy, rõ ràng rằng từ xưa đến nay chúng ta vẫn quan niệm rằng lập trình bên phía server chỉ dùng được những ngôn ngữ như php, ruby, ... nhưng từ khi node js ra đời nó mang đến tư tưởng mới cho việc lập trình cả bên phía server cũng như bên phía client. Và đối với node js thì chúng ta biến những điều mà trước kia chỉ có thể thao tác được bên phía client thì nay cũng thao tác và xử lý được trên server, và ngược lại.

Node js được viết bằng ngôn ngữ javascript, nó là một trình biên đóng gói của Google’s V8 JavaScript engine, libuv platform abstraction layer, và một thư viện lõi được viết bằng Javascript. Mục tiêu của Node js là làm cho web có khả năng push như trong một số ứng dụng gmail. Node js cung cấp công cụ giúp lập trình viên có thể làm việc trong non-blocking, mô hình I/O . Sau hơn 20 năm nghiên cứu, xây dựng và phát triển, nhóm kĩ sư đã cho ra đời sản phẩm ứng dụng web node js chạy thời gian thực và kết nối 2 chiều client và server, cho phép trao đổi dữ liệu một cách tự do.

Node js làm việc như thế nào?

Ý tưởng chính của Node js là sử dụng non-blocking, hướng sự vào ra dữ liệu thông qua các tác vụ thời gian thực một cách nhanh chóng. Bởi vì, Node js có khả năng mở rộng nhanh chóng, khả năng xử lý một số lượng lớn các kết nối đồng thời bằng thông lượng cao. Nếu như các ứng dụng web truyền thống, các request tạo ra một luồng xử lý yêu cầu mới và chiếm RAM của hệ thống thì việc tài nguyên của hệ thống sẽ được sử dụng không hiệu quả. Chính vì lẽ đó giải pháp mà Node js đưa ra là sử dụng luồng đơn (Single-Threaded), kết hợp với non-blocking I/O để thực thi các request, cho phép hỗ trợ hàng chục ngàn kết nối đồng thời. So sánh xử lý request truyền thống và request trên node js

Tính toán nhanh như sau: giả sử mỗi một luồng đính kèm 2MB dữ liệu được gửi lên server chạy trong hệ thống với 8GB RAM hệ thống, thì có tối đa khoảng 4000 kết nối đồng thời. Tuy nhiên với Node js thì nó có khả năng mở rộng ra cả hàng triệu kết nối cùng lúc. Thật vậy, cơ chế của Node js Application xử lý Model như sau:

  • Client gửi request đến Web Server
  • Node js Web Service duy trì trong nội bộ một luồng giới hạn để cung cấp dịch vụ cho Client Request.
  • Node js Web Service nhận tất cả các request và đặt chúng vào một trong Queue. Nó được gọi là một Event Queue.
  • Node js Web Service nội bộ có một thành phần được gọi là "Event Loop".
  • Event Loop chỉ sử dụng một luồng đơn để xử lý Model.
  • Event Loop kiểm tra tất cả các Request đặt trong Event Queue. Nếu không có request nào thì chờ request đến vô thời hạn
  • Nếu có request thì sẽ lấy một request từ Event Queue:
  • Khởi động quá trình xử lý tiến trình từ client request
  • Nếu Client Request không chứa nhiều Blocking I/O thì xử lý tất cả mọi thứ và chuẩn bị cho quá trình gửi lại phản hồi cho phía client.
  • Nếu Client Request chứa nhiều Blocking I/O như việc tương tác với cơ sở dữ liệu, tập tin hệ thống, dịch vụ mở rộng, thì nó sẽ thực hiện theo các phương pháp tiếp cận khác nhau.
  1. Kiểm tra các luồng sẵn có từ nội bộ bên trong của request gửi lên
  2. Chọn một luồng và chỉ định cho client request tương ứng với luồng đó
  3. Luồng đó phải có trách nhiệm với reuqest đó, xử lý nó, thực thi các hoạt động Blocking I/O, chuẩn bị các phản hồi và gửi lại cho Event Loop.
  4. Event Loop gửi lại phản hồi tương ứng cho client.

sơ đồ xử lý request từ client của node js.png

Mô tả sơ đồ như sau:

  • Có n số lượng client gửi request lên web service. Chúng ta giả định rằng chúng đều truy cập đồng thời vào ứng dụng Web của chúng ta
  • Chúng ta giả định client của chúng ta là Client-1, Client-2, ...client-n
  • Web server của chúng ta duy trì một vùng các luồng có giới hạn và giả định rằng m là số luồng của vùng luồng đó.
  • Node js Web service nhận các request từ client-1, client-2, ...client-n và đặt chúng vào trong Event Queue
  • Node js Event Loop chọn các request theo dạng một đối một
    • Event Loop chọn client-1 request-1
      • Kiểm tra trong client-1 request-1 có không yêu cầu bất kì hoạt động Blocking I/O hoặc mất nhiều thời gian cho việc tính toán phức tạp
      • Request này thì đơn giản và Non-blocking I/O, nó không có đòi hỏi một xử lý nào riêng biệt.
      • Event Loop xử lý tất cả các giai đoạn để cung cấp cho hoạt động của client-1 request-1 (hoạt động ở đây nghĩa là các function của javascript) và chuẩn bị response-1.
      • Event Loop gửi Response-1 đến Client-1
    • Event Loop chọn client-2 request-2
      • Kiểm tra trong client-2 request-2 có không yêu cầu bất kì hoạt động Blocking I/O hoặc mất nhiều thời gian cho việc tính toán phức tạp.
      • Request này thì đơn giản và Non-blocking I/O, nó không có đòi hỏi một xử lý nào riêng biệt.
      • Event Loop xử lý tất cả các giai đoạn để cung cấp cho hoạt động của client-2 request-2 và chuẩn bị response-2.
      • Event Loop gửi Response-2 đến Client-2.
    • Event Loop chọn client-n request-n
      • Kiểm tra trong client-n request-n có không yêu cầu bất kì hoạt động Blocking I/O hoặc mất nhiều thời gian cho việc tính toán phức tạp.
      • Request này thì phức tạp và nhiệm vụ Blocking I/O. Event Loop thì không thể xử lý request này ngay được.
      • Event Loop lựa chọn Thread T-1 từ nội bộ khu vực Thread và chỉ định Client-n Request-n này ánh xạ với Thread T-1
      • Thread T-1 đọc và xử lý Request-n, thực hiện Blocking IO cần thiết hoặc tính toán nhiệm vụ, và hoàn tất để chuẩn bị Response-n.
      • Thread T-1 gửi Response-n này đến Event Loop
      • Event Loop trong công đoạn này gửi Response-n này tới Client-n

Tại đây Client Request thì gọi đến một hoặc nhiều function javascript. Các function Javascript có thể gọi tới các function khác hoặc có thể sử dụng chức năng callback đặc thù.

Tư tưởng của Event Loop được hiểu như sau:

public class EventLoop {
while(true){
    if (EventQueue nhận một JavaScript Function){
        ClientRequest request = EventQueue.getClientRequest();
        if (request requires BlokingIO or takes more computation time) {
            chỉ định request đến Thread T1
        } else {
            Xử lý và chuẩn bị response
        }
    }
}

NPM: The Node Package Manager

Khi thảo luận về Node js thì một điều chắc chắn không nên bỏ qua là xây dựng package quản lý sử dụng các cộng cụ NPM mà mặc định với mọi cài đặt Node js. Ý tưởng của mô-đun NPM là khá tương tự như Ruby-Gems: một tập hợp các hàm có sẵn có thể sử dụng được, thành phần tái sử dụng, tập hợp các cài đặt dễ dàng thông qua kho lưu trữ trực tuyến với các phiên bản quản lý khác nhau.

Danh sách các mô-đun có thể tìm trên web NPM package hoặc có thể truy cập bằng cách sử dụng công cụ NPM CLI sẽ tự động cài đặt với Node js.

Một số các module NPM phổ biến nhất hiện nay là:

  • expressjs.com/ - Express.js, một Sinatra-inspired web framework khá phát triển của Node.js, chứa rất nhiều các ứng dụng chuẩn của Node.js ngày nay.
  • connect - Connect là một mở rộng của HTTP server framework cho Node.js, cung cấp một bộ sưu tập của hiệu suất cao "plugins" được biết đến như là trung gian; phục vụ như một nền tảng cơ sở cho Express
  • socket.io and sockjs - Hai thành phần Server-side websockets components nổi tiếng nhất hiện nay.
  • Jade - Một trong những engines mẫu, lấy cảm hứng từ HAML, một phần mặc định trong Express.js.
  • mongo and mongojs - MongoDB hàm bao để cung cấp các API cho cơ sở dữ liệu đối tượng trong MongoDB Node.js
  • redis - thư viện Redis client.
  • coffee-script - CoffeeScript trình biên dịch cho phép developers viết các chương trình Node.js của họ dùng Coffee.
  • underscore (lodash, lazy) - Thư viện tiện ích phổ biến nhất trong JavaScript, package được sử dụng với Node.js, cũng như hai đối tác của mình, hứa hẹn hiệu suất tốt hơn bằng cách lấy một cách tiếp cận thực hiện hơi khác nhau.
  • forever - Có lẽ là tiện ích phổ biến nhất để đảm bảo rằng một kịch bản nút cho chạy liên tục. Giữ quá trình Node.js của bạn lên trong sản xuất đối mặt với bất kỳ thất bại không ngờ tới.

Ví dụ về Node.js

Ứng dụng chat

Chat là thời gian thực, ứng dụng đa người dùng điển hình nhất, thông qua nhiều giao thức độc quyền và mở chạy trên các cổng không chuẩn, với khả năng thực hiện tất cả mọi thứ ngày hôm nay trong Node.js với WebSockets chạy qua cổng chuẩn 80.

Các ứng dụng chat thực sự là ví dụ điển hình nhất của Node.js: đó là một ứng dụng trao đổi thông tin nhanh chóng, lưu lượng truy cập cao, dữ liệu chuyên sâu (nhưng / xử lý tính toán thấp) ứng dụng chạy trên các thiết bị phân phối. Nó cũng là một trường hợp tuyệt vời cho học tập, vì nó đơn giản, nhưng nó bao gồm hầu hết các mô hình mà bạn từng được sử dụng trong một ứng dụng Node.js điển hình.

Trong ví dụ đơn giản nhất, chúng tôi có một phòng chat đơn trên trang web của chúng tôi, nơi mọi người đến và có thể trao đổi một hoặc nhiều tin nhắn (trên thực tế tất cả). Ví dụ, có ba người trên tất cả các trang web kết nối với bảng tin của chúng tôi.

Trên phía máy chủ, chúng ta có một ứng dụng đơn giản Express.js mà thực hiện hai điều:

  • GET '/' xử lý yêu cầu phục vụ các trang web có chứa cả một bảng thông báo và một nút "Send" để khởi đầu vào tin nhắn mới.
  • Một máy chủ WebSockets mà nghe cho thư mới được phát ra bởi các client WebSocket.

Bên phía Client, chúng ta có một trang HTML với một vài xử lý thiết lập cho một sự kiện nhấn vào nút "Send", mà chọn lên các thông báo đầu vào và gửi nó xuống WebSocket, và một là lắng nghe cho message mới đến client WebSockets khác (tức là, các tin nhắn được gửi bởi người dùng khác, mà bên phía server muốn hiển thị trên toàn bộ các client).

Cùng phân tích quá trình khi một client gửi một message thì diễn ra các xử lý:

  • Trình duyệt bắt sự kiện nhấn nút "Send" thông qua một trình xử lý JavaScript, chọn lên các giá trị từ các trường đầu vào (tức là, các tin nhắn văn bản), và phát ra một thông điệp WebSocket sử dụng máy khách kết nối với máy chủ WebSocket (khởi tạo trên web khởi tạo trang).
  • Phần phía máy chủ của các kết nối WebSocket nhận message và chuyển tiếp nó tới tất cả các client kết nối khác sử dụng các phương thức broadcast.
  • Tất cả các client nhận được message mới như tin nhắn push thông qua một thành phần client-side WebSockets chạy trong trang web. Sau đó, họ lấy nội dung tin nhắn và cập nhật các trang web tại chỗ bằng cách thêm các bài viết mới cho broadcast. ctl_nodejs_2.png

Đây là ví dụ đơn giản nhất. Đối với một giải pháp mạnh mẽ hơn, bạn có thể sử dụng một bộ nhớ cache đơn giản dựa trên các lưu trữ Redis. Hoặc trong một giải pháp tiên tiến hơn, một hàng đợi các message để xử lý việc định tuyến các message đến các client và một cơ chế phân phối mạnh mẽ hơn nên có thể trang trải cho các khoản lỗ kết nối tạm thời hoặc tin nhắn lưu trữ cho khách hàng đã đăng ký khi họ offline. Nhưng bất kể những cải tiến mà bạn thực hiện, Node.js sẽ vẫn được hoạt động theo các nguyên tắc cơ bản: phản ứng với các sự kiện, xử lý nhiều kết nối đồng thời, và duy trì tính lưu động trong trải nghiệm người dùng.

Tài liệu tham khảo

Why The Hell Would I Use Node.js? A Case-by-Case Tutorial

Proccess model single thread and event loop

Node.js w/1M concurrent connections!