Vòng lặp sự kiện Nodejs


141

Có hai vòng lặp bên trong kiến ​​trúc nodejs không?

  • libev / libuv
  • vòng lặp sự kiện javascript v8

Trên một yêu cầu I / O, nút có xếp hàng yêu cầu tới libeio, từ đó thông báo về tính khả dụng của dữ liệu thông qua các sự kiện bằng libev và cuối cùng các sự kiện đó được xử lý bởi vòng lặp sự kiện v8 bằng cách gọi lại?

Về cơ bản, libev và libeio được tích hợp trong kiến ​​trúc nodejs như thế nào?

Có tài liệu nào có sẵn để đưa ra một bức tranh rõ ràng về kiến ​​trúc nội bộ của nodejs không?

Câu trả lời:


175

Cá nhân tôi đã đọc mã nguồn của node.js & v8.

Tôi đã gặp một vấn đề tương tự như bạn khi tôi cố gắng hiểu kiến ​​trúc của node.js để viết các mô đun gốc.

Những gì tôi đang đăng ở đây là sự hiểu biết của tôi về node.js và điều này có thể hơi lạc hậu.

  1. Libev là vòng lặp sự kiện thực sự chạy bên trong tệp node.js để thực hiện các hoạt động vòng lặp sự kiện đơn giản. Nó được viết ban đầu cho các hệ thống * nix. Libev cung cấp một vòng lặp sự kiện đơn giản nhưng được tối ưu hóa cho quá trình chạy. Bạn có thể đọc thêm về libev ở đây .

  2. LibEio là một thư viện để thực hiện đầu ra đầu vào không đồng bộ. Nó xử lý các mô tả tập tin, xử lý dữ liệu, ổ cắm, vv Bạn có thể đọc thêm về nó ở đây .

  3. LibUv là một lớp trừu tượng trên đỉnh libeio, libev, c-ares (đối với DNS) và iocp (đối với windows không đồng bộ-io). LibUv thực hiện, duy trì và quản lý tất cả các io và sự kiện trong nhóm sự kiện. (trong trường hợp của libeio threadpool). Bạn nên xem hướng dẫn của Ryan Dahl trên libUv. Điều đó sẽ bắt đầu có ý nghĩa hơn với bạn về cách libUv tự hoạt động và sau đó bạn sẽ hiểu cách node.js hoạt động trên đỉnh libuv và v8.

Để hiểu chỉ vòng lặp sự kiện javascript, bạn nên xem xét các video này

Để xem libeio được sử dụng với node.js như thế nào để tạo các mô-đun không đồng bộ, bạn nên xem ví dụ này .

Về cơ bản những gì xảy ra bên trong node.js là vòng lặp v8 chạy và xử lý tất cả các phần javascript cũng như các mô-đun C ++ [khi chúng đang chạy trong một luồng chính (theo tài liệu chính thức, chính node.js là một luồng)]. Khi bên ngoài luồng chính, libev và libeio xử lý nó trong nhóm luồng và libev cung cấp sự tương tác với vòng lặp chính. Vì vậy, theo hiểu biết của tôi, node.js có 1 vòng lặp sự kiện cố định: đó là vòng lặp sự kiện v8. Để xử lý các tác vụ không đồng bộ C ++, nó sử dụng một luồng [thông qua libeio & libev].

Ví dụ:

eio_custom(Task,FLAG,AfterTask,Eio_REQUEST);

Mà xuất hiện trong tất cả các mô-đun thường gọi hàm Tasktrong luồng. Khi hoàn thành, nó gọi AfterTaskhàm trong luồng chính. Trong khi đó Eio_REQUEST, trình xử lý yêu cầu có thể là một cấu trúc / đối tượng mà động cơ của nó là cung cấp sự giao tiếp giữa luồng và luồng chính.


Dựa vào thực tế rằng libuv sử dụng libev trong nội bộ là một cách tốt để làm cho mã của bạn không phải là nền tảng chéo. Bạn chỉ nên quan tâm đến giao diện công cộng của libuv.
Raynos

1
@Raynos libuv nhằm mục đích đảm bảo nhiều thư viện x của nó. Đúng ? do đó sử dụng libuv là một ý tưởng hay
ShrekOverflow

1
@Abhishek Từ Doc process.nextTick- Trên vòng lặp tiếp theo xung quanh vòng lặp sự kiện gọi cuộc gọi lại này. Đây không phải là bí danh đơn giản cho setTimeout (fn, 0), nó hiệu quả hơn nhiều. Vòng lặp sự kiện này đề cập đến? Vòng lặp sự kiện V8?
Tamil


4
Có cách nào để 'xem' hàng đợi sự kiện này không? Id muốn có thể xem thứ tự các cuộc gọi trên ngăn xếp và xem các chức năng mới được đẩy vào đó để hiểu rõ hơn về những gì đang xảy ra ... có một số biến cho bạn biết những gì đã được đẩy đến hàng đợi sự kiện?
tbarbe

20

Có vẻ như một số thực thể được thảo luận (ví dụ: libev, v.v.) đã mất đi sự liên quan, do thực tế là đã được một thời gian, nhưng tôi nghĩ rằng câu hỏi vẫn có tiềm năng lớn.

Hãy để tôi thử giải thích hoạt động của mô hình hướng sự kiện với sự trợ giúp của một ví dụ trừu tượng, trong môi trường UNIX trừu tượng, trong bối cảnh của Node, như ngày nay.

Quan điểm của chương trình:

  • Script engine bắt đầu thực thi script.
  • Bất cứ khi nào gặp phải một hoạt động ràng buộc CPU, nó được thực hiện nội tuyến (máy thật), trong sự hoàn chỉnh của nó.
  • Bất cứ khi nào gặp phải thao tác ràng buộc I / O, yêu cầu và trình xử lý hoàn thành của nó đều được đăng ký với một 'máy móc sự kiện' (máy ảo)
  • Lặp lại các hoạt động theo cách tương tự ở trên cho đến khi kịch bản kết thúc. Hoạt động bị ràng buộc của CPU - thực thi các dòng bị ràng buộc, I / O, yêu cầu máy móc như trên.
  • Khi I / O hoàn thành, người nghe được gọi lại.

Bộ máy sự kiện ở trên được gọi là khung vòng lặp sự kiện libuv AKA. Node tận dụng thư viện này để thực hiện mô hình lập trình hướng sự kiện.

Quan điểm của Node:

  • Có một luồng để lưu trữ thời gian chạy.
  • Chọn tập lệnh người dùng.
  • Biên dịch nó thành bản địa [đòn bẩy v8]
  • Tải nhị phân, và nhảy vào điểm vào.
  • Mã được biên dịch thực thi các hoạt động liên kết của CPU trong dòng, sử dụng các nguyên hàm lập trình.
  • Nhiều mã liên quan đến I / O và bộ đếm thời gian có kết thúc tốt đẹp. Ví dụ, mạng I / O.
  • Vì vậy, các cuộc gọi I / O được chuyển từ tập lệnh tới các cầu nối C ++, với tay cầm I / O và trình xử lý hoàn thành được chuyển qua làm đối số.
  • Mã bản địa thực hiện vòng lặp libuv. Nó thu nhận vòng lặp, liệt kê một sự kiện cấp thấp đại diện cho I / O và một trình bao bọc gọi lại riêng vào cấu trúc vòng lặp libuv.
  • Mã riêng trở về tập lệnh - không có I / O nào được thực hiện tại thời điểm này!
  • Các mục ở trên được lặp lại nhiều lần, cho đến khi tất cả các mã không phải I / O được thực thi và tất cả các mã I / O được đăng ký sẽ là libuv.
  • Cuối cùng, khi không còn gì trong hệ thống để thực thi, nút chuyển điều khiển sang libuv
  • libuv bắt đầu hoạt động, nó chọn tất cả các sự kiện đã đăng ký, truy vấn hệ điều hành để có được khả năng hoạt động của chúng.
  • Những người đã sẵn sàng cho I / O ở chế độ không chặn, được chọn, I / O được thực hiện và các cuộc gọi lại của họ được phát hành. Cái này sau cái kia.
  • Những cái chưa sẵn sàng (ví dụ ổ cắm đã đọc, mà điểm cuối khác chưa viết gì) sẽ tiếp tục được thử nghiệm với HĐH cho đến khi chúng có sẵn.
  • Vòng lặp bên trong duy trì một bộ đếm thời gian ngày càng tăng. Khi ứng dụng yêu cầu gọi lại bị trì hoãn (chẳng hạn như setTimeout), giá trị bộ định thời bên trong này được tận dụng để tính toán thời điểm thích hợp để thực hiện cuộc gọi lại.

Mặc dù hầu hết các chức năng được phục vụ theo cách này, một số (phiên bản không đồng bộ) của các hoạt động tệp được thực hiện với sự trợ giúp của các luồng bổ sung, được tích hợp tốt vào libuv. Trong khi các hoạt động I / O mạng có thể chờ đợi trong sự kiện bên ngoài, chẳng hạn như điểm cuối khác phản hồi với dữ liệu, v.v. các hoạt động tệp cần một số công việc từ chính nút. Ví dụ: nếu bạn mở một tệp và đợi fd sẵn sàng với dữ liệu, điều đó sẽ không xảy ra, vì thực tế không có ai đọc! Đồng thời, nếu bạn đọc từ tệp nội tuyến trong luồng chính, nó có khả năng chặn các hoạt động khác trong chương trình và có thể gây ra sự cố có thể nhìn thấy, vì hoạt động của tệp rất chậm so với các hoạt động bị ràng buộc cpu. Vì vậy, các luồng công nhân nội bộ (có thể định cấu hình thông qua biến môi trường UV_THREADPOOL_SIZE) được sử dụng để hoạt động trên các tệp,

Hi vọng điêu nay co ich.


Làm thế nào bạn biết những điều này, bạn có thể chỉ cho tôi nguồn?
Suraj Jain

18

Giới thiệu về libuv

Các Node.js dự án bắt đầu vào năm 2009 như là một môi trường hoạt Javascript tách rời khỏi trình duyệt. Sử dụng libev của V8 và Marc Lehmann , node.js đã kết hợp một mô hình I / O - có sự kiện - với một ngôn ngữ rất phù hợp với phong cách lập trình; do cách nó đã được định hình bởi các trình duyệt. Khi node.js trở nên phổ biến, điều quan trọng là làm cho nó hoạt động trên Windows, nhưng libev chỉ chạy trên Unix. Windows tương đương với các cơ chế thông báo sự kiện kernel như kqueue hoặc (e) poll là IOCP. libuv là một sự trừu tượng xung quanh libev hoặc IOCP tùy thuộc vào nền tảng, cung cấp cho người dùng API dựa trên libev. Trong phiên bản nút-v0.9.0 của libuv libev đã bị xóa .

Ngoài ra một hình ảnh mô tả Vòng lặp sự kiện trong Node.js của @ BusyRich


Cập nhật ngày 05/09/2017

Mỗi vòng lặp sự kiện Node.js doc này ,

Sơ đồ sau đây cho thấy một tổng quan đơn giản về thứ tự các hoạt động của vòng lặp sự kiện.

   ┌───────────────────────┐
┌─>│        timers         
  └──────────┬────────────┘
  ┌──────────┴────────────┐
       I/O callbacks     
  └──────────┬────────────┘
  ┌──────────┴────────────┐
       idle, prepare     
  └──────────┬────────────┘      ┌───────────────┐
  ┌──────────┴────────────┐         incoming:   
           poll          │<─────┤  connections, 
  └──────────┬────────────┘         data, etc.  
  ┌──────────┴────────────┐      └───────────────┘
          check          
  └──────────┬────────────┘
  ┌──────────┴────────────┐
└──┤    close callbacks    
   └───────────────────────┘

lưu ý: mỗi hộp sẽ được gọi là "pha" của vòng lặp sự kiện.

Tổng quan về giai đoạn

  • bộ hẹn giờ : giai đoạn này thực hiện các cuộc gọi lại được lên lịch bởi setTimeout()setInterval().
  • Các cuộc gọi lại I / O : thực hiện hầu hết tất cả các cuộc gọi lại ngoại trừ các cuộc gọi lại gần , các cuộc gọi được lên lịch bởi bộ định thời và setImmediate().
  • nhàn rỗi, chuẩn bị : chỉ sử dụng nội bộ.
  • thăm dò ý kiến : lấy các sự kiện I / O mới; nút sẽ chặn ở đây khi thích hợp.
  • kiểm tra : setImmediate()gọi lại được gọi ở đây.
  • cuộc gọi lại gần : vd socket.on('close', ...).

Giữa mỗi lần chạy vòng lặp sự kiện, Node.js kiểm tra xem nó có đang chờ bất kỳ I / O không đồng bộ hoặc bộ định thời nào không và tắt hoàn toàn nếu không có bất kỳ.


Bạn đã trích dẫn rằng " In the node-v0.9.0 version of libuv libev was removed", nhưng không có mô tả nào về nó trong nodejs changelog. github.com/nodejs/node/blob/master/CHANGELOG.md . Và nếu libev bị loại bỏ thì bây giờ làm thế nào I / O async được thực hiện trong nodejs?
intekhab

@intekhab, Mỗi liên kết này , tôi nghĩ libuv dựa trên libeio có thể được sử dụng làm vòng lặp sự kiện trong node.js.
zangw

@intekhab Tôi nghĩ libuv đang triển khai tất cả các tính năng liên quan đến I / O và bỏ phiếu. ở đây kiểm tra tài liệu này: docs.libuv.org/en/v1.x/loop.html
mohit kaushik

13

Có một vòng lặp sự kiện trong Kiến trúc NodeJs.

Mô hình vòng lặp sự kiện Node.js

Các ứng dụng nút chạy trong một mô hình hướng sự kiện đơn luồng. Tuy nhiên, Node thực hiện một nhóm luồng trong nền để có thể thực hiện công việc.

Node.js thêm công việc vào hàng đợi sự kiện và sau đó có một luồng duy nhất chạy vòng lặp sự kiện lấy nó. Vòng lặp sự kiện lấy mục trên cùng trong hàng đợi sự kiện, thực thi nó và sau đó lấy mục tiếp theo.

Khi thực thi mã tồn tại lâu hơn hoặc đã chặn I / O, thay vì gọi hàm trực tiếp, nó sẽ thêm chức năng vào hàng đợi sự kiện cùng với một cuộc gọi lại sẽ được thực hiện sau khi chức năng hoàn thành. Khi tất cả các sự kiện trên hàng đợi sự kiện Node.js đã được thực thi, ứng dụng Node.js sẽ chấm dứt.

Vòng lặp sự kiện bắt đầu gặp sự cố khi các chức năng ứng dụng của chúng tôi chặn trên I / O.

Node.js sử dụng các cuộc gọi lại sự kiện để tránh phải chờ I / O chặn. Do đó, mọi yêu cầu thực hiện chặn I / O đều được thực hiện trên một luồng khác nhau trong nền.

Khi một sự kiện chặn I / O được lấy từ hàng đợi sự kiện, Node.js lấy một luồng từ nhóm luồng và thực hiện chức năng ở đó thay vì trên chuỗi vòng lặp sự kiện chính. Điều này ngăn chặn I / O chặn giữ phần còn lại của các sự kiện trong hàng đợi sự kiện.


8

Chỉ có một vòng lặp sự kiện được cung cấp bởi libuv, V8 chỉ là một công cụ thời gian chạy JS.


1

Là người mới bắt đầu sử dụng javascript, tôi cũng có nghi ngờ tương tự, NodeJS có chứa 2 vòng lặp sự kiện không?. Sau một thời gian dài nghiên cứu và thảo luận với một trong những người đóng góp V8, tôi đã có được những khái niệm sau.

  • Vòng lặp sự kiện là một khái niệm trừu tượng cơ bản của mô hình lập trình JavaScript. Vì vậy, công cụ V8 cung cấp một triển khai mặc định cho vòng lặp sự kiện, mà các trình nhúng (trình duyệt, nút) có thể thay thế hoặc mở rộng . Các bạn có thể tìm thấy triển khai mặc định V8 của vòng lặp sự kiện tại đây
  • Trong NodeJS, chỉ có một vòng lặp sự kiện tồn tại , được cung cấp bởi thời gian chạy nút. Việc thực hiện vòng lặp sự kiện mặc định V8 đã được thay thế bằng việc thực hiện vòng lặp sự kiện NodeJS

0

Các pbkdf2chức năng có thực hiện hoạt Javascript nhưng nó thực sự đại biểu tất cả công việc phải làm để phía bên C ++.

env->SetMethod(target, "pbkdf2", PBKDF2);
  env->SetMethod(target, "generateKeyPairRSA", GenerateKeyPairRSA);
  env->SetMethod(target, "generateKeyPairDSA", GenerateKeyPairDSA);
  env->SetMethod(target, "generateKeyPairEC", GenerateKeyPairEC);
  NODE_DEFINE_CONSTANT(target, OPENSSL_EC_NAMED_CURVE);
  NODE_DEFINE_CONSTANT(target, OPENSSL_EC_EXPLICIT_CURVE);
  NODE_DEFINE_CONSTANT(target, kKeyEncodingPKCS1);
  NODE_DEFINE_CONSTANT(target, kKeyEncodingPKCS8);
  NODE_DEFINE_CONSTANT(target, kKeyEncodingSPKI);
  NODE_DEFINE_CONSTANT(target, kKeyEncodingSEC1);
  NODE_DEFINE_CONSTANT(target, kKeyFormatDER);
  NODE_DEFINE_CONSTANT(target, kKeyFormatPEM);
  NODE_DEFINE_CONSTANT(target, kKeyTypeSecret);
  NODE_DEFINE_CONSTANT(target, kKeyTypePublic);
  NODE_DEFINE_CONSTANT(target, kKeyTypePrivate);
  env->SetMethod(target, "randomBytes", RandomBytes);
  env->SetMethodNoSideEffect(target, "timingSafeEqual", TimingSafeEqual);
  env->SetMethodNoSideEffect(target, "getSSLCiphers", GetSSLCiphers);
  env->SetMethodNoSideEffect(target, "getCiphers", GetCiphers);
  env->SetMethodNoSideEffect(target, "getHashes", GetHashes);
  env->SetMethodNoSideEffect(target, "getCurves", GetCurves);
  env->SetMethod(target, "publicEncrypt",
                 PublicKeyCipher::Cipher<PublicKeyCipher::kPublic,
                                         EVP_PKEY_encrypt_init,
                                         EVP_PKEY_encrypt>);
  env->SetMethod(target, "privateDecrypt",
                 PublicKeyCipher::Cipher<PublicKeyCipher::kPrivate,
                                         EVP_PKEY_decrypt_init,
                                         EVP_PKEY_decrypt>);
  env->SetMethod(target, "privateEncrypt",
                 PublicKeyCipher::Cipher<PublicKeyCipher::kPrivate,
                                         EVP_PKEY_sign_init,
                                         EVP_PKEY_sign>);
  env->SetMethod(target, "publicDecrypt",
                 PublicKeyCipher::Cipher<PublicKeyCipher::kPublic,
                                         EVP_PKEY_verify_recover_init,
                                         EVP_PKEY_verify_recover>);

tài nguyên: https://github.com/nodejs/node/blob/master/src/node_crypto.cc

Mô-đun Libuv có một trách nhiệm khác có liên quan đến một số chức năng rất đặc biệt trong thư viện tiêu chuẩn.

Đối với một số lệnh gọi hàm thư viện tiêu chuẩn, phía Node C ++ và Libuv quyết định thực hiện các phép tính đắt tiền bên ngoài vòng lặp sự kiện.

Thay vào đó, họ sử dụng một thứ gọi là nhóm luồng, nhóm luồng là một chuỗi gồm bốn luồng có thể được sử dụng để chạy các tác vụ tính toán đắt tiền như pbkdf2hàm.

Theo mặc định, Libuv tạo ra 4 luồng trong nhóm luồng này.

Ngoài các luồng được sử dụng trong vòng lặp sự kiện, có bốn luồng khác có thể được sử dụng để giảm tải các tính toán đắt tiền cần phải xảy ra trong ứng dụng của chúng tôi.

Nhiều hàm trong thư viện chuẩn Node tự động sử dụng nhóm luồng này. Các pbkdf2chức năng là một trong số họ.

Sự hiện diện của nhóm chủ đề này là rất đáng kể.

Vì vậy, Node không thực sự là một luồng đơn, bởi vì có các luồng khác mà Node sử dụng để thực hiện một số tác vụ tính toán tốn kém.

Nếu nhóm sự kiện chịu trách nhiệm thực hiện nhiệm vụ tính toán tốn kém, thì ứng dụng Node của chúng tôi không thể làm gì khác.

CPU của chúng tôi chạy tất cả các hướng dẫn bên trong một luồng một.

Bằng cách sử dụng nhóm luồng, chúng ta có thể thực hiện những việc khác trong vòng lặp sự kiện trong khi các phép tính đang diễn ra.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.