10.1 需要进行HTTP连接复用的高并发场景
10.1.1 反向代理Nginx与Java Web应用服务之间的HTTP高并发通信
10.1.2 微服务网关与微服务Provider实例之间的HTTP高并发通信
10.1.3 分布式微服务Provider实例之间的RPC的HTTP高并发通信
10.1.4 Java通过HTTP客户端访问REST接口服务的HTTP高并发通信
10.2 详解传输层TCP
10.2.1 TCP/IP的分层模型
10.2.2 HTTP报文传输原理
10.2.4 TCP的三次握手
一段简单的服务端监听新连接请求,并且被动打开(Passive Open)传输套接字的Java示例代码,具体如下:
public class SocketServer {
public static void main(String[] args) {
try {
//创建服务端socket
ServerSocket serverSocket = new ServerSocket(8080);
//循环监听等待客户端的连接
一段简单的客户端连接主动打开的Java示例代码如下:
public class SocketClient {
public static void main(String[] args) throws InterruptedException {
try {
//和服务器创建连接
Socket socket = new Socket("localhost",8080);
//写入给监听方的输出流
OutputStream os = socket.getOutputStream();
…
//读取监听方的输入流
InputStream is = socket.getInputStream();
…
} catch (Exception e) {
e.printStackTrace();
}
}
}
10.2.5 TCP的四次挥手
10.2.6 三次握手、四次挥手的常见面试题
10.3 TCP连接状态的原理与实验
10.3.2 通过netstat指令查看连接状态
10.4 HTTP长连接原理
HTTP长连接和HTTP短连接,指的是传输层的TCP连接是否被多次使用。
10.4.1 HTTP长连接和短连接
10.4.2 不同HTTP版本中的长连接选项
10.5 服务端HTTP长连接技术
10.5.1 应用服务器Tomcat的长连接配置
独立部署Tomcat的长连接配置
一个使用HTTP长连接的Connector连接器的配置示例大致如下(Tomcat版本假定8.0或以上): ```java
2. 内嵌式部署Tomcat的长连接配置<br />一段简单的定制化TomcatServletWebServerFactory容器工厂的配置代码大致如下:
```java
package com.crazymaker.springcloud.standard.config;
//省略import
@Configuration
@ConditionalOnClass({Connector.class})
public class TomcatConfig
{
@Autowired
private HttpConnectionProperties httpConnectionProperties;
@Bean
public TomcatServletWebServerFactory
createEmbeddedServletContainerFactory()
{
TomcatServletWebServerFactory tomcatFactory =
new TomcatServletWebServerFactory();
//增加连接器的定制配置
tomcatFactory.addConnectorCustomizers(connector ->
{
Http11NioProtocol protocol =
(Http11NioProtocol) connector.getProtocolHandler();
//定制keepAliveTimeout,确定下次请求过来之前Socket连接保持多久
//设置600秒内没有请求则服务端自动断开Socket连接
protocol.setKeepAliveTimeout(600000);
//当客户端发送的请求超过10000个时强制关闭Socket连接
protocol.setMaxKeepAliveRequests(1000);
//设置最大连接数
protocol.setMaxConnections(3000);
//省略其他配置
});
return tomcatFactory;
}
}
10.5.2 Nginx承担服务端角色时的长连接配置
一段简单的Nginx承担服务端角色时的长连接配置代码如下:
#…
http {
include mime.types;
default_type application/octet-stream;
#长连接保持时长
keepalive_timeout 65s;
#长连接最大处理请求数
keepalive_requests 1000;
#…
server {
listen 80;
server_name openresty localhost;
10.5.3 服务端长连接设置的注意事项
- 单个客户端的HTTP请求数较少时
2. 单个客户端的请求数较多时10.6 客户端HTTP长连接技术原理与实验
10.6.1 HttpURLConnection短连接技术
```java package com.crazymakercircle.util; //省略import
//HTTP 客户端处理帮助类 @Slf4j public class HttpClientHelper { /**
* 使用JDK的 java.net.HttpURLConnection 发起HTTP请求
*/
public static String jdkGet(String url)
{
InputStream inputStream = null; //输入流
HttpURLConnection httpConnection = null; //HTTP连接实例
StringBuilder builder = new StringBuilder();
try
{
URL restServiceURL = new URL(url);
//打开HttpURLConnection连接实例
httpConnection =
(HttpURLConnection) restServiceURL.openConnection();
//设置请求头
httpConnection.setRequestMethod("GET");
httpConnection.setRequestProperty(
"Accept", "application/json");
//建立连接,发送请求
httpConnection.connect();
//读取响应码
if (httpConnection.getResponseCode() != 200)
{
throw new RuntimeException("Failed with Error code : "
+ httpConnection.getResponseCode());
}
//读取响应内容(字节流)
inputStream = httpConnection.getInputStream();
byte[] b = new byte[1024];
int length = -1;
while ((length = inputStream.read(b)) != -1)
{
builder.append(new String(b, 0, length));
}
} catch (MalformedURLException e)
{
e.printStackTrace();
} catch (IOException e)
{
e.printStackTrace();
} finally
{
//关闭流和连接
quietlyClose(inputStream);
httpConnection.disconnect();
}
return builder.toString();
}
//…
}
<a name="lgFEs"></a>
## 10.6.2 HTTP短连接的通信实验
<a name="xImQT"></a>
## 10.6.3 Apache HttpClient客户端的HTTP长连接技术
![image.png](https://cdn.nlark.com/yuque/0/2022/png/27178439/1650250453265-a2eb1a8a-f506-4a43-9a13-a2492a5f33cf.png#clientId=u249f0497-48b2-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=463&id=u162ef499&margin=%5Bobject%20Object%5D&name=image.png&originHeight=579&originWidth=844&originalType=binary&ratio=1&rotation=0&showTitle=false&size=102874&status=done&style=none&taskId=u80347f99-cd6d-4d2c-87df-6a6cc0d2dfc&title=&width=675.2)
```java
package com.crazymakercircle.util;
//省略import
//HTTP 协议处理帮助类
@Slf4j
public class HttpClientHelper
{
//长连接的保持时长,单位为ms(毫秒)
private static final long KEEP_ALIVE_DURATION = 600000;
//客户端和服务器建立连接的超时时长,单位ms
private static final int CONNECT_TIMEOUT = 2000;
//建立连接后,客户端从服务器读取数据的超时时长,单位ms
private static final int SOCKET_TIMEOUT = 2000;
//从连接池获取连接的超时时长,单位ms
private static final int REQUEST_TIMEOUT = 2000;
//无效长连接的清理间隔,单位ms
private static final int EXPIRED_CHECK_GAP = 6000;
//连接池内对不活跃连接的检查间隔,单位ms
private static final int VALIDATE_AFTER_INACTIVITY = 2000;
//最大的连接数
private static final int POOL_MAXTOTAL = 500;
//每一个路由(可以理解为IP+端口)的最大连接数
private static final int MAX_PER_ROUTE = 500;
//单例:HTTP长连接管理器,也就是连接池
private static PoolingHttpClientConnectionManager
httpClientConnectionManager;
//单例:全局的池化HTTP客户端实例
private static CloseableHttpClient pooledHttpClient;
//线程池:负责HTTP连接池的无效连接清理
private static ScheduledExecutorService monitorExecutor = null;
//创建全局连接池:HTTP连接管理器
public static void createHttpClientConnectionManager()
{
//DNS解析器
DnsResolver dnsResolver = SystemDefaultDnsResolver.INSTANCE;
//负责HTTP传输的套接字工厂
ConnectionSocketFactory plainSocketFactory =
PlainConnectionSocketFactory.getSocketFactory();
//负责HTTPS传输的安全套接字工厂
LayeredConnectionSocketFactory sslSocketFactory =
SSLConnectionSocketFactory.getSocketFactory();
//根据应用层协议,为其注册传输层的套接字工厂
Registry<ConnectionSocketFactory> registry =
RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", plainSocketFactory)
.register("https", sslSocketFactory)
.build();
//创建连接管理器
httpClientConnectionManager =
new PoolingHttpClientConnectionManager(
registry, //传输层套接字注册器
null,
null,
dnsResolver, //DNS解析器
KEEP_ALIVE_DURATION, //长连接的连接保持时长
TimeUnit.MILLISECONDS); //保持时长的时间单位
//连接池内,连接不活跃多长时间后,需要进行一次验证
//默认为2秒 TimeUnit.MILLISECONDS
httpClientConnectionManager.setValidateAfterInactivity(
VALIDATE_AFTER_INACTIVITY);
//最大连接数,高于这个值时的新连接请求,需要阻塞和排队等待
httpClientConnectionManager.setMaxTotal(POOL_MAXTOTAL);
//设置每个route默认的最大连接数,路由是对MaxTotal的细分
//每个路由实际最大连接数默认值是由DefaultMaxPerRoute控制的
//MaxPerRoute设置过小,无法支持大并发
httpClientConnectionManager.setDefaultMaxPerRoute(MAX_PER_ROUTE);
}
//省略其他方法
}
客户端关闭异常连接的定时执行代码,大致如下:
package com.crazymakercircle.util;
//省略import
//HTTP 协议处理帮助类
@Slf4j
public class HttpClientHelper
{
//省略其他方法
/**
* 定时处理线程:对异常连接进行关闭
*/
private static void startExpiredConnectionsMonitor()
{
//空闲监测,配置文件默认为6秒,生产环境建议稍微放大一点
int idleCheckGap = IDLE_CHECK_GAP;
//设置保持连接的时长,根据实际情况调整配置
long keepAliveTimeout = KEEP_ALIVE_DURATION;
//开启监控线程,关闭异常和空闲线程
monitorExecutor = Executors.newScheduledThreadPool(1);
monitorExecutor.scheduleAtFixedRate(new TimerTask()
{
@Override
public void run()
{
//关闭异常连接,包括被服务端关闭的长连接
httpClientConnectionManager.closeExpiredConnections();
//关闭keepAliveTimeout(保持连接时长)超时的不活跃连接
httpClientConnectionManager.closeIdleConnections(
keepAliveTimeout, TimeUnit.MILLISECONDS);
//获取连接池的状态
PoolStats status =
httpClientConnectionManager.getTotalStats();
//输出连接池的状态,仅供测试使用
/*
log.info(" manager.getRoutes().size():" +
manager.getRoutes().size());
log.info(" status.getAvailable():" + status.getAvailable());
log.info(" status.getPending():" + status.getPending());
log.info(" status.getLeased():" + status.getLeased());
log.info(" status.getMax():" + status.getMax());
*/
}
}, idleCheckGap, idleCheckGap, TimeUnit.MILLISECONDS);
}
}
下面是一段创建带连接池的全局客户端实例pooledHttpClient的代码,大致如下:
package com.crazymakercircle.util;
//省略import
//HTTP 协议处理帮助类
@Slf4j
public class HttpClientHelper
{
//省略其他方法
/**
* 创建带连接池的 pooledHttpClient 全局客户端实例
*/
public static CloseableHttpClient pooledHttpClient()
{
if (null != pooledHttpClient)
{
return pooledHttpClient;
}
createHttpClientConnectionManager();
log.info(" Apache httpclient 初始化HTTP连接池 starting===");
//请求配置实例
RequestConfig.Builder requestConfigBuilder =
RequestConfig.custom();
//读取数据的超时设置
requestConfigBuilder.setSocketTimeout(SOCKET_TIMEOUT);
//建立连接的超时设置
requestConfigBuilder.setConnectTimeout(CONNECT_TIMEOUT);
//从连接池获取连接的等待超时时间设置
requestConfigBuilder.setConnectionRequestTimeout(
REQUEST_TIMEOUT);
RequestConfig config = requestConfigBuilder.build();
//httpclient建造者实例
HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
//设置连接池管理器
httpClientBuilder.setConnectionManager(
httpClientConnectionManager);
//设置HTTP请求配置信息
httpClientBuilder.setDefaultRequestConfig(config);
//httpclient默认提供了一个Keep-Alive策略
//这里进行定制:确保客户端与服务端在长连接的保持时长一致
httpClientBuilder.setKeepAliveStrategy(
new ConnectionKeepAliveStrategy()
{
@Override
public long getKeepAliveDuration(
HttpResponse response, HttpContext context)
{
//获取响应头中HTTP.CONN_KEEP_ALIVE中的Keep-Alive部分值
//如果服务端响应"Keep-Alive: timeout=60",表示保持时长为60秒
//则客户端也设置连接的保持时长为60秒
//目的:确保客户端与服务端在长连接的保持时长一致
HeaderElementIterator it = new BasicHeaderElementIterator
(response.headerIterator(HTTP.CONN_KEEP_ALIVE));
while (it.hasNext())
{
HeaderElement he = it.nextElement();
String param = he.getName();
String value = he.getValue();
if (value != null && param.equalsIgnoreCase
("timeout"))
{
try
{
return Long.parseLong(value) * 1000;
} catch (final NumberFormatException ignore)
{
}
}
}
//如果服务端响应头中没有设置保持时长,则使用客户端统一定义时长为600秒
return KEEP_ALIVE_DURATION;
}
});
//实例化:全局的池化HTTP客户端实例
pooledHttpClient = httpClientBuilder.build();
log.info(" Apache httpclient 初始化HTTP连接池 finished===");
//启动定时处理线程:对异常和空闲连接进行关闭
startExpiredConnectionsMonitor();
return pooledHttpClient;
}
}
10.6.4 Apache HttpClient客户端长连接实验
10.6.5 Nginx承担客户端角色时的长连接技术
keepalive指令的使用示例大致如下:
upstream memcached_backend {
server 127.0.0.1:11211;
server 10.0.0.2:11211;
//可以理解为连接池可以缓存32个连接
keepalive 32;
}
综合以上两点,在Nginx上负责下游HTTP请求路由和转发的location配置区块中,需要使用proxy_http_version指令和proxy_set_header指令完成HTTP请求头的配置优化,具体代码如下:
server {
listen 8080 default_server;
server_name "";
//…
//处理下游客户端请求转发的location配置区块
location / {
proxy_pass http://memcached_backend;
//转发之前,进行请求头重置,重置HTTP协议的版本为1.1
proxy_http_version 1.1;