Event-driven trong Node.js
event-driven
1
nodejs
64
lock-free
2
White

dungcoi viết ngày 19/12/2015

Trong note này bạn sẽ thấy sự xuất hiện các node.js , socket.io, redis ... hay đủ thứ khác, nhưng nội dung của note chỉ luẩn quẩn quanh khái niệm socket và thread thôi, không nói sâu hay cụ thể một cái gì cả, bạn có thể an tâm mà đọc.

Mở đầu của note này là một câu hỏi nho nhỏ trên StackOverflow:

I have created a nodejs application which works fine in a single core. This application uses socket.io for light&fast communication.
Now, I wanted to scale my application vertically, by enabling it to run on multi-core system using cluster module in nodejs.
Everything in my application stores data in Redis, therefore, there isn't a problem dealing with some basic data & sessions.
However, when I spawn multiple worker processes using cluster, it seems that each worker has its own socket handling.
For example, lets assume there is a chat room called 'guest' room.
User A and user B connects to the room, and they are distributed to the different worker process.
Since they are in different process and these processes do not share sockets listener, there is no way for user A and user B to talk to each other.
What is a good approach to solve this problem? Does socket.io supports multi-core system?
Is socket.io only for single core use?
Nguồn

Tóm tắt cho bạn nào lười đọc:

Node.js chỉ chạy trên một CPU core.
Bạn này làm một chat room nhỏ và có nhu cầu scale connection ra nhiều core để tận dụng tài nguyên CPU hệ thống.
Để scale node.js ra nhiều core, bạn này dùng module cluster của để chia số connection theo từng instance độc lập (là các process rời).
Vấn đề xảy ra:
Node1 đang giữ connection với instance Client1
Node2 đang giữ connection với instance Client2

Giờ Client1 nhắn một message tới Client2 thì phải làm sao? Vì 2 instance này là chạy độc lập hoàn toàn, không share tài nguyên socket cho nhau?

Cách giải quyết vấn đề này không khó, chỉ cần dùng một service khác để liên lạc giữa Node1Node2 là được.
Mô hình có thể dùng như sau, trong hình này dùng giải pháp load balance khác chứ không phải module cluster của Node.js, nhưng tư tưởng chính là phân tán connection là tương tự nhau

alt text
Nguồn

Mình nghĩ đây là một câu hỏi hay nếu bạn tìm hiểu sâu về nó.

Node.js chạy trên 1 CPU core duy nhất ?

Bạn có thể ngạc nhiên, nhưng không chỉ node.js , mà nhiều app nổi tiếng về web service khác như: Redis, HAProxy cũng chạy một duy nhất bằng 1 core duy nhất. Cụ thể hơn là các app này đều chỉ chạy bằng 1 thread duy nhất trong mỗi instance.
Lý do nằm sau nó nằm gọn trong từ khóa: lock-free
Lock-free chia thành 2 thành phần nhỏ:

  • Lock-free về socket: tức là none-blocking socket, quen thuộc hơn là socket bất đồng bộ (asynchronous)
  • Lock-free về kiến trúc phần mềm: Trong kiến trúc phần mềm đó, sẽ không còn các khái niệm lock, mutex ... gì cả, lý do là tất cả các xử lý của app đều chạy trong một thread duy nhất. Chúng ta sẽ đi chi tiết hơn.

1.Lock-free trong socket

a.Blocking IO

Blocking socket là kết nối synchronous, trong đó mỗi hàm xử lý socket như send, recv khi được gọi sẽ đợi tới khi xử lý xong tác vụ mới trả về kết quả. Nếu trong trường hợp hàm recv được gọi mà kết nối chưa có dữ liệu tới thì hàm đó sẽ bị block lại, cho tới khi có dữ liệu vào hay kết nối xảy ra lỗi, hệ điều hành mới trả giá trị về với dữ liệu hay lỗi tương ứng.Việc này khiến mỗi một thread làm việc của app chỉ có thể xử lý một connection.

Trong giai đoạn đầu xây dựng các web server, các server dùng dạng blocking socket này, mỗi kết nối từ phía client tới server sẽ được fork một process hoặc thread riêng biệt. Với số lượng connection nhỏ thì phương pháp này không có vấn đề, tuy nhiên khi số connection tăng lên thì các tài nguyên hệ thống bị tăng lên đáng kể, và vấn đề C10K xuất hiện.

C10K - Vấn đề 10.000 kết nối, với cách thức xử lý mỗi kết nối sử dụng một tài nguyên riêng biệt như vậy, các web server sẽ có số kết nối xử lý cùng lúc không thể vượt quá 10.000 kết nối.
Hiện nay vẫn còn nhiều web server sử dụng kiến trúc:

  • Apache: Trong cấu hình, cho phép fork tiến trình cho từng kết nối
  • Các dạng thread-pool server

b.Non-blocking IO

Non-blocking socket là dạng kết nối asynchronous, trong đó hàm read, recv khi được gọi sẽ kiểm tra xem trong vùng nhớ sẽ trả về ngay kết quả dựa vào trạng thái hiện tại. Ví dụ trong trường hợp hàm recv được gọi, hệ điều hành sẽ kiểm tra xem trong vùng buffer của kết nối xem có dữ liệu hay không và trả về ngay kết quả, nếu có trả về kết quả, nếu không có trả về mã lỗi tương ứng thay vì block lại như ở blocking.

Tuy nhiên, việc này khiến phát sinh thêm một nhu cầu là làm sao để nhận biết một kết nối có dữ liệu tới. Các hệ điều hành cài đặt cái API cấp thấp để làm nhiệm vụ này:

  • Windows: Input/output completion port (IOCP)
  • Linux/Unix: Các hàm select, poll, epoll
  • FreeBSD: kqueue

Các API trên các hệ điều hành có tên khác nhau nhưng đều có chung nhiệm vụ là kiểm tra xem một danh sách file descriptor có sự kiện xảy ra hay không, các sự kiện có thể là dữ liệu hoặc lỗi, cài đặt sử dụng API này người ta thường sử dụng event-driven phần này sẽ được đề cập ở phần sau.

Với non-blocking socket, chỉ những kết nối nào có sự kiện xảy ra mới cần xử lý, việc này giúp tiết kiệm rất nhiều tài nguyên hệ thống. Một luồng làm việc có thể kiểm soát rất nhiều kết nối cùng lúc. Bằng cách này vấn đề C10K được giải quyết.
Hầu hết các server thông dụng hiện nay đều sử dụng dạng kết nối này, tiêu biểu và thông dụng là nginx.

2.Lock-free trong kiến trúc phần mềm

Một vấn đề lớn khi xây dựng các ứng dụng như server là việc nhiều kết nối sử dụng cùng một số tài nguyên nhất định. Các cơ chế synchronous giữa các thread giúp khắc phục vấn đề trên, tuy nhiên lại gây ra vấn đề:

  • Gây giảm hiệu suất sử dụng tài nguyên, do việc các luồng chờ nhau
  • Khả năng lỗi hay thiếu ổn định do thiếu xót khi xử lý đồng thời gây ra

Event-driven

Để khắc phục vấn đề, toàn bộ server làm việc trên một trên thread duy nhất, khi muốn sử dụng thêm tài nguyên CPU thì sẽ tạo thêm instance mới trên CPU tương ứng, và các tài nguyên sử dụng hoàn toàn độc lập.
App sử dụng mô hình event-driven, toàn bộ việc xử lý của server sẽ chịu sự điều phối của một vòng lặp trung tâm.
alt text
Nguồn

Có thể nói non-blocking socket và event-driven là đôi tình nhân sinh là ra dành cho nhau, và các cơ chế polling là ông tơ bà nguyệt kết nối chúng lại. Ta có một công thức khá đơn giản cho một event-driven trong trường hợp của lập trình socket như node.js.

non-blocking socket + polling + one thread = event-driven socket

Đây là kiến trúc phần mềm của rất nhiều app về web server thông dụng hiện nay sử dụng như:

  • NodeJS
  • Redis
  • HAProxy

Nếu bạn muốn đọc thêm hay tranh cãi gì kiến trúc này là dỏm thì bạn có thể đọc thêm tại đâyđây nữa

Bạn cũng chú ý, kiến trúc này hỗ trợ chỉ hỗ trợ 1 instance chạy trên 1 core, không tức là nó không hỗ trợ nhiều core.
Trong trường hợp của HAProxy, do tính chất của proxy là các tài nguyên mỗi connection là hoàn toàn độc lập, điều này giúp các instance không nhất thiết phải liên lạc lẫn nhau, nên có thể fork process và sử dụng các tài nguyên hoàn toàn độc lập.
Trường hợp của câu hỏi đầu bài thì hơi khác chút, do 2 instance này sẽ cần dùng tài nguyên lẫn nhau nên phải dùng thêm thành phần khác để 2 instance liên lạc lẫn nhau.
Bài viết ban đầu tính dài hơn, còn nhiều cái hay ho hơn nếu phân tích về cách làm việc của các liên kết:

  • client-nodejs: long-polling nếu là http connect
  • nodejs-redis: pubsub

Nhưng mình dừng, do nói dài nói dai đâm ra nói dở :sweat_smile:

Hiểu để xài nhau tốt hơn bạn nhé :blush:

Bình luận


White
{{ comment.user.name }}
Bỏ hay Hay
{{comment.like_count}}
Male avatar
{{ comment_error }}
Hủy
   

Hiển thị thử

Chỉnh sửa

White

dungcoi

4 bài viết.
89 người follow
Kipalog
{{userFollowed ? 'Following' : 'Follow'}}
Cùng một tác giả
White
45 14
HTTP/1 Năm 1989, Tim BernersLee phát minh ra HTTP. Năm 1996, HTTP 1.0 được tổ chức RFC thông qua trở thành một chuẩn, HTTP 1.1 cũng hình thành cùn...
dungcoi viết hơn 2 năm trước
45 14
White
26 3
simplehttp à một source viết bởi bitly : https://github.com/bitly/simplehttp Như các bạn thấy các từ simple, bit : đơn giản và nhỏ bé thôi, đừng t...
dungcoi viết 2 năm trước
26 3
White
14 2
(Link) là một phần mềm load balancing thông dụng được phát triển trên ngôn ngữ C, có tốc độ xử lý và độ ổn định rất cao, các lý do thì hầu hết đã đ...
dungcoi viết hơn 2 năm trước
14 2
{{like_count}}

kipalog

{{ comment_count }}

bình luận

{{liked ? "Đã kipalog" : "Kipalog"}}


White
{{userFollowed ? 'Following' : 'Follow'}}
4 bài viết.
89 người follow

 Đầu mục bài viết

Vẫn còn nữa! x

Kipalog vẫn còn rất nhiều bài viết hay và chủ đề thú vị chờ bạn khám phá!