Yêu cầu chuyên sâu về Node.js và CPU


215

Tôi đã bắt đầu mày mò với máy chủ HTTP Node.js và thực sự muốn viết Javascript phía máy chủ nhưng điều gì đó đang ngăn tôi bắt đầu sử dụng Node.js cho ứng dụng web của mình.

Tôi hiểu toàn bộ khái niệm I / O không đồng bộ nhưng tôi hơi lo ngại về các trường hợp cạnh trong đó mã thủ tục rất nặng về CPU như thao tác hình ảnh hoặc sắp xếp các tập dữ liệu lớn.

Theo tôi hiểu, máy chủ sẽ rất nhanh cho các yêu cầu trang web đơn giản như xem danh sách người dùng hoặc xem bài đăng trên blog. Tuy nhiên, nếu tôi muốn viết mã chuyên sâu CPU (ví dụ như ở phần cuối quản trị viên) tạo đồ họa hoặc thay đổi kích thước hàng ngàn hình ảnh, yêu cầu sẽ rất chậm (vài giây). Vì mã này không đồng bộ, mọi yêu cầu đến máy chủ trong vài giây đó sẽ bị chặn cho đến khi yêu cầu chậm của tôi được thực hiện.

Một đề xuất là sử dụng Web Worker cho các tác vụ chuyên sâu của CPU. Tuy nhiên, tôi e rằng nhân viên web sẽ khó viết mã sạch vì nó hoạt động bằng cách bao gồm một tệp JS riêng. Điều gì xảy ra nếu mã chuyên sâu của CPU nằm trong phương thức của đối tượng? Thật là tệ khi viết một tệp JS cho mọi phương thức sử dụng nhiều CPU.

Một đề xuất khác là sinh ra một tiến trình con, nhưng điều đó làm cho mã thậm chí ít được bảo trì hơn.

Bất kỳ đề xuất để vượt qua trở ngại (nhận thức) này? Làm thế nào để bạn viết mã hướng đối tượng sạch với Node.js trong khi đảm bảo các tác vụ nặng của CPU được thực thi không đồng bộ?


2
Olivier, bạn đã hỏi câu hỏi giống hệt tôi có trong đầu (mới đến nút) và đặc biệt liên quan đến việc xử lý hình ảnh. Trong Java, tôi có thể sử dụng một ExecutorService cố định và chuyển tất cả các công việc thay đổi kích thước và chờ nó kết thúc từ tất cả các kết nối, trong nút, tôi chưa tìm ra cách xáo trộn công việc với một mô-đun bên ngoài giới hạn (hãy nói) số lượng hoạt động đồng thời tối đa là 2 tại một thời điểm. Bạn đã tìm thấy một cách thanh lịch để làm điều này?
Riyad Kalla

Câu trả lời:


55

Những gì bạn cần là một hàng đợi nhiệm vụ! Di chuyển các tác vụ chạy dài của bạn ra khỏi máy chủ web là một điều TỐT. Giữ mỗi tác vụ trong tệp js "riêng biệt" sẽ thúc đẩy việc sử dụng lại mô đun và mã. Nó buộc bạn phải suy nghĩ về cách cấu trúc chương trình của bạn theo cách sẽ giúp dễ dàng gỡ lỗi và duy trì trong thời gian dài. Một lợi ích khác của hàng đợi nhiệm vụ là các công nhân có thể được viết bằng một ngôn ngữ khác. Chỉ cần bật một nhiệm vụ, thực hiện công việc và viết phản hồi lại.

một cái gì đó như thế này https://github.com/resque/resque

Dưới đây là một bài viết từ github về lý do tại sao họ xây dựng nó http://github.com/blog/542-int sinhing-resque


35
Tại sao bạn liên kết đến các thư viện Ruby trong một câu hỏi đặc biệt có căn cứ trong thế giới nút?
Jonathan Dumaine

1
@JonathanDumaine Đó là một triển khai tốt của hàng đợi nhiệm vụ. Rad mã ruby ​​và viết lại trong javascript. LỢI NHUẬN!
Simon Stender Boisen

2
Tôi là một fan hâm mộ lớn của gearman cho việc này, công nhân gearman không thăm dò máy chủ gearman cho công việc mới - công việc mới ngay lập tức được đẩy tới công nhân. Rất nhạy
Casey Flynn

1
Trên thực tế, ai đó đã chuyển nó đến thế giới nút: github.com/technoweenie/coffee-resque
FrontierPologistso

@pacerier, sao bạn lại nói thế? Đê xuât của bạn la gi?
luis.espinal

289

Đây là sự hiểu lầm về định nghĩa của máy chủ web - nó chỉ nên được sử dụng để "nói chuyện" với khách hàng. Các tác vụ tải nặng nên được ủy quyền cho các chương trình độc lập (tất nhiên điều đó cũng có thể được viết bằng JS).
Bạn có thể nói rằng nó bẩn, nhưng tôi đảm bảo với bạn rằng quy trình máy chủ web bị kẹt trong việc thay đổi kích thước hình ảnh chỉ tệ hơn (ngay cả khi cho phép nói Apache, khi nó không chặn các truy vấn khác). Tuy nhiên, bạn có thể sử dụng một thư viện chung để tránh sự dư thừa mã.

EDIT: Tôi đã đưa ra một sự tương tự; ứng dụng web nên là một nhà hàng. Bạn có người phục vụ (máy chủ web) và đầu bếp (công nhân). Nhân viên phục vụ tiếp xúc với khách hàng và thực hiện các nhiệm vụ đơn giản như cung cấp thực đơn hoặc giải thích nếu một số món ăn chay. Mặt khác, họ giao nhiệm vụ khó khăn hơn cho nhà bếp. Bởi vì những người phục vụ chỉ làm những việc đơn giản mà họ đáp ứng nhanh chóng và đầu bếp có thể tập trung vào công việc của họ.

Node.js ở đây sẽ là một người phục vụ duy nhất nhưng rất tài năng, có thể xử lý nhiều yêu cầu cùng một lúc và Apache sẽ là một nhóm người phục vụ câm chỉ xử lý một yêu cầu. Nếu người phục vụ Node.js này bắt đầu nấu ăn, đó sẽ là một thảm họa ngay lập tức. Tuy nhiên, nấu ăn cũng có thể làm cạn kiệt ngay cả một lượng lớn người phục vụ Apache, không đề cập đến sự hỗn loạn trong nhà bếp và sự giảm dần về độ nhạy.


6
Vâng, trong một môi trường mà các máy chủ web là đa luồng hoặc đa tiến trình và có thể xử lý nhiều hơn một yêu cầu đồng thời, rất phổ biến để dành một vài giây cho một yêu cầu. Mọi người đã mong đợi điều đó. Tôi muốn nói rằng sự hiểu lầm là node.js là một máy chủ web "thông thường". Sử dụng node.js, bạn phải điều chỉnh mô hình lập trình của mình một chút và điều đó bao gồm việc đẩy "hoạt động lâu dài" ra một số nhân viên không đồng bộ.
Thilo

13
Đừng sinh ra một tiến trình con cho mọi yêu cầu (đánh bại mục đích của node.js). Công nhân sinh sản từ bên trong yêu cầu nặng nề của bạn. Hoặc định tuyến công việc nặng nề của bạn đến một cái gì đó khác ngoài node.js.
Thilo

47
Tương tự tốt, mbq!
Lance Fisher

6
Ha, tôi thực sự thích điều đó. "Node.js: làm hủ tục làm việc tồi tệ"
ethan

7
@mbq Tôi thích sự tương tự nhưng nó có thể sử dụng một số công việc. Mô hình đa luồng truyền thống sẽ là một người vừa làm bồi bàn vừa nấu ăn. Sau khi đặt hàng, người đó phải quay lại và nấu bữa ăn trước khi có thể xử lý một đơn đặt hàng khác. Mô hình node.js có các nút là người phục vụ và webworkers là đầu bếp. Những người phục vụ xử lý tìm nạp / giải quyết các yêu cầu trong khi công nhân quản lý các nhiệm vụ tốn nhiều thời gian hơn. Nếu bạn cần mở rộng quy mô lớn hơn, bạn chỉ cần biến máy chủ chính thành một cụm nút và đảo ngược proxy, các tác vụ chuyên sâu của CPU đến các máy chủ khác được xây dựng để xử lý luồng xử lý.
Evan Plaice

16

Bạn không muốn mã chuyên sâu CPU của mình thực thi async, bạn muốn nó thực thi song song . Bạn cần phải xử lý công việc ra khỏi luồng phục vụ các yêu cầu HTTP. Đó là cách duy nhất để giải quyết vấn đề này. Với NodeJS, câu trả lời là mô đun cụm, để sinh sản các quá trình con để làm việc nặng. (AFAIK Node không có bất kỳ khái niệm nào về các luồng / bộ nhớ dùng chung; nó xử lý hoặc không có gì). Bạn có hai tùy chọn cho cách bạn cấu trúc ứng dụng của mình. Bạn có thể nhận được giải pháp 80/20 bằng cách sinh ra 8 máy chủ HTTP và xử lý đồng bộ các tác vụ chuyên sâu tính toán trên các quy trình con. Làm điều đó khá đơn giản. Bạn có thể mất một giờ để đọc về nó tại liên kết đó. Trong thực tế, nếu bạn chỉ trích xuất mã ví dụ ở đầu liên kết đó, bạn sẽ tự mình kiếm được 95%.

Một cách khác để cấu trúc điều này là thiết lập một hàng đợi công việc và gửi các tác vụ tính toán lớn qua hàng đợi. Lưu ý rằng có rất nhiều chi phí liên quan đến IPC cho hàng đợi công việc, vì vậy điều này chỉ hữu ích khi các tác vụ lớn hơn đáng kể so với chi phí chung.

Tôi ngạc nhiên rằng không ai trong số những câu trả lời khác thậm chí đề cập đến cụm.

Bối cảnh: Mã không đồng bộ là mã tạm dừng cho đến khi có điều gì đó xảy ra ở một nơi khác , tại thời điểm đó, mã sẽ thức dậy và tiếp tục thực thi. Một trường hợp rất phổ biến khi một cái gì đó chậm phải xảy ra ở một nơi khác là I / O.

Mã không đồng bộ không hữu ích nếu bộ xử lý của bạn chịu trách nhiệm thực hiện công việc. Đó chính xác là trường hợp với các nhiệm vụ "tính toán chuyên sâu".

Bây giờ, có vẻ như mã không đồng bộ là thích hợp, nhưng thực tế nó rất phổ biến. Nó chỉ xảy ra không hữu ích cho việc tính toán các nhiệm vụ chuyên sâu.

Chờ đợi trên I / O là một mô hình luôn xảy ra trong các máy chủ web, ví dụ. Mỗi khách hàng kết nối với máy chủ của bạn đều có một ổ cắm. Hầu hết thời gian các ổ cắm đều trống rỗng. Bạn không muốn làm bất cứ điều gì cho đến khi một ổ cắm nhận được một số dữ liệu, tại thời điểm đó bạn muốn xử lý yêu cầu. Dưới vỏ bọc, một máy chủ HTTP như Node đang sử dụng thư viện sự kiện (libev) để theo dõi hàng ngàn ổ cắm mở. HĐH thông báo cho libev, và sau đó libev thông báo cho NodeJS khi một trong các socket nhận dữ liệu và sau đó NodeJS đặt một sự kiện vào hàng đợi sự kiện và mã http của bạn sẽ khởi động lần này và xử lý các sự kiện lần lượt. Các sự kiện không được đưa vào hàng đợi cho đến khi ổ cắm có một số dữ liệu, vì vậy các sự kiện không bao giờ chờ đợi trên dữ liệu - nó đã có sẵn cho chúng.

Các máy chủ web dựa trên sự kiện đơn luồng có ý nghĩa như một mô hình khi nút cổ chai đang chờ trên một loạt các kết nối ổ cắm trống và bạn không muốn toàn bộ luồng hoặc xử lý cho mọi kết nối nhàn rỗi và bạn không muốn thăm dò 250k của mình ổ cắm để tìm cái tiếp theo có dữ liệu về nó.


nên trả lời đúng .... như đối với giải pháp khi bạn sinh ra 8 cụm, bạn sẽ cần 8 lõi phải không? Hoặc tải cân bằng với nhiều máy chủ.
Muhammad Umer

cũng là cách tốt để tìm hiểu về giải pháp thứ 2, thiết lập một hàng đợi. Khái niệm về hàng đợi khá đơn giản, nhưng đó là phần nhắn tin giữa các quy trình và hàng đợi là nước ngoài.
Muhammad Umer

Đúng rồi. Bạn cần phải đưa công việc vào cốt lõi khác, bằng cách nào đó. Đối với điều đó, bạn cần một cốt lõi khác.
masonk

Re: hàng đợi. Câu trả lời thực tế là sử dụng hàng đợi công việc. Có một số có sẵn cho nút. Tôi chưa bao giờ sử dụng bất kỳ trong số họ vì vậy tôi không thể đưa ra một đề nghị. Câu trả lời tò mò là các quy trình công nhân và quy trình xếp hàng cuối cùng sẽ giao tiếp qua các ổ cắm.
masonk

7

Vài cách tiếp cận bạn có thể sử dụng.

Như @Tim lưu ý, bạn có thể tạo một tác vụ không đồng bộ nằm bên ngoài hoặc song song với logic phục vụ chính của bạn. Phụ thuộc vào yêu cầu chính xác của bạn, nhưng ngay cả cron cũng có thể hoạt động như một cơ chế xếp hàng.

WebWorkers có thể hoạt động cho các quy trình không đồng bộ của bạn nhưng hiện tại chúng không được hỗ trợ bởi node.js. Có một số tiện ích mở rộng cung cấp hỗ trợ, ví dụ: http://github.com/cramforce/node-worker

Bạn vẫn nhận được bạn vẫn có thể sử dụng lại các mô-đun và mã thông qua cơ chế "yêu cầu" tiêu chuẩn. Bạn chỉ cần đảm bảo rằng công văn ban đầu cho nhân viên chuyển tất cả thông tin cần thiết để xử lý kết quả.


0

Sử dụng child_processlà một giải pháp. Nhưng mỗi quá trình con sinh ra có thể tiêu tốn rất nhiều bộ nhớ so với Gogoroutines

Bạn cũng có thể sử dụng giải pháp dựa trên hàng đợi như kue

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.