我使用
Spring来实现以下几点:
在服务器上,我通过XML格式的REST接口接收数据.我想将数据转换成JSON并将其POST到另一台服务器.我的代码(我删除了一些敏感的类名/ URL,以避免雇主的愤怒)看起来像这样:
主/配置类:
package stateservice; import org.apache.http.HttpHost; import org.apache.http.client.config.RequestConfig; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.web.client.RestTemplate; @SpringBootApplication public class App { Logger log = LoggerFactory.getLogger(App.class); public static void main(String[] args) { System.out.println("Start!"); SpringApplication.run(StateServiceApplication.class,args); System.out.println("End!"); } @Bean public RestTemplate restTemplate() { log.trace("restTemplate()"); HttpHost proxy = new HttpHost("proxy_url",8080); PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(); // Increase max total connection to 200 cm.setMaxTotal(200); cm.setDefaultMaxPerRoute(50); RequestConfig requestConfig = RequestConfig.custom().setProxy(proxy).build(); HttpClientBuilder httpClientBuilder = HttpClientBuilder.create(); httpClientBuilder.setDefaultRequestConfig(requestConfig); httpClientBuilder.setConnectionManager(cm); HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory( httpClientBuilder.build()); return new RestTemplate(requestFactory); } }
代表RESTful接口的类:
package stateservice; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import foo.bar.XmlData @RestController public class StateController { private static Logger log = LoggerFactory.getLogger(DataController.class); @Autowired ForwarderService forwarder; @RequestMapping(value = "/data",method = RequestMethod.POST) public String postState(@RequestBody XmlData data) { forwarder.forward(data); return "Done!"; } }
最后,货代:
package stateservice; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; import foo.bar.Converter; import foo.bar.XmlData; @Service public class ForwarderService { private static Logger log = LoggerFactory.getLogger(ForwarderService.class); String uri = "forward_uri"; @Autowired RestTemplate restTemplate; @Async public String forward(XmlData data) { log.trace("forward(...) - start"); String json = Converter.convert(data); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); ResponseEntity<String> response = restTemplate.postForEntity(uri,new HttpEntity<String>(json,headers),String.class); // responseEntity.getBody(); // log.trace(responseEntity.toString()); log.trace("forward(...) - end"); return response.getBody(); } }
然而,连接管理器很少似乎释放重新使用的连接,此外,系统会在CLOSE_WAIT状态(可以使用netstat看到)中连接.池中的所有连接都被租用,但未被释放,并且一旦CLOSE_WAIT状态的连接数达到ulimit,我会收到’太多的open file’异常
解决方法
Apache HttpEntity有一个技巧 – 释放锁定的连接 – 响应必须被完全消耗和关闭.有关详细信息,请参阅
EntityUtils和
HttpEntity文档:
EntityUtils.consume(response);
从4.3版本开始,当在CloseableHttpResponse上调用#close()方法时,Apache HttpClient会将连接释放回池中.
但是,Spring 4.0版本仅支持Spring Web功能,请参阅HttpComponentsClientHttpResponse.java中的方法#close():
@Override public void close() { // Release underlying connection back to the connection manager try { try { // Attempt to keep connection alive by consuming its remaining content EntityUtils.consume(this.httpResponse.getEntity()); } finally { // Paranoia this.httpResponse.close(); } } catch (IOException ignore) { } }
成功的关键在于“// Paranoia” – 显式.close()调用.它实际上释放连接回到池.