{
    "componentChunkName": "component---src-templates-blog-post-tsx",
    "path": "/phan-phoi-xu-ly-trong-nodejs-cluster/",
    "result": {"data":{"site":{"siteMetadata":{"title":"Chung Tran","author":"@chungtran4078"}},"markdownRemark":{"id":"1c2bdf47-9d26-5dfc-aed9-00c3dad313f4","excerpt":"Ở phần 1 và phần 2 chúng ta đã tìm hiểu về Child processes và Worker threads, cả 2 đều là giải pháp đưa tác vụ nặng ra khỏi luồng chính để tránh block các…","html":"<p>Ở phần 1 và phần 2 chúng ta đã tìm hiểu về Child processes và Worker threads, cả 2 đều là giải pháp đưa tác vụ nặng ra khỏi luồng chính để tránh block các request khác. Cả 2 đều hoạt động dựa trên ý tưởng: “ê ku, ra chỗ khác mà chạy cái code tốn CPU của mày đi, đừng có đứng đây làm tắc đường”.</p>\n<p><span\n      class=\"gatsby-resp-image-wrapper\"\n      style=\"position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; \"\n    >\n      <a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/448cec2cb306faf00cebce01c446cb5d/71c1d/image.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-background-image\"\n    style=\"padding-bottom: 66.89189189189189%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAAsTAAALEwEAmpwYAAACJElEQVQoz1VS2W7bMBD0/39Pn9oCaYC0bhzXaZLWziFZsmzrsGWJN8XlctkqNgqUIIh5mFkOOTMhohACEQ2DA/DJOv/85Wa+eLyd399M7+aLx+ns/uv3H9PZItsUIZy5lzUhohgjEdnBSaWlVJwLIZQQkguplBFSdT2rmq4+dFqbGOM//SSEYO1wGREpUAgUiAKNnGCdIYqdhLI19clwDf/dzLhYPCy1HknG622bp2WS1gmzXSe7vM57yWav/GndvRSs7KCXrmyVR4wxTqTSq9fUGAvgNajikH26+3A9veKus2CJyAz2Za/SUua1anqQxrth3N7jBBGLfWWsFUKdRHuQ1f16ltWJBKadRkQ92J9rvsz71y2re+Aa6h2fXyeit6M42+ysHTiXR9EUXdqwqmFlb1rttEdU1jxk/Llg6V7UDJiGIjvdXb+1lRjf/Gv1prRBj9x2L/XvVb583iyPqjHOIHrj7FMuVgVL9qLqnTTeGpDcOgsT56Dr2eBcpKidakR1Um2nWjlwBwOGcBa/bvusZAcOA4xfdY7mknOMEfG9AYEQkSgGDIjoPAQk79E6j4jgEQDdAN5jCO9ibezHq2/G2oDE9Kno0nEIhqNqDrKKIVoHt88dk45i4L1+e9qdezmJRMbYc1pEtKvqrNhaOxhjN8U+3+6cA6lUmu+35SEErOtjkhRCyks9Qxgdvp8BwAOMDkeTDs4YvB+Rhwv2Y8h/xX8A7+TfPUpMwgkAAAAASUVORK5CYII='); background-size: cover; display: block;\"\n  ></span>\n  <img\n        class=\"gatsby-resp-image-image\"\n        alt=\"Cluster trong Nodejs\"\n        title=\"Cluster trong Nodejs\"\n        src=\"/static/448cec2cb306faf00cebce01c446cb5d/fcda8/image.png\"\n        srcset=\"/static/448cec2cb306faf00cebce01c446cb5d/12f09/image.png 148w,\n/static/448cec2cb306faf00cebce01c446cb5d/e4a3f/image.png 295w,\n/static/448cec2cb306faf00cebce01c446cb5d/fcda8/image.png 590w,\n/static/448cec2cb306faf00cebce01c446cb5d/efc66/image.png 885w,\n/static/448cec2cb306faf00cebce01c446cb5d/c83ae/image.png 1180w,\n/static/448cec2cb306faf00cebce01c446cb5d/71c1d/image.png 1536w\"\n        sizes=\"(max-width: 590px) 100vw, 590px\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;\"\n        loading=\"lazy\"\n        decoding=\"async\"\n      />\n  </a>\n    </span></p>\n<p>Còn với Cluster thì sao? Khác với 2 ông trên, Cluster không đưa tác vụ nặng ra chỗ khác, mà nó… <strong>nhân bản toàn bộ server của bạn lên</strong>. Mỗi bản sao chạy trên một process riêng, mỗi process xài một nhân CPU riêng. Và điều kỳ diệu là tất cả chúng <strong>cùng chia sẻ chung một cổng (port)</strong>. Request vào sẽ được phân phối cho các worker xử lý thông qua thuật toán round-robin.</p>\n<p>Nói cách khác, nếu Child processes và Worker threads giống như thuê thêm thợ phụ để làm mấy việc nặng cho mình, thì Cluster giống như… mở thêm 4 cái quán y chang cái quán hiện tại, cùng bán chung một mặt tiền, khách vào được chia đều cho các quán. Quán nào đang bận thì khách được đưa sang quán khác.</p>\n<p>Quay lại bài toán tính số fibonacci từ phần 1, lần này thay vì fork ra child process hay tạo worker thread, chúng ta sẽ dùng cluster để fork ra nhiều instance của chính server đó.</p>\n<h2>Cách Cluster hoạt động</h2>\n<p>Đầu tiên cần hiểu cluster trong Nodejs không phải là load balancer như Nginx. Nó là một module cho phép bạn tạo ra nhiều process con (worker) từ một process cha (master). Tất cả worker cùng lắng nghe trên một port, master sẽ phân phối kết nối đến cho từng worker.</p>\n<p>Module <code class=\"language-text\">cluster</code> có sẵn trong Nodejs, không cần cài thêm gì hết. Flow hoạt động cơ bản:</p>\n<ol>\n<li>Master process khởi động, fork ra N worker (thường là bằng số nhân CPU)</li>\n<li>Mỗi worker là một instance độc lập của ứng dụng</li>\n<li>Master lắng nghe port và phân phối request tới worker qua round-robin</li>\n<li>Nếu worker chết, master có thể tự động fork worker mới thay thế</li>\n</ol>\n<h2>Code thực tế</h2>\n<p>Chúng ta sẽ tách code thành 2 file: <code class=\"language-text\">cluster.js</code> (entry point cho master) và <code class=\"language-text\">server.js</code> (code server mà mỗi worker sẽ chạy).</p>\n<p><strong>File: <code class=\"language-text\">cluster.js</code></strong></p>\n<div class=\"gatsby-highlight\" data-language=\"javascript\"><pre class=\"language-javascript\"><code class=\"language-javascript\"><span class=\"token keyword\">import</span> cluster <span class=\"token keyword\">from</span> <span class=\"token string\">\"cluster\"</span>\n<span class=\"token keyword\">import</span> os <span class=\"token keyword\">from</span> <span class=\"token string\">\"os\"</span>\n<span class=\"token keyword\">import</span> fs <span class=\"token keyword\">from</span> <span class=\"token string\">\"fs\"</span>\n\n<span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>cluster<span class=\"token punctuation\">.</span>isMaster<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  console<span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"[Cluster] Master process đang bắt đầu chạy.\"</span><span class=\"token punctuation\">,</span> process<span class=\"token punctuation\">.</span>pid<span class=\"token punctuation\">)</span>\n\n  <span class=\"token comment\">// Fork worker dựa trên số nhân CPU</span>\n  <span class=\"token keyword\">for</span> <span class=\"token punctuation\">(</span><span class=\"token keyword\">var</span> i <span class=\"token operator\">=</span> <span class=\"token number\">0</span><span class=\"token punctuation\">,</span> coreCount <span class=\"token operator\">=</span> os<span class=\"token punctuation\">.</span><span class=\"token function\">cpus</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span>length<span class=\"token punctuation\">;</span> i <span class=\"token operator\">&lt;</span> coreCount<span class=\"token punctuation\">;</span> i<span class=\"token operator\">++</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    cluster<span class=\"token punctuation\">.</span><span class=\"token function\">fork</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n  <span class=\"token punctuation\">}</span>\n\n  <span class=\"token comment\">// Khi worker chết (ví dụ do uncaught exception)</span>\n  <span class=\"token comment\">// Master sẽ tự động tạo worker mới thay thế</span>\n  cluster<span class=\"token punctuation\">.</span><span class=\"token function\">on</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"exit\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">worker<span class=\"token punctuation\">,</span> code<span class=\"token punctuation\">,</span> signal</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n    console<span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"[Cluster] Worker kết thúc.\"</span><span class=\"token punctuation\">,</span> worker<span class=\"token punctuation\">.</span>process<span class=\"token punctuation\">.</span>pid<span class=\"token punctuation\">)</span>\n    console<span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"[Cluster] Tử ẹo:\"</span><span class=\"token punctuation\">,</span> worker<span class=\"token punctuation\">.</span>exitedAfterDisconnect<span class=\"token punctuation\">)</span>\n\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token operator\">!</span>worker<span class=\"token punctuation\">.</span>exitedAfterDisconnect<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n      <span class=\"token comment\">// Worker chết bất đắc kỳ tử -> tạo mới</span>\n      cluster<span class=\"token punctuation\">.</span><span class=\"token function\">fork</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n    <span class=\"token punctuation\">}</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">}</span> <span class=\"token keyword\">else</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token comment\">// Code trong nhánh này sẽ được chạy bởi worker</span>\n  <span class=\"token function\">eval</span><span class=\"token punctuation\">(</span>fs<span class=\"token punctuation\">.</span><span class=\"token function\">readFileSync</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"./server.js\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"utf8\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n  console<span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"[Worker] Worker bắt đầu.\"</span><span class=\"token punctuation\">,</span> process<span class=\"token punctuation\">.</span>pid<span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p><strong>Một vài điểm cần lưu ý:</strong></p>\n<ul>\n<li><code class=\"language-text\">cluster.isMaster</code> để phân biệt master process và worker process. Từ Node 16+ thì nên dùng <code class=\"language-text\">cluster.isPrimary</code> (do <code class=\"language-text\">isMaster</code> đã bị deprecated).</li>\n<li>Worker chết vì lỗi sẽ được tự động fork lại. Cẩn thận nếu code worker có bug khiến nó crash ngay khi khởi tạo, bạn sẽ dính vòng lặp fork-chết-fork vô hạn, CPU lên 100% trong nháy mắt.</li>\n<li><code class=\"language-text\">eval(fs.readFileSync(...))</code> là cách chạy code server trong worker mà không cần require/import động. Cách làm này không phải best practice nhưng tiện cho demo. Production thì nên tách hẳn worker thành file riêng và dùng <code class=\"language-text\">cluster.setupMaster()</code>.</li>\n</ul>\n<p><strong>File: <code class=\"language-text\">server.js</code></strong></p>\n<div class=\"gatsby-highlight\" data-language=\"javascript\"><pre class=\"language-javascript\"><code class=\"language-javascript\"><span class=\"token keyword\">import</span> http <span class=\"token keyword\">from</span> <span class=\"token string\">\"http\"</span>\n\nhttp<span class=\"token punctuation\">.</span><span class=\"token function\">createServer</span><span class=\"token punctuation\">(</span>\n  <span class=\"token keyword\">function</span> <span class=\"token function\">handleRequest</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">request<span class=\"token punctuation\">,</span> response</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    response<span class=\"token punctuation\">.</span><span class=\"token function\">writeHead</span><span class=\"token punctuation\">(</span><span class=\"token number\">200</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span> <span class=\"token string\">\"Content-Type\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"text/html\"</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n    response<span class=\"token punctuation\">.</span><span class=\"token function\">write</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Hello world from process \"</span> <span class=\"token operator\">+</span> process<span class=\"token punctuation\">.</span>pid <span class=\"token operator\">+</span> <span class=\"token string\">\".\"</span><span class=\"token punctuation\">)</span>\n\n    <span class=\"token comment\">// Giữ request mở 300ms để demo round-robin</span>\n    <span class=\"token function\">setTimeout</span><span class=\"token punctuation\">(</span>response<span class=\"token punctuation\">.</span><span class=\"token function\">end</span><span class=\"token punctuation\">.</span><span class=\"token function\">bind</span><span class=\"token punctuation\">(</span>response<span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span> <span class=\"token number\">300</span><span class=\"token punctuation\">)</span>\n  <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">listen</span><span class=\"token punctuation\">(</span><span class=\"token number\">8000</span><span class=\"token punctuation\">)</span></code></pre></div>\n<p>Server đơn giản lắng nghe trên port 8000. Điều thú vị là mỗi worker đều gọi <code class=\"language-text\">.listen(8000)</code> nhưng không hề bị lỗi <code class=\"language-text\">EADDRINUSE</code>. Đây chính là “phép thuật” của cluster: các worker chia sẻ port thông qua IPC với master, master sẽ tiếp nhận connection và phân phối cho worker.</p>\n<h2>Chạy thử</h2>\n<div class=\"gatsby-highlight\" data-language=\"bash\"><pre class=\"language-bash\"><code class=\"language-bash\">node cluster.js</code></pre></div>\n<p>Mở trình duyệt, truy cập <code class=\"language-text\">http://localhost:8000</code>, refresh vài lần. Bạn sẽ thấy <code class=\"language-text\">process.pid</code> thay đổi qua mỗi lần refresh — minh chứng cho việc request đang được phân phối luân phiên qua các worker.</p>\n<h2>Vậy khi nào dùng gì?</h2>\n<table>\n<thead>\n<tr>\n<th></th>\n<th>Child processes</th>\n<th>Worker threads</th>\n<th>Cluster</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><strong>Dùng khi</strong></td>\n<td>Cần chạy code ngôn ngữ khác (Python, C++, …)</td>\n<td>Cần chia sẻ bộ nhớ giữa các thread</td>\n<td>Cần scale server theo chiều ngang trên cùng 1 máy</td>\n</tr>\n<tr>\n<td><strong>Bộ nhớ</strong></td>\n<td>Độc lập, tốn RAM</td>\n<td>Chia sẻ (SharedArrayBuffer)</td>\n<td>Độc lập, tốn RAM</td>\n</tr>\n<tr>\n<td><strong>Giao tiếp</strong></td>\n<td>IPC (send/on)</td>\n<td>postMessage, MessagePort</td>\n<td>Không trực tiếp (qua master)</td>\n</tr>\n<tr>\n<td><strong>Overhead</strong></td>\n<td>Nặng (tạo process mới)</td>\n<td>Nhẹ hơn process</td>\n<td>Trung bình</td>\n</tr>\n</tbody>\n</table>\n<p>Child processes và Worker threads là giải pháp cho bài toán “đưa code nặng ra khỏi luồng chính”. Còn Cluster là giải pháp cho bài toán “tận dụng hết CPU để xử lý nhiều request cùng lúc”. Chúng không thay thế nhau, mà bổ trợ cho nhau.</p>\n<p>Thực tế, không ai cấm bạn dùng <strong>Cluster + Worker threads</strong> cùng lúc: mỗi worker trong cluster lại tự tạo worker threads riêng để xử lý tác vụ nặng. Khi đó bạn vừa scale được theo chiều ngang (cluster), vừa tối ưu được CPU-intensive task (worker threads). Đây cũng là cách mà các framework như NestJS hay các production app thường triển khai.</p>\n<p>Nói đến đây tự nhiên nhớ hồi đi phỏng vấn, được hỏi “làm sao để Nodejs chạy đa luồng?”, mình trả lời “dùng cluster”, ông interviewer cười khẩy “cluster có phải đa luồng đâu”. Mình cũng cười theo vì thực ra ổng nói đúng, cluster là đa process chứ không phải đa luồng. Nhưng kệ đi, quan trọng là biết dùng đúng chỗ.</p>\n<p>Hy vọng qua 3 phần các bạn đã có cái nhìn tổng quan về các phương án phân phối xử lý trong Nodejs. Tùy bài toán mà chọn vũ khí phù hợp, đừng có cầm súng săn voi đi bắn chuột.</p>","frontmatter":{"title":"Phân phối xử lý trong Nodejs P3: Cluster","date":"2022-05-30","category":"Nodejs","tags":["nodejs","performance","optimization"],"image":{"childImageSharp":{"gatsbyImageData":{"layout":"constrained","backgroundColor":"#f8f8f8","images":{"fallback":{"src":"/static/448cec2cb306faf00cebce01c446cb5d/fea51/image.png","srcSet":"/static/448cec2cb306faf00cebce01c446cb5d/c1c85/image.png 158w,\n/static/448cec2cb306faf00cebce01c446cb5d/8858c/image.png 315w,\n/static/448cec2cb306faf00cebce01c446cb5d/fea51/image.png 630w,\n/static/448cec2cb306faf00cebce01c446cb5d/1988a/image.png 1260w","sizes":"(min-width: 630px) 630px, 100vw"},"sources":[{"srcSet":"/static/448cec2cb306faf00cebce01c446cb5d/496d3/image.webp 158w,\n/static/448cec2cb306faf00cebce01c446cb5d/ef980/image.webp 315w,\n/static/448cec2cb306faf00cebce01c446cb5d/6f8b3/image.webp 630w,\n/static/448cec2cb306faf00cebce01c446cb5d/1dc1a/image.webp 1260w","type":"image/webp","sizes":"(min-width: 630px) 630px, 100vw"}]},"width":630,"height":420}}}}}},"pageContext":{"slug":"/phan-phoi-xu-ly-trong-nodejs-cluster/","previous":{"fields":{"slug":"/phan-phoi-xu-ly-trong-nodejs-worker-threads/"},"frontmatter":{"title":"Phân phối xử lý trong Nodejs P2: Worker threads","category":"Nodejs","tags":["nodejs","performance","optimization"]}},"next":{"fields":{"slug":"/gatsby-github-pages-blog-mien-phi/"},"frontmatter":{"title":"Gatsby + GitHub Pages = Blog miễn phí trọn đời, nghe như lừa đảo nhưng thật 100%","category":"Frontend","tags":["gatsby","react","github","blog"]}}}},
    "staticQueryHashes": ["3649515864","3894061898"]}