RestTemplate介绍
RestTemplate是Spring框架提供的一个用于发送HTTP请求的同步客户端工具,它简化了与Restful服务进行交互的过程,提供了一系列便捷的方法来处理常见的HTTP请求,如GET、POST、PUT、DELETE等,RestTemplate会自动处理HTTP请求和响应的序列号和反序列化,让开发者可以更专注于业务逻辑的实现。
代码示例
/**
* 转发
* @param request
* @param url
* @return
*/
public ResponseEntity distribute(HttpServletRequest request, String url) {
try {
RequestEntity requestEntity = this.createRequestEntity(request, url);
return this.restTemplate.exchange(requestEntity, Object.class);
} catch (Exception var7) {
log.error("distribute_error",e);
throw new RuntimeException("distribute_error");
}
}
/**
* 组装请求实体
* @param request
* @param url
* @return
* @throws URISyntaxException
* @throws IOException
*/
private RequestEntity createRequestEntity(HttpServletRequest request, String url) throws URISyntaxException, IOException {
String method = request.getMethod();
HttpMethod httpMethod = HttpMethod.resolve(method);
MultiValueMap<String, String> headers = this.parseRequestHeader(request, url);
byte[] body = this.parseRequestBody(request);
return new RequestEntity(body, headers, httpMethod, new URI(url));
}
/**
* 解析请求头
* @param request
* @param url
* @return
* @throws URISyntaxException
*/
private MultiValueMap<String, String> parseRequestHeader(HttpServletRequest request, String url) throws URISyntaxException {
HttpHeaders headers = new HttpHeaders();
List<String> headerNames = Collections.list(request.getHeaderNames());
Iterator var = headerNames.iterator();
while(var.hasNext()) {
String headerName = (String)var.next();
List<String> headerValues = Collections.list(request.getHeaders(headerName));
Iterator var1 = headerValues.iterator();
while(var1.hasNext()) {
String headerValue = (String)var1.next();
headers.add(headerName, headerValue);
}
}
headers.setHost(this.getInetSocketAddress(url));
return headers;
}
/**
* 解析请求体
* @param request
* @return
* @throws IOException
*/
private byte[] parseRequestBody(HttpServletRequest request) throws IOException {
InputStream inputStream = request.getInputStream();
return StreamUtils.copyToByteArray(inputStream);
}
private InetSocketAddress getInetSocketAddress(String url) throws URISyntaxException {
URI newUri = new URI(url);
int port = newUri.getPort();
//默认端口配置
if (newUri.getPort() == -1) {
if ("http".equals(newUri.getScheme())) {
port = 80;
} else if ("https".equals(newUri.getScheme())) {
port = 443;
}
}
return new InetSocketAddress(newUri.getHost(), port);
}
问题描述
前期流量较小,转发请求都正常,后面请求接口流量增加,1小时20w左右并发,开始有大量连接池失败的异常日志【timeout waiting for connection from pool】,上游有大量500报错。请求转发成功20万,失败了5万+
问题处理
spring的RestTemplate底层是Apache HttpClient,HttpClient是支持池化技术的。HttpClient使用的版本是4.5.14。默认的连接池配置如下:
maxTotal:连接池中所有连接的最大数量。这个值决定了在任何时刻,连接池可以持有的最大连接数。如果达到这个限制,客户端会等待可用连接,默认值是20
defaultMaxPerRoute:每个路由(即每个目标服务器地址)的最大连接数。路由是指一个特定的主机和端口组合。这意味着对于同一个目标服务器,你可以同时保持的最大连接数。默认值是2
ConnectionRequestTimeout连接超时时间为-1
经过排查发现,HttpClient使用的版本为4.5.X,默认的路由连接数为2,无法满足高并发场景。分别修改连接池参数配置maxTotal=2000,defaultMaxPerRoute=100。具体的设置依据如下:
客户端服务实例有30个,每个线程单独处大概500ms以内,设置了defaultMaxPerRoute=100,能支持大概6000TPS的并发,满足当前业务需求。