Chuyển đến nội dung chính

NodeJS architecture concept (p2)

Tiếp nối series giới thiệu về NodeJs for mah friends... Ở phần 1 trước mình đã viết vềmối quan hệ giữa NodeJS và V8. Trong phần 2 này mình sẽ tiếp tục viết về NodeJS architecture core. Mọi người đón đọc nhé

1. Các khái niệm cơ bản trước Nodejs

1.1 Blocking và Non-blocking I/O

I/O là quá trình giao tiếp (lấy dữ liệu vào, trả dữ liệu ra) giữa một hệ thống thông tin và môi trường bên ngoài. Với CPU, thậm chí mọi giao tiếp dữ liệu với bên ngoài cấu trúc chip như việc nhập/ xuất dữ liệu với memory (RAM) cũng là tác vụ I/O. Trong kiến trúc máy tính, sự kết hợp giữa CPU và bộ nhớ chính (main memory – RAM) được coi là bộ não của máy tính, mọi thao tác truyền dữ liệu với bộ đôi CPU/Memory, ví dụ đọc ghi dữ liệu từ ổ cứng đều được coi là tác vụ I/O.
Do các thành phần bên trong kiến trúc phụ thuộc vào dữ liệu từ các thành phần khác, mà tốc độ giữa các thành phần này là khác nhau, khi một thành phần hoạt động không theo kịp thành phần khác, khiến thành phần khác phải rảnh rỗi vì không có dữ liệu làm việc, thành phần chậm chạp kia trở thành một bottle-neck, kéo lùi hiệu năng của toàn bộ hệ thống.
Dựa theo các thành phần của kiến trúc máy tính hiện đại, tốc độ thực hiện tiến trình phụ thuộc:
· CPU Bound: Tốc độ thực hiện tiến trình bị giới hạn bởi tốc độ xử lý của CPU.
· Memory Bound: Tốc độ thực hiện tiến trình bị giới hạn bởi dung lượng khả dụng và tốc độ truy cập của bộ nhớ.
· Cache Bound: Tốc độ thực hiện tiến trình bị giới hạn bởi số lượng ô nhớ và tốc độ của các thanh cache khả dụng.
· I/O Bound: Tốc độ thực hiện tiến trình bị giới hạn bởi tốc độ của các tác vụ I/O.
Blocking I/O: Yêu cầu thực thi một IO operation, sau khi hoàn thành thì trả kết quả lại. Process/Theard gọi bị block cho đến khi có kết quả trả về hoặc xảy ra ngoại lệ.
Nonblocking I/O: Yêu cầu thực thi IO operation và trả về ngay lập tức (timeout = 0). Nếu operation chưa sẵn sàng để thực hiện thì thử lại sau. Tương đương với kiểm tra IO operation có sẵn sàng ngay hay không, nếu có thì thực hiện và trả về, nếu không thì thông báo thử lại sau.

1.2 Synchorous và Asynchorous

Synchronous: Các sự kiện diễn ra theo thứ tự. Một sự kiện chỉ được bắt đầu khi sự kiện trước kết thúc.
Asynchronous: Không theo thứ tự, các hành động có thể xảy ra đồng thời hoặc chí ít, mặc dù các hành động bắt đầu theo thứ tự nhưng kết thúc thì không. Một hành động có thể bắt đầu (và thậm chí kết thúc) trước khi hành động trước đó hoàn thành.
Sau khi gọi hành động A, ta không trông chờ kết quả ngay mà chuyển sang bắt đầu hành động B. Hành động A sẽ hoàn thành vào một thời điểm trong tương lai, khi ấy, ta có thể quay lại xem xét kết quả của A hoặc không. Trong trường hợp quan tâm đến kết quả của A, ta cần một sự kiện Asynchronous Notification thông báo rằng A đã hoàn thành.
Một ví dụ về bất đồng bộ:
setTimeout(()=>{   console.log("hello1")},2000)setTimeout(()=>{   console.log("hello")},1000)setTimeout(()=>{   console.log("hello2")},3000)
Output: hello, hello1, hello2
Vì thời điểm xảy ra sự kiện hành động A hoàn thành là không thể xác định, việc bỏ dở công việc đang thực hiện để chuyển sang xem xét và xử lý kết quả của A gây ra sự thay đổi luồng xử lý của chương trình một cách không thể dự đoán.
> Luồng của chương trình khi ấy không tuần tự nữa mà phụ thuộc vào các sự kiện xảy ra. Mô hình như vậy gọi là Event-Driven.

1.3 Callback

“ Callback function có thể được hiểu nôm na là một function A được truyền vào một function B thông qua danh sách các tham số của B. Lúc này tại hàm B sẽ gọi đến hàm A để thực hiện một chức năng nào đó mà A đang nắm giữ. Javascript là một ngôn ngữ lập trình hướng sự kiện và bất đồng bộ nên callback function đóng vai trò rất quan trọng, bạn sẽ truyền một callback function vào các sự kiện và xử lý bất đồng bộ đó. “
Giả sử ta có một ajax lấy dữ liệu từ client về, ta biết khái niệm của ajax chính là bất đồng bộ, sau khi dữ liệu lấy về được thành công, thực hiện các thao tác trên dữ liệu này.
ES6 định nghĩa 3 state 1 lời gọi hàm không đồng bộ:
· Pending: hàm đang được thực hiện và chưa trả về kết quả. Trong lúc này, nếu cố tình console.log biến kết quả sẽ nhận được output <pending>
· Fulfilled: hàm đã thực hiện xong – thành công và trả về kết quả.
· Rejected: hàm đã thực hiện xong – không thành công. Thường thì sẽ bắt exception tại bước này.

2. Các thành phần và kiến trúc trong Nodejs

Câu chuyện về Nodejs xoay quanh hình vẽ này:




Ta thấy rằng V8 Javascript Engine là trình dịch javascript, rõ ràng không biết gì về vào/ra file cả. Đằng sau cánh gà, thứ mang lại cơ chế Asychronous Event-Driven Non-Blocking I/O là libuv – một thư viện multi-platform hỗ trợ asynchronous I/O. Thứ trong folder deps/uv trên Github repo của Nodejs chính là repo của libuv.

2.1 Libuv – thành phần xử lý vào ra bất đồng bộ

Thread pool: Cách truyền thống là dùng multithreading. Khi gọi một I/O operation, thread chính bỏ đi và tiếp tục thực hiện lệnh khác, việc harder operation này được giao cho một worker theard hoặc một child process. Sau khi operation này hoàn thành trong worker thread, worker threar thông báo lại cho main thread.Vấn đề là ở đây:
· Sinh thread tiêu tốn tài nguyên để tạo mới và cần bộ nhớ cho stack của riêng nó (Như Linux là tối thiểu 2MB mỗi thread by default)
· Các vấn đề thread-safety như deadlock (do các thread chia sẻ tài nguyên), racing conditions, mutex,…
· Tiêu tốn tài nguyên cho thread context switching, scheduler của kernel cần làm việc nhiều hơn
· Worker thread là I/O bound
Những ngoằng loằng trong việc khởi tạo thread có thể được giảm bớt bằng cách sử dụng Thread Pool. Trong mô hình cổ điển, với ví dụ về socket, các webserver cũ thường cho phép tạo 1 process/thread cho mỗi incoming request. Bằng cách ấy, main thread vẫn có thể lắng nghe và accept() các yêu cầu kết nối mới, trong khi worker thread vẫn có thể chờ recv() từ client đã kết nối một cách đồng thời (đồng thời một cách tương đối).
Với nonblocking I/O hành động thử đọc/ghi dữ liệu lên file được gọi là polling (thăm dò). Nếu không có cơ chế thông báo thời điểm file operation sẵn sàng để thực hiện, chương trình của bạn sẽ phải liên tục polling một file trong một vòng lặp vô hạn cho tới khi thành công. Unix cung cấp cơ chế I/O Multiplexing cho phép đồng thời theo dõi nhiều file descriptor để xem có thể thực hiện I/O operation nào đó trên bất kỳ file nào mà không bị hay không. Một lời gọi giám sát có thể block process gọi nó cho tới khi có bất kỳ một file nào sẵn sàng. Ba system call có thể thực hiện I/O Multiplexing là select(), poll() và epoll().
Libuv tự quảng cáo nó là một multi-platform support library with a focus on asynchronous I/O. Kiến trúc của libuv sử dụng epoll, kqueue và dev/poll cho các hệ điều hành Unix-like. epoll là system call của Linux, kqueue là system call tương tự trong các hệ đều hành phát triển từ BSD (Một phiên bản của Unix) trong đó có Mac OS X, cuối cùng là dev/poll cho họ Solaris. Tất cả đều được bọc bởi giao diện uv__oi_t. Riêng Windows thì sử dụng IOCP (Mình cũng chẳng biết nó là cái gì nữa).
Các phương pháp này (có lẽ) chia sẻ chung một cơ chế để theo dõi I/O event notification của nhiều file descriptor khác nhau.

2.2 Callstack trong V8 engine




Call Stack là thành phần của V8. Nó là cấu trúc dữ liệu mà V8 dùng để theo dõi các lệnh gọi hàm. Mỗi khi chúng ta gọi một function, V8 đặt một reference đến function đó bên trong 1 call stack và nó tiếp tục làm như vậy cho mỗi lần gọi lồng nhau của các function khác. Điều này cũng bao gồm các function tự gọi mình(đệ quy). Câu chuyện về call stack của JavaScript có lẽ chúng ta đều đã được nghe kể rất nhiều lần. Ta đều biết rằng stack frame được push mỗi khi một hàm được gọi, và được pop với lệnh return. Sau khi lần lượt xử lý hết các lệnh trong chương trình, call stack trở nên rỗng ruột, một phép màu mang tên event loop sẽ nhặt các hàm callback trong một tạo vật gọi là event queue (hay task queue), đẩy vào trong call stack, và V8 engine tiếp tục thực thi hàm đang nằm trong call stack này.

2.3 Event Loop – trái tim của Nodejs

Event Loop được cung cấp bởi thư viện libuv. Nó không phải là thành phần của V8. Vòng sự kiện là thực thể xử lý các sự kiện bên ngoài và chuyển đổi chúng thành lời kêu gọi gọi lại. Đó là một vòng lặp mà chọn các sự kiện từ hàng đợi sự kiện và đẩy callbacks của họ vào Call Stack. Nó cũng là một vòng lặp đa pha. Event Loop là một thực thể xử lý các event ngoại vi và chuyển đổi chúng vào trong lời gọi callback. Nó là một vòng lặp chọn các event từ danh sách event đang chờ và đẩy callback của các event này vào Call Stack.
Node JS Platform tuân theo mô hình Single Threaded with Event Loop. Mô hình chính Node JS Processing dựa trên mô hình cơ bản Javascript Event với cơ chế Javascript callback. Khi Node JS đi theo kiến trúc này, nó có thể xử lý được đồng thời nhiều yêu cầu của clients rất dễ dàng. Trước khi thảo luận về mô hình này, trước tiên hãy đi qua biểu đồ dưới đây.





Các bước xử lý mô hình vòng lặp sự kiện đơn Threaded:
· Clients gửi yêu cầu tới Web Server.
· Node JS Web Server duy trì một Limited Thread Pool để cung cấp dịch vụ cho các yêu cầu của clients.
· Node JS Web Server nhận được các yêu cầu đó và đưa chúng vào hàng đợi. Nó được hiểu như là là "Event Queue".
· Node JS Web Server nội bộ có một Components, gọi là "Event Loop". Tại sao nó có tên này là vì nó sử dụng vòng lặp vô hạn để nhận yêu cầu và xử lý chúng.
· Event Loop sử dụng một Single Thread duy nhất. Đó là trọng tâm của mô hình Node JS Platform Processing Model.
· Event Loop kiểm tra bất kỳ client request nào được đặt trong Event Queue. Nếu không, nó sẽ chờ các request đến.
· Nếu có, nó chọn một yêu cầu client từ Event Queue
o Bắt đầu quá trình yêu cầu của client.
o Nếu client request không yêu cầu bất kỳ Blocking IO Operations, thì xử lý mọi thứ và chuẩn bị phản hồi và gửi lại cho client.
o Nếu client request yêu cầu một số Blocking IO Operations như tương tác với cơ sở dữ liệu, file systems, extenal services thì nó sẽ theo cách tiếp cận khác.
§ Kiểm tra các Thread có sẵn từ Internal Thread Pool
§ Chọn một Thread và chỉ định request client này cho Thread đó.
§ Thread này chịu trách nhiệm lấy request đó, xử lý nó, thực hiện các Blocking IO Operations, chuẩn bị phản hồi và gửi nó trở lại Event Loop
§ Event Loop lần lượt gửi phản hồi cho client tương ứng.

Event Loop Pseudo Code

public class EventLoop {  while(true){     if(Event Queue receives a JavaScript   Function Call){         ClientRequest request =   EventQueue.getClientRequest();         If(request requires BlokingIO or takes more computation time)             Assign request to   Thread T1         Else             Process and   Prepare response     }   }} 

3. ECMAScript 2015 (ES6)

Node.js được xây dựng trên phiên bản mới nhất của V8. Bằng việc luôn đảm bảo cập nhật đến phiên bản mới nhất của V8, Nodejs luôn được cập nhât những tính năng mới nhất của JavaScript ECMA-262 (ES6).
Những tính năng này được chia thành ba nhóm là shipping, staged và in progress:
· Shipping: Tính năng ổn định và được đặt mặc định trong Node.js
· Staged: Tính năng gần hoàn thiện và có thể sử dụng bằng việc đặt cờ harmony, nhưng chưa ổn định
· In progress: Tính năng đang thử nghiệm, có thể sử dụng, V8 team engineer có thể thay đổi mà không cần thông báo.
Để xem các tính năng đang thử nghiệm của Node.js
Ø node --v8-options | grep "in progress"
Để xem các tính năng đang được hoàn thiện trong node: http://node.green/

4. Cộng đồng phát triển Nodejs

NPM: Khả năng phát triển nhanh chóng và vượt bậc của Nodejs chính là đến từ kho lưu trữ mã nguồn mở lớn nhất hiên tại là npm (node package managerment). Npm đã được tích hợp sẵn khi cài đặt Nodejs. NPM cho phép các lập trình viên chia sẻ và sử dụng lại mã nguồn javascript dưới dạng module. Tính tời thời điểm tháng 1 năm 2017, trên npm hiện có khoảng hơn 350000 packages. Sự liên quan giữa Nodejs và npm chính là: Nodejs là nền tảng mà trên đó lập trình viên triển khai các gói npm để tạo thành dự án.
Lịch sử Nodejs:
Nhìn lại lịch sử hơn 9 năm phát triển của Nodejs cũng là cách để tìm hiểu xu hướng của công nghệ. Một bài viết hay: https://blog.risingstack.com/history-of-node-js/

8. Demo SocketIO: realtime chat + gameonline

Việc xây dựng các chat app hoặc game online trong thời gian thực trước đây thường phải cài đặt thêm một ứng dụng của bên thứ 3. Các ngôn ngữ PHP, ASP.NET hay Java đều có thể lập trình được socket nhưng nhược điểm là các connection đều bị die khi server vừa trả lời client xong hoặc bạn phải am hiểu hơn về các công nghệ như TCP/UDP , HTTP,.. Với NodeJS việc keep-alive các connection này trở nên đơn giản hơn vì được xây dựng theo cấu trúc hướng sự kiện và non blocking.
SocketIO được xây dựng lên nền tảng Nodejs dựa trên công nghệ socket. Cần hiểu rằng . Socket khác SocketIO và SocketIO không phải là mô hình Socket duy nhất hiện nay, và cũng không phải là mô hình web socket duy nhất hiện nay.Socket là cách bạn tổ chức mô hình client-server để một trong 2 bên luôn trong tình trạng sẵn sàng trả lời bên kia và ngược lại. Để đảm bảo việc này, kết nối giữa Client và Server phải ở trạng thái “keep-alive” và phải luôn xảy ra quá trình đồng bộ giữa Client-Server. Socket sẽ mang lại khả năng trả lời tức thì từ một trong 2 bên khi bên kia đưa ra một sự kiện, thay vì phải thực thi lại một loạt các thủ tục kết nối phức tạp như trước.
Sockets mới xuất hiện trong HTML5, là một kỹ thuật Reverse Ajax. Sockets cho phép các kênh giao tiếp song song hai chiều và hiện đã được hỗ trợ trong nhiều trình duyệt (Firefox, Google Chrome và Safari). Kết nối được mở thông qua một HTTP request (yêu cầu HTTP), được gọi là liên kết WebSockets với những header đặc biệt. Kết nối được duy trì để bạn có thể viết và nhận dữ liệu bằng JavaScript như khi bạn đang sử dụng một TCP socket đơn thuần.
Vậy tại sao không dùng ajax trong các ứng dụng cần realtime? Dữ liệu truyền tải thông qua giao thức HTTP (thường dùng với kĩ thuật Ajax) chứa nhiều dữ liệu không cần thiết trong phần header. Một header request/response của HTTP có kích thước khoảng 871 byte, trong khi với WebSocket, kích thước này chỉ là 2 byte (sau khi đã kết nối).
Giao thức bắt tay của websocket:



SocketIO dựa trên 2 hàm chủ yếu là on và emit. Hàm on là hàm lắng nghe các sự kiện vừa được xảy ra. Hàm emit là hàm phát ra các tín hiệu. Dựa trên 2 hàm này, server và client sẽ có các động thái “lắng nghe” và “phát ra” để trao đổi dữ liệu.
Demo appchat + game realtime pvp: https://github.com/damminhtien/FLY-CHAT

Nhận xét

Bài đăng phổ biến từ blog này

KĨ THUẬT LẬP TRÌNH

Nhân cái ngày mưa gió này làm cái blog vui :) Dựa trên đề cương ôn tập môn Kĩ thuật lập trình (Programming Technique) của thầy Trịnh Thành Trung 1. Thứ tự thực hiện các phép toán trong C 1.1 Vi ết chương trình nhập các tham số tương ứng và tính giá trị các biểu thức sau    int a,b,c,d;    a=b=c++=d=10;    in ra a,b,c,d    a=b=++c=d=10;    in ra a,b,c,d    Giữ nguyên đoạn code trên, sửa dòng khai báo thành   int a,c,d,b; chạy chương trình và xem kết quả và đưa ra nhận xét  Trong biểu thức gán  a=b=c++=d=10; (1) a=b=++c=d=10; (2) khi cho vào trình biên dịch chạy (như của mình là TDM GCC 4.9.2 64bit Release) thì biểu thức (1) sinh ra lỗi, trình biên dịch thông báo   "[Error] lvalue required as left operand of assignment" , biểu thức (2) không sinh ra lỗi, console hiển thị các giá trị a=b=c=d=1. Lý giải như sau : Trong C++ có 2 kiểu trả về là tham trị (value) và tham chiếu (reference) :  Đối với biểu thức hậu tố (postfix), sau khi t

[Operating System] PROCESS SYNCHRONIZATION PROBLEM

Operating SYsTem PROCESS SYNCHRONIZATION PROBLEM S ummarize Details some process synchronization problems that popular. Guide Teacher DR. Pham Dang Hai Đàm Minh Tiến - 2017 INTRODUCTION In   computer science ,   synchronization   refers to one of two distinct but related concepts: synchronization of   processes , and synchronization of   data .   Process synchronization   refers to the idea that multiple processes are to join up or   handshake   at a certain point, in order to reach an agreement or commit to a certain sequence of action.   Data synchronization   refers to the idea of keeping multiple copies of a dataset in coherence with one another, or to maintain   data integrity . Process synchronization primitives are commonly used to implement data synchronization. This document will intro some sychronization pr

CẤU TRÚC DỮ LIỆU VÀ GIẢI THUẬT

Bài viết dựa trên cuốn sách "Cấu trúc dữ liệu và thuật toán" của thầy Nguyễn Đức Nghĩa - Đại học Bách Khoa Hà Nội. " Nhân cái ngày mà người người nhà nhà ôn thi như thế này, sau khi đọc hết cuốn sách, mình nghĩ sao không thử viết 1 cái blog, vừa để chia sẻ mà lại ôn tập " I. CÁC KHÁI NIỆM CƠ BẢN: Trong phần này chủ yếu nói đến các khái niệm về thuật toán và đánh giá. Định nghĩa:  Thuật toán là một dãy hữu hạn các bước để từ đầu vào thu được đầu ra mong muốn. Đánh giá thuật toán dựa trên 2 tiêu chí cơ bản là :  Tài nguyên máy tính Thời gian thực hiện (số phép toán thực hiện) => Sau đây chỉ nói về thời gian thực hiện:  Có 3 loại thời gian tính:  Thời gian tính tốt nhất (tiệm cận dưới) : loại này ít được quan tâm Thời gian tính trung bình  Thời gian tính tồi nhất (tiệm cận trên) : BIG-O  => Sau đây chỉ nói về Big-O:        Big O được hiểu là thời gian tính tồi nhất của một thuật toán (worst case) hay ý nghĩa hình học của