CHƯƠNG 1: CƠ SỞ LÝ THUYẾT 1.1 KIẾN TRÚC HỆ THỐNG Hình 1: Hình minh hoạ kiến trúc hệ thống[1] Kiến trúc chung của hệ thống bao gồm có 3 tầng truyền thống, tầng hiển thị front- end(Reactjs),tầng ứng dụng backend (expressJs và nodeJs) và tầng cơ sở dữ liệu (mongodb), front-end và backend giao tiếp với nhau qua RESTful API 1.1 NodeJS là gì Node là một nền tảng phát triển các ứng dụng web back-end được viết bằng JavaScript, 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 NodeJS là làm cho web có khả năng push như trong một số ứng dụng gmail. NodeJS 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 NodeJS 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.
Ngoài việc chạy trên JavaScript thì Node có những tính năng đi kèm sau: 18 - Có trình CLI (giao diện dòng lệnh) - Chạy theo mô hình REPL - Có các hàm quản lý tiến trình - Có các đối tượng hỗ trợ làm việc với dữ liệu nhị phân - Hỗ trợ TCP và UDP - Hỗ trợ phân giải DNS - Hỗ trợ HTTP và HTTPS - Có thể truy cập file và thư mục Dưới đây là sơ đồ về các thành phần quan trọng trong NodeJS: Hình 2: Các thành phần quan trọng trong NodeJS [2] NodeJS cho phép chúng ta dễ dàng thực hiện các giao thức mạng cấp thấp. Ví dụ, NodeJS có một mô-đun HTTP, cho phép xây dựng một máy chủ web chỉ với một vài dòng mã, nhưng do đó chúng ta phải tìm hiểu thêm nhiều thứ, chẳng hạn như hiểu tiêu đề của các gói HTTP, không giống như PHP, vốn chỉ là một mô-đun mở rộng. cho các mạng hiện có Máy chủ (như Apache hay NginX) - tức là PHP dễ sử dụng hơn NodeJS, nhưng không cho phép lập trình viên làm công việc cấp thấp. Tuy nhiên, vì NodeJS là một framework mã nguồn mở, nên có rất nhiều thư viện cho phép các nhà phát triển viết các máy chủ web nhanh hơn và dễ dàng hơn.2 Kiến trúc của NodeJS NodeJS sử dụng kiển trúc lập trình hướng sự kiện bất đồng bộ, và đây là một tính năng làm cho các ứng dụng NodeJS có thể chạy với hiệu suất cao.
Chẳng hạn như đối với các ứng dụng bình thường như một chương trình viết bằng C++ thì khi ta viết chương 19 trình để đọc dữ liệu, chương trình sẽ phải dừng lại ở đoạn code đọc dữ liệu để chờ cho đến khi có dữ liệu để đọc, nếu muốn chương trình tiếp tục vừa chạy các công việc khác vừa đọc dữ liệu thì phải dùng đến đa luồng (multi-threading), tuy nhiên việc dùng đa luồng rất phức tạp và có thể làm chậm server nếu quản lý đa luồng không tốt. Trong khi đó, NodeJS chỉ sử dụng một luồng duy nhất, các câu lệnh nhập xuất không cần phải chờ mà được thực hiện ngay lập tức bằng cách sử dụng Event Loop, cứ mỗi lần có sự kiện xảy ra thì chuyển dữ liệu của sự kiện đó đến các hàm xử lý tương ứng, và trong khi các hàm xử lý đang chạy thì vòng lặp sự kiện vẫn tiếp tục nhận sự kiện và chuyển đến các hàm xử lý tương ứng khác. Đối với các chương trình bình thường thì khi chạy đến dòng code trên, luồng chạy chương trình đó sẽ phải dừng lại để đợi quá trình xử lý từ cơ sở dữ liệu thực hiện xong và trả về rồi mới tiếp tục được, trong quá trình đợi đó sẽ có nhiều yêu cầu khác có thể xảy ra và hiệu suất phần mềm sẽ giảm do lãng phí tài nguyên, để giải quyết tình trạng đó thì ta có thể dùng cơ chế đa luồng để xử lý, tuy nhiên đa luồng có một nhược điểm làm tiêu tốn nhiều bộ nhớ và CPU và nếu chúng ta quản lý không tốt việc xử lý đa luồng có thể làm chậm server. Thay vì dùng đa luồng thì NodeJS sử dụng cơ chế Event Loop để giải quyết việc này, nói một cách đơn giản thì Node sẽ đưa các câu lệnh chờ trên vào một luồng khác là Event Loop để xử lý riêng, trong khi luồng chính vẫn sẽ chạy các công việc của riêng nó, và khi nào luồng chính “rảnh” rồi thì luồng Event Loop sẽ chuyển các công việc đã thực hiện xong trở về lại luồng chính.
Và chính vì NodeJS chỉ sử dụng 2 luồng nên tài nguyên hệ thống sẽ không bị chiếm nhiều như khi dùng cơ chế đa luồng, ngoài ra việc viết mã sử dụng Event Loop đơn giản hơn nhiều Trong đoạn mã trên, kết quả trả về từ hàm query() thay vì được gán vào một biến thì sẽ được truyền vào một hàm khác là function(err, result), và hàm này sẽ được chuyển vào luồng Event Loop và chờ cho đến khi luồng chính “rảnh” thì mới được chuyển qua.3 Cách thức hoạt động của NodeJS Ý tưởng chính của NodeJS 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ì, NodeJS 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ử 20 dụng không hiệu quả. Chính vì lẽ đó giải pháp mà NodeJS đư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[1].
Hình 3: Cách thức hoạt động của NodeJS[3] 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 NodeJS 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 NodeJS Application xử lý Model như sau: - Client gửi request đến Web Server. - NodeJS 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.
- NodeJS 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. 21 - NodeJS 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. Kiểm tra các luồng sẵn có từ nội bộ bên trong của request gửi lên. Chọn một luồng và chỉ định cho client request tương ứng với luồng đó. 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.
Event Loop gửi lại phản hồi tương ứng cho client. 22 Hình 4: Cơ chế của Event Loop trong NodeJS [4] 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 đến 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 đó. - NodeJS Web service nhận các request từ client-1, client-2 đến client-n và đặt chúng vào trong Event Queue - NodeJS Event Loop chọn các request theo dạng một đối một: ▪ Event Loop chọn client-1 request-1 o 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.
o 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. 23 o 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. o Event Loop gửi Response-1 đến Client-1 ▪ Event Loop chọn client-2 request-2 o 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. o 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.
o 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. o Event Loop gửi Response-2 đến Client-2. ▪ Event Loop chọn client-n request-n o 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. o 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. o 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 o 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. o Thread T-1 gửi Response-n này đến Event Loop o 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ù.1 Định nghĩa ExpressJS là một framework được xây dựng trên nền tảng của NodeJS.
Nó cung cấp các tính năng mạnh mẽ để phát triển web hoặc mobile. ExpressJS hỗ trợ các method HTTP và midleware tạo ra API vô cùng mạnh mẽ và dễ sử dụng. 24 Hình 5: Cấu trúc một API được xây dựng với ExpressJS[3] Tổng hợp một số chức năng chính của ExpressJS như sau: - Thiết lập các lớp trung gian để trả về các HTTP request. - Khai báo router cho phép sử dụng với các hành động khác nhau dựa trên phương thức HTTP và URL.
- Cho phép trả về các trang HTML dựa vào các tham số.