日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關咨詢
選擇下列產品馬上在線溝通
服務時間:8:30-17:00
你可能遇到了下面的問題
關閉右側工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
在Node.js中使用SO_RESUEPORT

前言:今天下載了Node.js最新版代碼,并為Node.js的TCP模塊增加了SO_RESUEPORT的能力,本文介紹一下具體的實現(xiàn),關于SO_RESUEPORT的知識可以參考之前的文章或者網(wǎng)上文章。

1 Libuv

SO_RESUEPORT是操作系統(tǒng)內核提供的能力,所以第一步首先修改Libuv??紤]到操作系統(tǒng)兼容性的問題,目前只支持Linux系統(tǒng),舊版Mac OS也支持相關屬性但是效果不符合預期,新版Mac OS倒是支持,考慮到Node.js在幾乎都是部署到Linux,所以可以先關注Linux內核。首先修改deps/uv/include/uv.h。

 
 
 
 
  1. enum uv_tcp_flags { 
  2.   UV_TCP_IPV6ONLY = 1, 
  3.   // 支持SO_RESUEPORT flags 
  4.   UV_TCP_REUSEPORT = 2 
  5.  
  6. }; 

接著修改deps/uv/src/unix/tcp.c。

 
 
 
 
  1. #if defined(SO_REUSEPORT) && defined(__linux__)  
  2.   on = 1; 
  3.   if ((flags & UV_TCP_REUSEPORT) && setsockopt(tcp->io_watcher.fd, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on))) 
  4.     return UV__ERR(errno); 
  5. #endif 

這里判斷一下是否有兩個宏,有的話才能使用SO_RESUEPORT。如果支持則通過setsockopt設置socket的SO_REUSEPORT標記,這是最核心的邏輯。

2 修改C++層

修改完底層的Libuv后,繼續(xù)修改C++層,因為這是一個可選的屬性,所以我們需要增加相關的邏輯。修改src/tcp_wrap.cc。首先導出一個新的常量

 
 
 
 
  1. #if defined(SO_REUSEPORT) && defined(__linux__)  
  2.  NODE_DEFINE_CONSTANT(constants, UV_TCP_REUSEPORT); 
  3.  
  4. #endif 

在JS層可以通過判斷是否導出了這個常量來判斷系統(tǒng)是否支持SO_RESUEPORT。接著修改bind函數(shù),因為我們再bind的時候可以設置SO_RESUEPORT。

 
 
 
 
  1. template  
  2.  
  3. void TCPWrap::Bind( 
  4.  
  5.     const FunctionCallbackInfo& args, 
  6.     int family, 
  7.     std::function uv_ip_addr) { 
  8.   TCPWrap* wrap; 
  9.   ASSIGN_OR_RETURN_UNWRAP(&wrap, 
  10.                           args.Holder(), 
  11.                           args.GetReturnValue().Set(UV_EBADF)); 
  12.   Environment* env = wrap->env(); 
  13.   node::Utf8Value ip_address(env->isolate(), args[0]); 
  14.   int port; 
  15.   unsigned int flags = 0; 
  16.   if (!args[1]->Int32Value(env->context()).To(&port)) return; 
  17.   // ipv6支持ipv6Only和SO_RESUEPORT 
  18.   if (family == AF_INET6 && 
  19.       !args[2]->Uint32Value(env->context()).To(&flags)) { 
  20.     return; 
  21.   // ipv4之前是不支持任何標記的,這里需要加上這個邏輯,因為我們需要支持SO_RESUEPORT 
  22.   } else if (family == AF_INET4 && 
  23.       !args[2]->Uint32Value(env->context()).To(&flags)) { 
  24.     return; 
  25.   } 
  26.  
  27.   T addr; 
  28.   int err = uv_ip_addr(*ip_address, port, &addr); 
  29.  
  30.   if (err == 0) { 
  31.     err = uv_tcp_bind(&wrap->handle_, 
  32.                       reinterpret_cast(&addr), 
  33.                       flags); 
  34.   } 
  35.   args.GetReturnValue().Set(err); 
  36.  

C++主要是完成透傳flags的邏輯。

3 修改JS層

修改JS層是最復雜的地方,主要是為了應用層的兼容性問題。也就是說如果Node.js真的支持了SO_RESUEPORT,在某些平臺不支持SO_RESUEPORT的情況下,我們如何能保證我們的代碼能在各個平臺上跑。簡單來說,如果我們平臺支持SO_RESUEPORT,我們可以開啟多個子進程,然后分別執(zhí)行以下代碼。

 
 
 
 
  1. const http = require('http'); 
  2. http.createServer((req, res) => { 
  3.     res.end('hello'); 
  4.  
  5. }) 
  6.  
  7. .listen({port: 8000, reuseport: true}); 

這時候,只需要修改一下Node.js的net.js,把reuseport標記傳到C++層再傳到Libuv就行,但是問題是,如果我們這樣寫代碼,就無法在不支持SO_RESUEPORT的平臺跑了,因為會導致重復監(jiān)聽端口的錯誤。所以為了兼容性,我想的方案是利用Cluster模塊,目前Cluster模塊支持輪詢和共享兩種模式,那么我們再加一種reuseport模式就好了,這樣的好處是一旦我們平臺不支持SO_RESUEPORT,我們可以降級到Node.js現(xiàn)在到模式。我們知道Cluster模塊的原理有兩種,一種是主進程監(jiān)聽,分發(fā)連接給子進程,另一種是主進程創(chuàng)建socket,通過文件描述符傳遞的方式傳給子進程,所有的進程都是共享一個socket的。下面我們看看怎么做。首先修改lib/internal/cluster/primary.js。

 
 
 
 
  1. // 增加這if的邏輯 
  2. if ((message.addressType === 4 ||  
  3.  message.addressType === 6) &&  
  4.  (message.flags & TCPConstants.UV_TCP_REUSEPORT)) { 
  5.  handle = new ReusePort(key, address, message); 
  6. } else if (schedulingPolicy !== SCHED_RR || 
  7.     message.addressType === 'udp4' || 
  8.     message.addressType === 'udp6') { 
  9.   handle = new SharedHandle(key, address, message); 
  10. } else { 
  11.   handle = new RoundRobinHandle(key, address, message); 

我們在queryServer函數(shù)里增加了一個if的邏輯。如果addressType是4或6說明是TCP協(xié)議,并且設置了UV_TCP_REUSEPORT(listen的時候傳入),就會走到reuseport的邏輯,剩下的兩個else是目前Node.js的邏輯。我們看看ReusePort.js做了什么。

 
 
 
 
  1. 'use strict'; 
  2.  
  3. const assert = require('internal/assert'); 
  4.  
  5. const net = require('net'); 
  6.  
  7. const { constants: TCPConstants } = internalBinding('tcp_wrap'); 
  8.  
  9.  
  10. module.exports = ReusePort; 
  11.  
  12. function ReusePort(key, address, {port, addressType, fd, flags}) { 
  13.   this.key = key; 
  14.   this.workers = []; 
  15.   this.handles = []; 
  16.   this.list = [address, port, addressType, fd, flags]; 
  17.  
  18.  
  19.  
  20. ReusePort.prototype.add = function(worker, send) { 
  21.   assert(!this.workers.includes(worker)); 
  22.   const rval = net._createServerHandle(...this.list); 
  23.   let errno; 
  24.   let handle; 
  25.   if (typeof rval === 'number') 
  26.     errno = rval; 
  27.   else 
  28.     handle = rval; 
  29.   this.workers.push(worker); 
  30.   this.handles.push(handle); 
  31.   send(errno, null, handle); 
  32.  
  33. }; 
  34.  
  35.  
  36. ReusePort.prototype.remove = function(worker) { 
  37.   const index = this.workers.indexOf(worker); 
  38.  
  39.   if (index === -1) 
  40.     return false; // The worker wasn't sharing this handle. 
  41.  
  42.   this.workers.splice(index, 1); 
  43.   this.handles[index].close(); 
  44.   this.handles.splice(index, 1); 
  45.   return true; 
  46.  
  47. }; 

上面的代碼我們只需要關注net._createServerHandle。在不能多個進程同時監(jiān)聽同一個端口的情況下,Node.js只會調net._createServerHandle創(chuàng)建一個socket,然后多個進程共享。而我們這里會給每個進程創(chuàng)建一個socket。這個socket就是在子進程調用queryServer的時候返回給子進程的。剩下的邏輯我們暫時不用關注。最后看一下_createServerHandle的邏輯。

 
 
 
 
  1. const handle = new TCP(TCPConstants.SERVER); 
  2.  
  3. if (addressType === 6) { 
  4.  
  5.   err = handle.bind6(address, port, flags);}  
  6.  
  7. else { 
  8.  
  9.   err = handle.bind(address, port, flags || 0); 
  10.  

_createServerHandle的邏輯是創(chuàng)建一個socket并且給socket綁定IP和端口,我們看到這里會給C++層傳入flags,C++層就會傳到LIbuv了,這樣我們就完成了整個過程,整體的流程如下。

1 子進程執(zhí)行l(wèi)isten的時候,傳入reuseport為true

2 子進程通過進程間通信請求主進程

3 主進程返回一個新的socket并綁定到對應的地址

4 子進程執(zhí)行l(wèi)isten啟動服務器。

4 使用

接下來我們看看如何使用,首先創(chuàng)建一個server.js。

 
 
 
 
  1. const cluster = require('cluster'); 
  2.  
  3. const os = require('os'); 
  4.  
  5. const http = require('http'); 
  6.  
  7. const cpus = os.cpus().length; 
  8.  
  9.  
  10.  
  11.  if (cluster.isPrimary) { 
  12.  
  13.   const map = {}; 
  14.   for (let i = 0; i < cpus; i++) { 
  15.     const worker = cluster.fork(); 
  16.     map[worker.process.pid] = 0; 
  17.     worker.on('message', (pid) => { 
  18.         map[pid]++; 
  19.     }); 
  20.   } 
  21.  
  22.   process.on('SIGINT', () => { 
  23.     console.log(map); 
  24.   }); 
  25.  
  26. } else { 
  27.  
  28.   http.createServer((req, res) => { 
  29.       process.send(process.pid); 
  30.       res.end('hello'); 
  31.   }) 
  32.  
  33.   .listen({reuseport: true, port: 8000}); 
  34.  

再創(chuàng)建一個客戶端client.js

 
 
 
 
  1. const http = require('http'); 
  2. function connect() { 
  3.     setTimeout(() => { 
  4.         http.get('http://localhost:8000/', (res) => { 
  5.             console.log(res.statusCode); 
  6.             connect(); 
  7.         }); 
  8.     }, 50); 
  9.  
  10.  
  11. connect(); 

客戶端串行訪問服務器,我們看到使用方式和目前Node.js的Cluster使用一樣。即使我們把reuseport改成false或者其他平臺跑也沒問題,效果如下

我們看到在reuseport的情況下,負載還是挺均衡的。

后記:目前是通過listen的時候傳入?yún)?shù)去控制是否開啟SO_RESUEPORT的,后續(xù)可以增加通過設置cluster.schedulingPolicy的方式,和目前共享、輪詢模式對齊,考慮到Cluster模塊不是必須,因為我們可以直接用子進程模塊監(jiān)聽同一個端口。所以通過listen函數(shù)去控制是非常必要的。目前通過修改Node.js內核大概體驗了一下SO_RESUEPORT,后續(xù)review和改進一下代碼。


網(wǎng)站名稱:在Node.js中使用SO_RESUEPORT
網(wǎng)頁URL:http://www.5511xx.com/article/coisjdj.html