2010-05-26 10 views
5

Tengo 1000 hilos de Java dedicados donde cada hilo sondea una url correspondiente cada un segundo.BindException/Demasiados archivos abiertos durante el uso de HttpClient bajo carga

public class Poller { 
    public static Node poll(Node node) { 
     GetMethod method = null; 
     try { 
      HttpClient client = new HttpClient(new SimpleHttpConnectionManager(true)); 
      ...... 
     } catch (IOException ex) { 
      ex.printStackTrace(); 
     } finally { 
      method.releaseConnection(); 
     } 
    } 
} 

Los hilos se ejecutan cada segundo:

for (int i=0; i <1000; i++) { 
    MyThread thread = threads.get(i) // threads is a static field 
    if(thread.isAlive()) { 
     // If the previous thread is still running, let it run. 
    } else { 
     thread.start(); 
    } 
} 

El problema es que si corro el trabajo cada segundo consigo excepciones al azar como éstas:

java.net.BindException: Address already in use 
INFO httpclient.HttpMethodDirector: I/O exception (java.net.BindException) caught when processing request: Address already in use 
INFO httpclient.HttpMethodDirector: Retrying request 

pero si ejecuta el trabajo cada 2 segundos o más, todo funciona bien.

Incluso traté de cerrar la instancia de SimpleHttpConnectionManager() usando shutDown() sin ningún efecto.

Si hago netstat, veo miles de conexiones TCP en estado TIME_WAIT, lo que significa que se han cerrado y están despejando.

Así que para limitar el nº de conexiones, He intentado utilizar una única instancia de HttpClient y utilizar de esta manera:

public class MyHttpClientFactory { 
     private static MyHttpClientFactory instance = new HttpClientFactory(); 
     private MultiThreadedHttpConnectionManager connectionManager; 
     private HttpClient client; 

     private HttpClientFactory() { 
       init(); 
     } 

     public static HttpClientFactory getInstance() { 
       return instance; 
     } 

     public void init() { 
       connectionManager = new MultiThreadedHttpConnectionManager(); 
       HttpConnectionManagerParams managerParams = new HttpConnectionManagerParams(); 
       managerParams.setMaxTotalConnections(1000); 
       connectionManager.setParams(managerParams); 
       client = new HttpClient(connectionManager); 
     } 

     public HttpClient getHttpClient() { 
       if (client != null) { 
         return client; 
       } else { 
        init(); 
        return client; 
       } 
     } 
} 

Sin embargo después de correr durante exactamente 2 horas, se comienza a lanzar 'demasiados archivos abiertos' y eventualmente no puede hacer nada en absoluto.

ERROR java.net.SocketException: Too many open files 
INFO httpclient.HttpMethodDirector: I/O exception (java.net.SocketException) caught when processing request: Too many open files 
INFO httpclient.HttpMethodDirector: Retrying request 

yo debería ser capaz de aumentar el nº de conexiones permitidas y hacer que funcione, pero yo sólo estaría prolongando el mal. ¿Alguna idea de cuál es la mejor práctica para usar HttpClient en una situación como la anterior?

Por cierto, todavía estoy en HttpClient3.1.

Respuesta

2

No hay nada de malo en el primer error. Acaba de agotar los puertos empíricos disponibles. Cada conexión TCP puede permanecer en estado TIME_WAIT durante 2 minutos. Generas 2000/segundos. Pronto o después, el socket no puede encontrar ningún puerto local sin usar y obtendrá ese error. TIME_WAIT diseñado exactamente para este propósito. Sin él, su sistema podría secuestrar una conexión previa.

El segundo error significa que tiene demasiados enchufes abiertos. En algún sistema, hay un límite de 1K archivos abiertos. Tal vez solo llegue a ese límite debido a sockets persistentes y otros archivos abiertos. En Linux, puede cambiar este límite usando

ulimit -n 2048 

Pero eso está limitado por un valor máximo para todo el sistema.

3

Esto nos sucedió hace unos meses. En primer lugar, comprueba dos veces para asegurarte de que realmente llamas a releaseConnection() todo el tiempo. Pero incluso entonces, el sistema operativo realmente no reclama las conexiones TCP a la vez. La solución es utilizar Apache HTTP Client's MultiThreadedHttpConnectionManager. Esto agrupa y reutiliza las conexiones.

Ver http://hc.apache.org/httpclient-3.x/performance.html para obtener más consejos de rendimiento.

Actualización: ¡Vaya! No leí la muestra del código inferior. Si está ejecutando releaseConnection() y está utilizando MultiThreadedHttpConnectionManager, considere si su límite de sistema operativo en archivos abiertos por proceso es lo suficientemente alto. También tuvimos ese problema y necesitábamos extender el límite un poco.

+0

@Langali: ¡Oh, eso me enseñará a leer una publicación por completo! Otra cosa a considerar es si su límite de sistema operativo en el número de archivos abiertos por proceso es tal vez demasiado bajo. Extendimos la nuestra cuando descubrimos que Glassfish estaba usando casi toda su asignación para hacer cargas de clase, etc. Eso resolvió el problema para nosotros. –

0

Como sudo o root edite el archivo /etc/security/limits.conf.Al final del archivo justo arriba de "# Fin del archivo" ingrese los siguientes valores: * nofile suave 65535 * nofilo duro 65535 Esto establecerá la cantidad de archivos abiertos en ilimitados.

Cuestiones relacionadas