2017-11-03 77 views
7

Po uruchomieniu aplikacji node app.js proces ma tylko jeden wątek. Im dłużej trwa, tym więcej wątków jest tworzonych dla tego procesu. Problem polega na tym, że gdy chcę wykonać specyficzny rodzaj kodu tak:node.js wymusza tylko jeden wątek, aby wykonać kod

var io = require('socket.io')(process.env.PORT); 

To nie dlatego, że sygnał został wysłany z wielu wątków i dlatego kod isnt pomyślnie wykonana.

prosty test, jeśli ktoś to zrobić:

var io = require('socket.io')(9001); 
var io = require('socket.io')(9002); 
var io = require('socket.io')(9003); 
var io = require('socket.io')(9004); 

to działa dobrze, ale ten kod:

var cPort = 9001; 
setInterval(function() { 
    var io = require('socket.io')(cPort); 
    cPort++; 
}, 1000 * 60 * 2); // 1 sec * 60 seconds * 2 = 2 minutes interval 

przyzwyczajenie być wykonywane, ponieważ po 2 minutach węzeł będzie mieć wiele wątków i wszyscy spróbuj wykonać kod - w rezultacie zobaczysz error: address in use.

Tak więc pomimo uruchamiania wielowątkowego procesu tego samego pliku, jak mogę zmusić węzeł do wykonania tego kodu tylko raz?

06.11.2017 EDIT ----

W celu wyjaśnienia problemu:

co mi chodzi w pytaniu, nie mam problemu z zasobów, jeśli zacznę wszystkich serwerów na raz (na przykład 40 serwerów) wszystkie są uruchamiane pomyślnie i działają bezterminowo. Problem może się pojawić, jeśli uruchomię tylko jeden serwer, a następnie uruchomę kod, który automatycznie uruchamia się w razie potrzeby. W tym momencie zawsze widzę błąd address in use, oczywiście adres nie jest używany w momencie wykonania kodu. Obecnie muszę ręcznie uruchamiać więcej serwerów w weekendy, kiedy w innych dniach tygodnia jest więcej osób korzystających z usług i mniej serwerów, chciałem stworzyć zautomatyzowany system, który uruchamia i zamyka serwery w oparciu o populację.

jest to kod serwerów rozruchu:

var cp = require('child_process'), 
    servers = [], 
    per_server = config.per_server, 
    check_servers = function(callback) { 
     for(var i = 0; i < servers.length; i++) { 
      callback(i, servers[i]); 
     } 
    }; 

this.add_server = function(port) { 
    var server = { 
     port: port, 
     load: 0, 
     process: cp.fork(__dirname + '/../server_instance.js', [], { 
      env: { 
       port: port 
      } 
     }) 
    }; 

    server.process.on('message', function(message) { 
     server.load = message.load; 
    }); 

    servers.push(server); 
}; 

this.find_server = function() { 
    var min = Infinity, 
     port = false; 

    check_servers(function(index, details) { 
     if(details.load < min) { 
      min = details.load; 
      port = details.port; 
     } 
    }); 

    return port; 
}; 

teraz gdybym wykonać controller.add_server() 40 razy w wierszu rozpocznie 40 serwerów poprawnie, ale jeśli mogę to zrobić:

var start_port = 3185; 
setInterval(function() { 
    var min = Infinity; 

    check_servers(function(index, details) { 
     if(details.load < min) { 
      min = details.load; 
     } 
    }); 

    if(min > config.per_server) { 
     controller.add_server(start_port); 
     start_port++; 
    } 
}, 5000); 

otrzymuję losowy błąd podczas tworzenia drugiego, trzeciego i czwartego serwera, którego adres jest już używany.

07.11.2017 EDIT ----

Jak sugeruje Próbowałem następujących bibliotek do skanowania portów/Finder:

Tylko używając Pierwszy z nich był w stanie uruchomić co najmniej 2 serwery, jest to kod, którego użyłem:

setInterval(function() { 
    var min = Infinity; 

    check_servers(function(index, details) { 
     if(details.load < min) { 
      min = details.load; 
     } 
    }); 

    if(min > per_server) { 
     _self.add_server(); 
    } 
}, 5000); 

var portfinder = require('portfinder'); 
portfinder.basePort = 3185; 

this.add_server = function() { 
    portfinder.getPortPromise() 
     .then((port) => { 
      console.log('port found', port); 

      var server = { 
       port: port, 
       load: 0, 
       process: cp.fork(__dirname + '/../server_instance.js', [], { 
        env: { 
         port: port 
        } 
       }) 
      }; 

      server.process.on('message', function(message) { 
       server.load = message.load; 
      }); 

      servers.push(server); 

     }) 
     .catch((err) => { 
      console.log('error happened'); 
     }); 
}; 

Po wielu testach wygląda na to, że mogę uruchomić 2 serwery, a następnie losowo, przy trzeciej lub czwartej próbie. Jest jasne, że problem jest głębszy niż w przypadku znalezienia portów, ta biblioteka mówi mi tylko to, co już wiem, wiem, które porty są otwierane, i dwukrotnie sprawdzam, czy przed uruchomieniem skryptu spróbuję uruchomić serwer za pomocą polecenia ręcznego netstat -anp | grep PORT.

Jasne, że problem nie polega na wyszukiwaniu otwartych portów, z punktu widzenia wyniku wygląda na to, że węzeł próbuje uruchomić serwer wiele razy z pojedynczego polecenia.

śledzić EDIT ----

dodanie server_instance.js Kod:

var io = require('socket.io')(process.env.port), 
    connections_current = 0, 
    connections_made = 0, 
    connections_dropped = 0; 

io.on('connection', function(socket) { 

    connections_current++; 
    connections_made++; 

    // ... service logic here, not relevant (like query db, send data to users etc) 

    socket.on('disconnect', function() { 
     connections_current--; 
     connections_dropped++; 
    }); 

}); 

setInterval(function() { 
    process.send({ 
     load: connections_current 
    }); 
}, 5000); 

08.11.2017 EDIT ----

byłem testując wiele rozwiązań rozwiązać problem i zaobserwowałem tę sytuację:

  • test lokalny na mac osx, w którym mogę wygenerować maksymalnie 3000 połączeń z serwerem. Błąd nigdy się nie zdarza, węzeł ma 1 process i 6 threads dla pliku routera. Dzięki 3000 połączeń mogę bez problemu wygenerować nawet 200 serwerów.

  • test serwera na debiunie linux, gdzie generuję 2 miliony połączeń z serwerem. Błąd zawsze dzieje się na 3. lub 4. instancji serwera, gdy łączę wszystkie węzły osób ma 6 processes i 10 threads for every process dla pliku routera.

To jest oczywiście źródło problemu, im więcej mam pojemności, tym więcej spawnuje się węzłów procesów i wcześniej będzie się nakładać podczas próby uruchomienia nowego serwera.

+0

Nie jest jasne, co próbujesz osiągnąć. Twój kod 'setInterval()' uruchamia się w nieskończoność, uruchamiając coraz więcej serwerów, aż do wyczerpania zasobów po stronie serwera. – jfriend00

+0

@ jfriend00 https://stackoverflow.com/questions/47071894/socket-io-dynamically-start-servers-to-listen może rzucić trochę światła –

+0

Co to oznacza 'Nie działa, ponieważ sygnał został wysłany z wielu wątków, a zatem kod isnt pomyślnie wykonany.? – jfriend00

Odpowiedz

0

Możesz użyć pakietu portfinder, aby odkryć dostępne porty sieciowe w swoim systemie (zaczyna wykrywanie z portu 8000). Użycie jest proste:

const http = require('http'); 
const portfinder = require('portfinder'); 
const pid = process.pid; 


portfinder.getPort((err, port) => { 
    if (err) 
     throw err; 

    http.createServer((req, res) => {   
     res.end(`Response from server ${pid}.\n`); 
    }).listen(port,() => { 
     console.log(`Server ${pid} running on port ${port}...`); 
    });  
}); 



** EDIT **

Wydaje się, że ten sam port jest zwrócony wielokrotnie z portfinder, stąd błąd EADDRINUSE jest wyrzucany.Podejrzewam, że port nie podsłuchuje, gdy próbuje znaleźć nowy (zwracając ten sam port), ale wydaje się, że zaprzecza temu fakt, że uruchomienie wielu serwerów za pomocą prostej pętli for działa dobrze:

for (let i = 0; i < max_number_of_servers; ++i) { 
    this.add_server(); 
} 


prosty fix do kodu mogłoby być zwiększenie adres bazowy portfinder na każdym wywołaniu add_server:

portfinder.basePort = 8000; 

this.add_server = function() { 
     portfinder.getPortPromise() 
     .then((port) => { 

      portfinder.basePort += 1; 

      var server = { 
       port: port, 
       load: 0, 
       process: cp.fork('server_instance.js', [], { 
        env: { 
         port: port 
        } 
       }) 
      }; 

      server.process.on('message', function(message) { 
       server.load = message.load; 
       console.log("message"); 
      }); 

      servers.push(server); 

     }) 
     .catch((err) => { 
      console.log(err); 
     }); 
}; 

Kod ten wydaje się działać prawidłowo, przynajmniej na moim komputerze.
W każdym razie proponuję rozważenie innej implementacji. Imho, jeśli zauważysz, że w scenariuszach o najwyższym natężeniu ruchu potrzebujesz N serwerów do prawidłowego obsługiwania wszystkich żądań, nie ma potrzeby tworzenia mniejszej liczby serwerów, a następnie dynamicznej zmiany ich w bazie bieżącego ruchu, z kilku powodów:

  • Nowy proces jest kosztowną operacją i może zająć trochę czasu, aby zostać uruchomionym.
  • W przypadku dużego natężenia ruchu cały serwer jest już gotowy do obsługi żądań bez dodatkowego opóźnienia.
  • W przypadku niskiego/średniego natężenia ruchu Twoje serwery będą mniej przeciążone, ale zyskasz pod względem odporności i dostępności (jeśli wystąpi awaria procesu serwera, z dowolnego powodu istnieje wiele innych serwerów, które prawdopodobnie będą obsługiwały żądanie podczas może rozpocząć nowy proces serwera, który zajmuje trochę czasu).


Można użyć natywną moduł cluster zbudować łatwo aplikację Process Server dystrybuowane z automatycznego równoważenia obciążenia i tolerancji błędów. Domyślnie moduł clusteer wykonuje algorytm "round-robin", aby dystrybuować przychodzące żądania wśród pracowników, dzięki czemu można uzyskać równoważenie obciążenia za darmo!
Możliwym prosta implementacja (tylko dla testu użyłem innego port finder package):

// main.js 

const cluster = require('cluster'); 
const getPort = require('get-port'); 
const max_servers = 40; 

// master process 
if (cluster.isMaster) { 
    for (let i = 0; i < max_servers; ++i) { 
     getPort().then(port => { 
      cluster.fork({port: port}); 
     })   
    } 
    // detect exit event on workers 
    cluster.on("exit", (worker, errCode) => { 
     console.log(worker); 
     // start new worker in case of crashes 
     if (errCode != 0 && !worker.suicide) { 
      console.log("Worker-server crashed. Starting new worker..."); 
      getPort().then(port => { 
       cluster.fork({port: port}); 
      }) 
     } 
    }); 
} 
// worker process --> start server 
else { 
    require('./server_instance.js'); // [2] 
} 
// server_instance.js 

const http = require("http"); 
const pid = process.pid; 
let port = process.env.port; 

console.log(`Starting server on process ${pid} running on port ${port}...`); 

let io = require('socket.io')(process.env.port), 
    connections_current = 0, 
    connections_made = 0, 
    connections_dropped = 0; 

io.on('connection', function(socket) { 
    console.log(`Socket.io on process ${pid} running on port ${port}...`); 
    connections_current++; 
    connections_made++; 

    // ... service logic here, not relevant (like query db, send data to users etc) 

    socket.on('disconnect', function() { 
     connections_current--; 
     connections_dropped++; 
    }); 

}); 
+0

Miałem duże nadzieje na to rozwiązanie, ale niestety pomija ono porty i nadal powoduje błąd "adres w użyciu". na przykład otworzył serwery na portach '3185',' 3186', a następnie otworzył '3190', a następnie rozbił się podczas próby otwarcia' 3200', za każdym razem, gdy test jest uruchamiany, zwracane są różne porty i ulega awarii, podczas wszystkich portów w trakcie test sprawdzany jest ręcznie za pomocą 'netstat -anp | grep [PORT] ' – Mevia

+0

@Mevia To dziwne. Czy mogę zapytać, ile serwerów odsłuchowych próbujesz uruchomić? I na ilu maszynach? – revy

+0

Całkowite potrzebne będzie około 40 serwerów, mam tylko jedną maszynę do tego, ma '2x Intel Xeon E5-2680v4' i' 512GB DDR4 ECC 2133' więc jego dobrze dostarczone. Ponownie mogę uruchomić wszystkie 40 serwerów jednocześnie i działa, mogę obsłużyć ~ 2 miliony użytkowników podczas weekendu, nie ma problemów. Po prostu chciałem, aby był bardziej elastyczny i uruchamiał/zamykał serwery, jeśli jest taka potrzeba, w przeciwnym razie zawsze mam 40 serwerów działających. – Mevia

0

Najlepszym rozwiązaniem byłoby, aby generować numery portów w procesie głównego, a następnie przekazać je do procesów roboczych tak że te nie przecinają się.

Można również sprawdzić, czy port jest używany i uzyskać wolny port za pomocą modułu npm, takiego jak test-port-provider.