2015-08-07 10 views
25

Ho un servizio RESTful che funziona molto velocemente. Lo sto testando su localhost. Il client utilizza il modello Spring REST. Ho iniziato con un approccio ingenuo:Uso del modello Spring REST, creando troppe connessioni o lento

RestTemplate restTemplate = new RestTemplate(Collections.singletonList(new GsonHttpMessageConverter())); 

Result result = restTemplate.postForObject(url, payload, Result.class); 

Quando faccio un sacco di queste richieste, sto ottenendo la seguente eccezione:

Caused by: org.springframework.web.client.ResourceAccessException: I/O error on POST request for "http://localhost:8080/myservice":No buffer space available (maximum connections reached?): connect; nested exception is java.net.SocketException: No buffer space available (maximum connections reached?): connect 

Questo è causato da connessioni non essere chiuso e appeso in TIME_WAIT stato. L'eccezione inizia quando le porte effimere sono esaurite. Quindi l'esecuzione attende che le porte siano di nuovo libere. Sto vedendo le massime prestazioni con pause lunghe. Il tasso che sto ottenendo è quasi quello di cui ho bisogno, ma ovviamente queste connessioni TIME_WAIT non sono buone. Testato sia su Linux (Ubuntu 14) che Windows (7), risultati simili in tempi diversi a causa di diversi intervalli delle porte.

Per risolvere il problema, ho provato a utilizzare HttpClient con HttpClientBuilder dalla libreria dei componenti Http di Apache.

RestTemplate restTemplate = new RestTemplate(Collections.singletonList(new GsonHttpMessageConverter())); 
HttpClient httpClient = HttpClientBuilder.create() 
     .setMaxConnTotal(TOTAL) 
     .setMaxConnPerRoute(PER_ROUTE) 
     .build(); 
restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory(httpClient)); 

Result result = restTemplate.postForObject(url, payload, Result.class); 

Con questo client, non vedo eccezioni. Il client utilizza ora solo un numero molto limitato di porte effimere. Ma a prescindere dalle impostazioni che uso (TOTAL e PER_ROUTE), non riesco a ottenere le prestazioni di cui ho bisogno.

Utilizzando il comando netstat, vedo che non ci sono molte connessioni fatte al server. Ho provato a impostare i numeri a diverse migliaia, ma sembra che il client non usi mai così tanto.

C'è qualcosa che posso fare per migliorare le prestazioni, senza aprire troppe connessioni?


UPDATE: Ho provato a installare numero di connessioni totali e per tratta a 5000 e il 2500 ma sembra ancora come il client non è la creazione di più di un centinaio (a giudicare dalle netstat -n | wc -l). Il servizio REST è implementato usando JAX-RS ed è in esecuzione su Jetty.

UPDATE2: ora ho regolato il server con alcune impostazioni di memoria e sto ottenendo un throughput veramente buono. L'approccio ingenuo è ancora un po 'più veloce, ma penso che sia solo un piccolo overhead del pooling sul lato client.

+0

L'errore "Nessun spazio disponibile sul buffer" è più correlato al sistema, meno con il codice. Un sacco di connessioni TIME_WAIT indicano che le connessioni non sono raggruppate. Non modificare le impostazioni TIME_WAIT, causerà più problemi di quanti ne risolva (http://vincent.bernat.im/en/blog/2014-tcp-time-wait-state-linux.html). Le solite cause per nessuno spazio disponibile sul buffer sono le schede di rete rotte o un piccolo wmem_max su Linux. Dovresti essere in grado di creare almeno da 50 a 60k connessioni a un host remoto sulla stessa porta prima di esaurire i socket (quadrupli IP). – mp911de

risposta

31

In realtà Spring Boot non perde connessioni. Quello che vedete qui è il comportamento standard del kernel di Linux (e di tutti i principali sistemi operativi). Tutte le prese chiuse dalla macchina passano allo stato TIME_WAIT per un certo periodo di tempo. Questo serve a impedire che il prossimo socket che utilizza quella porta temporanea possa ricevere pacchetti che erano in realtà destinati al socket precedente su quella porta. La differenza che si vede tra i due è il risultato del pool di connessioni che si avvicina a ciascuno di essi.

In particolare, RestTemplatenon utilizza il pool di connessioni per impostazione predefinita. Ciò significa che ogni chiamata di riposo apre una nuova porta effimera locale e una nuova connessione al server. Se il tuo servizio è molto veloce, farà esplodere la sua gamma di porte locali disponibili in pochissimo tempo. Con Apache HttpClient, si sta sfruttando il pool di connessioni. Ciò impedirà alla tua applicazione di vedere il problema che hai descritto. Tuttavia, dato che il tuo servizio è in grado di rispondere più velocemente di quanto il kernel di Linux stia prendendo socket da TIME_WAIT, il pooling di connessioni renderà il tuo client più lento indipendentemente da ciò che fai (se non rallentasse nulla - allora avresti finito di porte effimere locali di nuovo).

Mentre è possibile abilitare il riutilizzo del TCP nel kernel Linux, può diventare pericoloso (i pacchetti possono essere ritardati e si possono ottenere porte effimere che ricevono pacchetti casuali che non capiscono e che potrebbero causare tutti i tipi di problemi). La soluzione qui è quella di utilizzare il pool di connessioni come nel secondo esempio, con numeri sufficientemente elevati da raggiungere vicino alla prestazione che stai cercando.

Per ottimizzare il pool di connessioni, è necessario modificare i parametri maxConnPerRoute e maxConnTotal. maxConnPerRoute limita il numero di connessioni che verranno effettuate a un singolo IP: coppia di porte e maxTotal limita il numero di connessioni totali che verranno mai aperte. Nel tuo caso, dal momento che sembra che tutte le richieste siano fatte nella stessa posizione, puoi impostarle sullo stesso valore (alto).

+0

Proverò a riformulare la domanda (senza "perdite"). C'è un modo in cui posso impostare il client Apache per utilizzare un pool più grande? Controllando netstat, penso che stia usando forse cento porte, non importa quale sia l'impostazione che uso. Vedrò se posso fare qualcosa per il server. – sm4

+0

Quali valori stai attualmente utilizzando in 'TOTAL' e' PER_ROUTE'? Queste sono le impostazioni per manipolare le dimensioni del pool di connessioni, dove 'PER_ROUTE 'limita il numero di connessioni a un singolo IP: coppia di porte e limiti' TOTAL', beh, il numero totale di connessioni –

+3

Sono ora in un punto in cui tutto funziona abbastanza veloce. Potresti aggiornare la tua risposta con la spiegazione di questi due parametri + menzione di eventuali spese generali? In questo modo sarebbe più utile alle altre persone. – sm4

Problemi correlati