Java 微服务网络连接优化
在如今的大型互联网络中, 微服务基本是最流行的架构. 采用微服务架构, 自然少不了内部各个服务之间的相互调用, 那么其中的网络连接处理, 自然成了要重点考虑的对象. 下面就一步步仔细看看其中每一步要考虑的细节.
基本的网络连接
下面的代码使用了 JDK 最基础的代码, 告诉我们如何使用 URL
和 HttpURLConnection
来进行网络连接.
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
public class HttpClientExample {
public static void main(String[] args) {
String urlString = "https://api.example.com/v1/search";
HttpURLConnection connection = null;
try {
// 创建 URL 对象
URL url = new URL(urlString);
// 打开连接
connection = (HttpURLConnection) url.openConnection();
// 设置请求方法
connection.setRequestMethod("GET");
// 设置请求属性(如有需要)
connection.setRequestProperty("Content-Type", "application/json");
// 设置连接超时和读取超时
connection.setConnectTimeout(5000);
connection.setReadTimeout(5000);
// 发起请求
int responseCode = connection.getResponseCode();
// 检查响应码
if (responseCode == HttpURLConnection.HTTP_OK) {
// 正常响应,读取数据
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
// 打印响应
System.out.println("Response: " + response.toString());
} else {
// 错误处理
System.err.println("Error: Received HTTP code " + responseCode);
BufferedReader errorReader = new BufferedReader(new InputStreamReader(connection.getErrorStream()));
StringBuilder errorResponse = new StringBuilder();
String errorLine;
while ((errorLine = errorReader.readLine()) != null) {
errorResponse.append(errorLine);
}
errorReader.close();
// 打印错误响应
System.err.println("Error Response: " + errorResponse.toString());
}
} catch (Exception e) {
// 异常处理
System.err.println("Exception occurred: " + e.getMessage());
e.printStackTrace();
} finally {
if (connection != null) {
// 断开连接
connection.disconnect();
}
}
}
}
设置 timeout
上面的代码中, 我们可以看到, 连接 timeout 和 read timeout 都是通过 setConnectTimeout
和 setReadTimeout
来设置的. 下面我们就来看看这两个 timeout 的区别.
连接 timeout
连接 timeout 是指在建立连接时, 客户端等待服务器响应的时间. 如果在这个时间内没有建立连接, 就会抛出 SocketTimeoutException
. 这个 timeout 主要是为了防止网络延迟过长导致的连接阻塞. 具体来说, 就是 TCP 三次握手的时间. 如果在这个时间内没有完成三次握手, 就会抛出异常. 这个数值通常根据网络环境来设置, 比如在局域网中, 可以根据测试来设置为500或1000毫秒, 而在广域网中, 可以设置为为更长时间.
read timeout
read timeout 是指在连接建立后, 客户端等待服务器响应数据的时间. 如果在这个时间内没有收到数据, 就会抛出 SocketTimeoutException
. 这个 timeout 主要是为了防止服务器长时间不响应导致的连接阻塞. 对于客户端这方具体来说, 当它发出去请求, 它就开始等带从 Socket 读取数据, 每次开始读, 就开始计时, 当到达 read timeout 的时间还没有任何数据收到, 就会因读取超时而抛出异常. 有时候从服务端返回的数据很多, 要分好几次读取, 每当当前一次 Socket.read() 读完, 就开始下一次read(), 直到所有的数据都读完. 每次重新读取(开始read()), 都会重新计时. 这个 timeout 通常设置为几秒到几十秒不等, 具体数值可以根据实际情况来设置.
对于 read timeout 的误解
很多人会误解 read timeout 是指在连接建立后, 客户端等待服务器响应数据的时间. 其实不是这样的, 它是指在连接建立后, 客户端等待服务器响应数据的时间. 如果在这个时间内没有收到数据, 就会抛出 SocketTimeoutException
. 意思是如果从服务端读取的数据分多次, 每次都会重新计时, 每次都可能timeout . 整体的读数据时间, 可能是设置的 timeout 的好几倍的时间. 比如我们下载一个几G的镜像文件, 设置timeout 是5秒, 但是整体下载镜像的时间可能是半小时, 只要每次读取数据的时间不超过5秒, 就不会抛出异常. 最后顺利下载完成.
连接池
连接池是为了提高性能, 减少连接的创建和销毁的开销. 连接池会预先创建一定数量的连接, 当需要使用连接时, 从连接池中获取一个可用的连接, 使用完毕后将其放回连接池. 这样可以避免频繁地创建和销毁连接, 提高性能.
在 Java 11 中,JDK 引入了新的 HttpClient API,它提供了更现代化和更灵活的 HTTP 客户端功能。HttpClient 默认支持连接池,这意味着它可以重用连接以提高性能和效率。
HttpClient 默认连接池的特点:
- 连接复用:HttpClient 会自动管理和复用连接,这样可以减少每次请求都重新建立连接的开销。连接复用对于 HTTP/1.1 和 HTTP/2 都有效。
- 线程安全:HttpClient 是线程安全的,可以在多个线程之间共享一个 HttpClient 实例。这意味着多个请求可以并发地使用同一个 HttpClient 实例进行发送。
- 自动管理:连接池的管理是自动的,用户无需手动配置连接池的大小或其他参数。HttpClient 会根据需要动态调整连接池的大小。
- 异步支持:HttpClient 支持异步请求,这可以更高效地利用连接池,因为异步请求不会阻塞线程。
- HTTP/2 支持:HttpClient 默认支持 HTTP/2 协议,这使得连接复用更加高效,因为 HTTP/2 允许在单个连接上并发多个请求和响应。
下面是一个使用 HttpClient 的示例代码:
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
public class HttpClientExample {
public static void main(String[] args) {
try {
// 创建 HttpClient 实例
HttpClient client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2)
.connectTimeout(Duration.ofSeconds(10))
.build();
// 创建 HttpRequest 实例
HttpRequest request = HttpRequest.newBuilder()
.uri(new URI("https://api.example.com/v1/search"))
.GET()
.build();
// 发送请求并接收响应
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
// 打印响应状态码和响应体
System.out.println("Response Code: " + response.statusCode());
System.out.println("Response Body: " + response.body());
} catch (Exception e) {
e.printStackTrace();
}
}
}
连接池连接 timeout
连接池的连接 timeout 是指在连接池中获取连接时, 客户端等待连接可用的时间. 如果在这个时间内没有获取到连接, 就会抛出 TimeoutException
. 这个 timeout 主要是为了防止连接池中的连接都被占用导致的阻塞. 这个 timeout 通常设置为几秒到几十秒不等, 具体数值可以根据实际情况来设置.
Apache HttpClient
Apache HttpClient 是 Apache 提供的一个功能强大的 HTTP 客户端库,广泛用于 Java 应用程序中进行 HTTP 请求和响应处理。它提供了丰富的功能,包括连接池、异步请求、自动重试、代理支持等。
Apache HttpClient 的连接池管理是通过 PoolingHttpClientConnectionManager
实现的。这个连接池可以管理多个连接,并且可以配置最大连接数、每个路由的最大连接数等参数。下面是一个使用 Apache HttpClient 的示例代码:
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
public class ApacheHttpClientExample {
public static void main(String[] args) {
// 创建一个 CloseableHttpClient 实例
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
// 创建一个 HttpGet 请求
HttpGet request = new HttpGet("https://api.example.com/v1/search");
// 发送请求并获取响应
try (CloseableHttpResponse response = httpClient.execute(request)) {
// 检查响应状态码
int statusCode = response.getStatusLine().getStatusCode();
System.out.println("Response Code: " + statusCode);
// 获取响应实体
HttpEntity entity = response.getEntity();
if (entity != null) {
// 将响应实体转换为字符串
String responseBody = EntityUtils.toString(entity);
System.out.println("Response Body: " + responseBody);
}
}
} catch (Exception e) {
// 处理异常
System.err.println("Exception occurred: " + e.getMessage());
e.printStackTrace();
}
}
}
连接池配置
Apache HttpClient 的连接池配置可以通过 PoolingHttpClientConnectionManager
来实现。但是配置的时候一定要注意, 除了配置连接池的最大连接数外, 还要注意每个路由的最大连接设置, 否则每个路由只有2个最大连接. 下面是一个使用 Apache HttpClient 的连接池配置示例代码:
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
public class ApacheHttpClientConfigExample {
public static void main(String[] args) {
// 创建一个连接池管理器
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
// 设置整个连接池的最大连接数
connectionManager.setMaxTotal(100); // 设置最大连接数为100
// 设置每个路由的最大连接数
connectionManager.setDefaultMaxPerRoute(20); // 设置每个路由的最大连接数为20
// 创建一个 HttpClient 实例,并使用连接池管理器
try (CloseableHttpClient httpClient = HttpClients.custom()
.setConnectionManager(connectionManager)
.build()) {
// 使用 httpClient 发送请求...
// 这里可以添加发送请求的代码逻辑
// 示例结束,不发送实际请求
System.out.println("HttpClient configured with connection pool.");
} catch (Exception e) {
e.printStackTrace();
}
}
}