2016-02-14 30 views
22

Przeszukuję wiele linków z modułem request równolegle z połączeniem modułu async.
Zauważam wiele błędów ETIMEDOUT i ESOCKETTIMEDOUT, mimo że linki są osiągalne i szybko reagują za pomocą chrome.Moduł żądania Node.js pobiera ETIMEDOUT i ESOCKETTIMEDOUT

Ograniczam numery maxSockets do 2 i timeout do 10000 w opcjach żądań. Używam async.filterLimit() z limitem 2, aby nawet zmniejszyć równoległość do 2 żądania za każdym razem. Mam więc 2 gniazda, 2 żądania i limit czasu 10 sekund na oczekiwanie na odpowiedź nagłówków z serwera, ale dostaję te błędy.

tutaj; s konfiguracja prośba używam:

{ 
      ... 
      pool: { 
       maxSockets: 2 
      }, 
      timeout: 10000 
      , 
      time: true 
      ... 
     } 

Oto fragment kodu używam do fecth linki:

var self = this; 
    async.filterLimit(resources, 2, function(resource, callback) { 
     request({ 
      uri: resource.uri 
     }, function (error, response, body) { 
      if (!error && response.statusCode === 200) { 
       ... 
      } else { 
       self.emit('error', resource, error); 
      } 
      callback(...); 
     }) 
    }, function(result) { 
     callback(null, result); 
    }); 

Słuchałem zdarzenia błędu i widzę, gdy kod błędu jest ETIMEDOUT obiekt connect jest albo prawda/fałsz, więc czasami jest to limit czasu połączenia, a czasami nie jest (zgodnie z dokumentami żądania)

UPDATE: postanowiłem zwiększyć w górę maxSockets do Infinity więc brak połączenia będzie rozłączyć z powodu braku dostępnych gniazd:

pool: { 
    maxSockets: Infinity 
} 

In-aby kontrolować przepustowość I wdrożone metody requestLoop które obsługują żądania z maxAttemps i retryDelay parametrów do sterowania żądania:

async.filterLimit(resources, 10, function(resource, callback) { 
     self.requestLoop({ 
      uri: resource.uri 
     }, 100, 5000, function (error, response, body) { 
       var fetched = false; 
       if (!error) { 
        ... 
       } else { 
        .... 
       } 
       callback(...); 
      }); 
    }, function(result) { 
     callback(null, result); 
    }); 

Implementation of requestLoop:

requestLoop = function(options, attemptsLeft, retryDelay, callback, lastError) { 
    var self = this; 
    if (attemptsLeft <= 0) { 
     callback((lastError != null ? lastError : new Error('...'))); 
    } else { 
     request(options, function (error, response, body) { 
      var recoverableErrors = ['ESOCKETTIMEDOUT', 'ETIMEDOUT', 'ECONNRESET', 'ECONNREFUSED']; 
      var e; 
      if ((error && _.contains(recoverableErrors, error.code)) || (response && (500 <= response.statusCode && response.statusCode < 600))) { 
       e = error ? new Error('...'); 
       e.code = error ? error.code : response.statusCode; 
       setTimeout((function() { 
        self.requestLoop(options, --attemptsLeft, retryDelay, callback, e); 
       }), retryDelay); 
      } else if (!error && (200 <= response.statusCode && response.statusCode < 300)) { 
       callback(null, response, body); 
      } else if (error) { 
       e = new Error('...'); 
       e.code = error.code; 
       callback(e); 
      } else { 
       e = new Error('...'); 
       e.code = response.statusCode; 
       callback(e); 
      } 
     }); 
    } 
}; 

Więc Podsumowując: - Wzmocnione maxSockets do Infinity spróbować przezwyciężyć błąd przekroczenia limitu czasu połączenia gniazda - Implemnted requestLoop metody kontrolowania powiodło się żądanie i maxAttemps jak również retryDelay takich wniosków - Jest tam również numer maxium współbieżnego żądania ustawionego przez liczbę przekazaną do async.filterLimit

Chcę zauważyć, że grałem również z ustawieniami wszystkiego tutaj, aby uzyskać darmowe indeksowanie, ale jak dotąd próby nie powiodły się.

Ciągle szukam pomocy w rozwiązaniu tego problemu.

UPDATE2: Zdecydowałem się upuścić async.filterLimit i utworzyć własny mechanizm limitów. mam tylko 3 zmienne, które pomogą mi osiągnąć to:
pendingRequests - tablica żądanie który będzie posiadał wszystkie żądania (wyjaśni później) activeRequests - liczba aktywnych żądań maxConcurrentRequests - liczba maksymalne dozwolone jednoczesnych żądań

do tablica pendingRequests, wciskam kompleksowy obiekt zawierający odniesienie do funkcji requestLoop jak również argumenty tablicę zawierającą argumenty mają być przekazywane do funkcji pętli:

self.pendingRequests.push({ 
       "arguments": [{ 
        uri: resource.uri.toString() 
       }, self.maxAttempts, function (error, response, body) { 
        if (!error) { 
         if (self.policyChecker.isMimeTypeAllowed((response.headers['content-type'] || '').split(';')[0]) && 
          self.policyChecker.isFileSizeAllowed(body)) { 
          self.totalBytesFetched += body.length; 
          resource.content = self.decodeBuffer(body, response.headers["content-type"] || '', resource); 
          callback(null, resource); 
         } else { 
          self.fetchedUris.splice(self.fetchedUris.indexOf(resource.uri.toString()), 1); 
          callback(new Error('Fetch failed because a mime-type is not allowed or file size is bigger than permited')); 
         } 
        } else { 
         self.fetchedUris.splice(self.fetchedUris.indexOf(resource.uri.toString()), 1); 
         callback(error); 
        } 
        self.activeRequests--; 
        self.runRequest(); 
       }], 
       "function": self.requestLoop 
      }); 
      self.runRequest(); 

Ty „” zauważyć połączenie do runRequest() na końcu. Funkcja ta praca jest zarządzanie żądania i prośby pożaru, jeżeli to możliwe przy zachowaniu maksymalnego activeRequests poniżej granicy maxConcurrentRequests:

var self = this; 
    process.nextTick(function() { 
     var next; 
     if (!self.pendingRequests.length || self.activeRequests >= self.maxConcurrentRequests) { 
      return; 
     } 
     self.activeRequests++; 
     next = self.pendingRequests.shift(); 
     next["function"].apply(self, next["arguments"]); 
     self.runRequest(); 
    }); 

To powinno rozwiązać wszelkie błędy limity czasu, przez moje Próby Tho, mam jeszcze zauważyłem niektóre limity czasu na konkretnych stronach, na których to testowałem. Nie mogę być w 100% pewny, ale myślę, że wynika to z charakteru strony internetowej, która wspiera serwer http ograniczając żądania użytkowników do maksimum, wykonując sprawdzanie ip i zwracając niektóre wiadomości HTTP 400 aby zapobiec ewentualnemu "atakowi" na serwerze.

+0

Czy zdarzyło Ci się kiedyś dowiedzieć czegoś takiego @Jorayen? – DvideBy0

+0

Zgadnij, nie, masz podobny problem czasami – Denny

+0

@ DvideBy0 Zaktualizowano rozwiązanie – Jorayen

Odpowiedz

19

Edit: duplikat https://stackoverflow.com/a/37946324/744276

Domyślnie węzeł ma 4 workers to resolve DNS queries. Jeśli twoja kwerenda DNS zajmuje długi czas, żądania będą blokowane w fazie DNS, a objawem jest dokładnie ESOCKETTIMEDOUT lub ETIMEDOUT.

Spróbuj zwiększyć swój uv rozmiar puli wątków:

export UV_THREADPOOL_SIZE=128 
node ... 

lub w index.js (lub gdziekolwiek zmierzasz wpisu):

#!/usr/bin/env node 
process.env.UV_THREADPOOL_SIZE = 128; 

function main() { 
    ... 
} 

I reproduced this locally poprzez spowolnienie odpowiedzi z serwera DNS za pomocą tc.

+0

dzięki za pomocny dodatek do tego tematu, jeszcze nie przetestowałem tego, co zasugerowałeś, ale to jest dobre niemniej jednak wiedzą.Będę również raportować wyniki, gdy tylko będę miał na to czas wolny :) – Jorayen

+0

To, co przeczytałem o UV_THREADPOOL_SIZE sugeruje, że jest to najważniejsze dla blokowania io (takie jak dostęp do dysku), ale nie ma znaczenia dla blokowanie io (takie jak dostęp do sieci). – Eric

0

Znalazłem, jeśli jest zbyt wiele żądań asynchronicznych, a następnie wyjątek ESOCKETTIMEDOUT dzieje się w systemie Linux. Obejście Znalazłem to robi:

Ustawienie tej opcji do żądania(): agent: false, pool: {maxSockets: 100} Zauważ, że po tym, limit czasu może leżeć więc może trzeba ją zwiększyć.