web-dev-qa-db-fra.com

Utilisation de Spring REST, créant trop de connexions ou ralentissant

J'ai un service RESTful qui fonctionne très rapidement. Je le teste sur localhost. Le client utilise Spring REST template. J'ai commencé par utiliser une approche naïve:

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

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

Lorsque je fais beaucoup de ces demandes, je reçois l'exception suivante:

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

Cela est dû au fait que les connexions ne sont pas fermées et se bloquent dans l'état TIME_WAIT. L'exception commence à se produire lorsque les ports éphémères sont épuisés. Ensuite, l'exécution attend que les ports soient à nouveau libres. Je vois des performances optimales avec de longues pauses. Le taux que j'obtiens est presque ce dont j'ai besoin, mais bien sûr, ces connexions TIME_WAIT ne sont pas bonnes. Testé à la fois sur Linux (Ubuntu 14) et Windows (7), des résultats similaires à des moments différents en raison de différentes plages de ports.

Pour résoudre ce problème, j'ai essayé d'utiliser un HttpClient avec HttpClientBuilder de la bibliothèque de composants Apache Http.

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);

Avec ce client, je ne vois aucune exception. Le client n'utilise désormais qu'un nombre très limité de ports éphémères. Mais quels que soient les paramètres que j'utilise (TOTAL et PER_ROUTE), je ne peux pas obtenir les performances dont j'ai besoin.

En utilisant la commande netstat, je constate qu'il n'y a pas beaucoup de connexions établies avec le serveur. J'ai essayé de régler les nombres à plusieurs milliers, mais il semble que le client n'en utilise jamais autant.

Puis-je faire quelque chose pour améliorer les performances, sans ouvrir trop de connexions?


MISE À JOUR: J'ai essayé de définir le nombre de connexions totales et par route à 5000 et 2500, mais il semble toujours que le client n'en crée pas plus d'une centaine (à en juger par netstat -n | wc -l). Le service REST est implémenté à l'aide de JAX-RS et exécuté sur Jetty.

UPDATE2: J'ai maintenant réglé le serveur avec certains paramètres de mémoire et j'obtiens un très bon débit. L'approche naïve est encore un peu plus rapide, mais je pense que c'est juste un petit surcoût de la mutualisation côté client.

29
sm4

En fait, Spring Boot ne fuit pas les connexions. Ce que vous voyez ici, c'est le comportement standard du noyau Linux (et de tous les principaux systèmes d'exploitation). Toutes les prises fermées de la machine passent à un TIME_WAIT état pour une certaine durée. C'est pour empêcher le prochain socket qui utilise ce port éphémère de recevoir des paquets qui étaient réellement destinés au socket précédent sur ce port. La différence que vous voyez entre les deux est le résultat des approches de mise en commun des connexions que chacune adopte.

Plus précisément, RestTemplatene pas utilise le pool de connexions par défaut. Cela signifie que chaque appel de repos ouvre un nouveau port éphémère local et une nouvelle connexion au serveur. Si votre service est très rapide, il soufflera dans sa gamme de ports locaux disponibles en un rien de temps. Avec Apache HttpClient, vous profitez du pool de connexions. Cela empêchera votre application de voir le problème que vous avez décrit. Cependant, étant donné que votre service est en mesure de répondre plus rapidement que le noyau Linux prend des sockets sur TIME_WAIT, la mise en commun des connexions rendra votre client plus lent, peu importe ce que vous faites (s'il ne ralentit rien - alors vous manquerez de nouveau de ports éphémères locaux).

Bien qu'il soit possible d'activer TCP réutilisation dans le noyau Linux, cela peut devenir dangereux (les paquets peuvent être retardés et vous pouvez obtenir des ports éphémères recevant des paquets aléatoires qu'ils ne comprennent pas, ce qui pourrait provoquer toutes sortes de La solution ici consiste à utiliser le regroupement de connexions comme vous l'avez fait dans le deuxième exemple, avec des nombres suffisamment élevés pour atteindre des performances proches de celles que vous recherchez.

Pour vous aider à régler votre pool de connexions, vous souhaiterez modifier les paramètres maxConnPerRoute et maxConnTotal. maxConnPerRoute limite le nombre de connexions qui seront établies à une seule paire IP: Port, et maxTotal limite le nombre de connexions totales qui seront jamais ouvertes. Dans votre cas, comme il semble que toutes les demandes soient faites au même emplacement, vous pouvez les définir sur la même valeur (élevée).

37
Colin M