10.1 需要进行HTTP连接复用的高并发场景

10.1.1 反向代理Nginx与Java Web应用服务之间的HTTP高并发通信

image.png

10.1.2 微服务网关与微服务Provider实例之间的HTTP高并发通信

image.png

10.1.3 分布式微服务Provider实例之间的RPC的HTTP高并发通信

image.png

10.1.4 Java通过HTTP客户端访问REST接口服务的HTTP高并发通信

10.2 详解传输层TCP

10.2.1 TCP/IP的分层模型

image.png
image.png

10.2.2 HTTP报文传输原理

image.png
image.png

10.2.4 TCP的三次握手

一段简单的服务端监听新连接请求,并且被动打开(Passive Open)传输套接字的Java示例代码,具体如下:

  1. public class SocketServer {
  2. public static void main(String[] args) {
  3. try {
  4. //创建服务端socket
  5. ServerSocket serverSocket = new ServerSocket(8080);
  6. //循环监听等待客户端的连接

一段简单的客户端连接主动打开的Java示例代码如下:

  1. public class SocketClient {
  2. public static void main(String[] args) throws InterruptedException {
  3. try {
  4. //和服务器创建连接
  5. Socket socket = new Socket("localhost",8080);
  6. //写入给监听方的输出流
  7. OutputStream os = socket.getOutputStream();
  8. //读取监听方的输入流
  9. InputStream is = socket.getInputStream();
  10. } catch (Exception e) {
  11. e.printStackTrace();
  12. }
  13. }
  14. }

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的长连接配置

  1. 独立部署Tomcat的长连接配置
    一个使用HTTP长连接的Connector连接器的配置示例大致如下(Tomcat版本假定8.0或以上): ```java

  1. 2. 内嵌式部署Tomcat的长连接配置<br />一段简单的定制化TomcatServletWebServerFactory容器工厂的配置代码大致如下:
  2. ```java
  3. package com.crazymaker.springcloud.standard.config;
  4. //省略import
  5. @Configuration
  6. @ConditionalOnClass({Connector.class})
  7. public class TomcatConfig
  8. {
  9. @Autowired
  10. private HttpConnectionProperties httpConnectionProperties;
  11. @Bean
  12. public TomcatServletWebServerFactory
  13. createEmbeddedServletContainerFactory()
  14. {
  15. TomcatServletWebServerFactory tomcatFactory =
  16. new TomcatServletWebServerFactory();
  17. //增加连接器的定制配置
  18. tomcatFactory.addConnectorCustomizers(connector ->
  19. {
  20. Http11NioProtocol protocol =
  21. (Http11NioProtocol) connector.getProtocolHandler();
  22. //定制keepAliveTimeout,确定下次请求过来之前Socket连接保持多久
  23. //设置600秒内没有请求则服务端自动断开Socket连接
  24. protocol.setKeepAliveTimeout(600000);
  25. //当客户端发送的请求超过10000个时强制关闭Socket连接
  26. protocol.setMaxKeepAliveRequests(1000);
  27. //设置最大连接数
  28. protocol.setMaxConnections(3000);
  29. //省略其他配置
  30. });
  31. return tomcatFactory;
  32. }
  33. }

10.5.2 Nginx承担服务端角色时的长连接配置

一段简单的Nginx承担服务端角色时的长连接配置代码如下:

  1. #…
  2. http {
  3. include mime.types;
  4. default_type application/octet-stream;
  5. #长连接保持时长
  6. keepalive_timeout 65s;
  7. #长连接最大处理请求数
  8. keepalive_requests 1000;
  9. #…
  10. server {
  11. listen 80;
  12. server_name openresty localhost;

10.5.3 服务端长连接设置的注意事项

  1. 单个客户端的HTTP请求数较少时
    2. 单个客户端的请求数较多时

    10.6 客户端HTTP长连接技术原理与实验

    10.6.1 HttpURLConnection短连接技术

    image.png ```java package com.crazymakercircle.util; //省略import

//HTTP 客户端处理帮助类 @Slf4j public class HttpClientHelper { /**

  1. * 使用JDK java.net.HttpURLConnection 发起HTTP请求
  2. */
  3. public static String jdkGet(String url)
  4. {
  5. InputStream inputStream = null; //输入流
  6. HttpURLConnection httpConnection = null; //HTTP连接实例
  7. StringBuilder builder = new StringBuilder();
  8. try
  9. {
  10. URL restServiceURL = new URL(url);
  11. //打开HttpURLConnection连接实例
  12. httpConnection =
  13. (HttpURLConnection) restServiceURL.openConnection();
  14. //设置请求头
  15. httpConnection.setRequestMethod("GET");
  16. httpConnection.setRequestProperty(
  17. "Accept", "application/json");
  18. //建立连接,发送请求
  19. httpConnection.connect();
  20. //读取响应码
  21. if (httpConnection.getResponseCode() != 200)
  22. {
  23. throw new RuntimeException("Failed with Error code : "
  24. + httpConnection.getResponseCode());
  25. }
  26. //读取响应内容(字节流)
  27. inputStream = httpConnection.getInputStream();
  28. byte[] b = new byte[1024];
  29. int length = -1;
  30. while ((length = inputStream.read(b)) != -1)
  31. {
  32. builder.append(new String(b, 0, length));
  33. }
  34. } catch (MalformedURLException e)
  35. {
  36. e.printStackTrace();
  37. } catch (IOException e)
  38. {
  39. e.printStackTrace();
  40. } finally
  41. {
  42. //关闭流和连接
  43. quietlyClose(inputStream);
  44. httpConnection.disconnect();
  45. }
  46. return builder.toString();
  47. }
  48. //…

}

  1. <a name="lgFEs"></a>
  2. ## 10.6.2 HTTP短连接的通信实验
  3. <a name="xImQT"></a>
  4. ## 10.6.3 Apache HttpClient客户端的HTTP长连接技术
  5. ![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)
  6. ```java
  7. package com.crazymakercircle.util;
  8. //省略import
  9. //HTTP 协议处理帮助类
  10. @Slf4j
  11. public class HttpClientHelper
  12. {
  13. //长连接的保持时长,单位为ms(毫秒)
  14. private static final long KEEP_ALIVE_DURATION = 600000;
  15. //客户端和服务器建立连接的超时时长,单位ms
  16. private static final int CONNECT_TIMEOUT = 2000;
  17. //建立连接后,客户端从服务器读取数据的超时时长,单位ms
  18. private static final int SOCKET_TIMEOUT = 2000;
  19. //从连接池获取连接的超时时长,单位ms
  20. private static final int REQUEST_TIMEOUT = 2000;
  21. //无效长连接的清理间隔,单位ms
  22. private static final int EXPIRED_CHECK_GAP = 6000;
  23. //连接池内对不活跃连接的检查间隔,单位ms
  24. private static final int VALIDATE_AFTER_INACTIVITY = 2000;
  25. //最大的连接数
  26. private static final int POOL_MAXTOTAL = 500;
  27. //每一个路由(可以理解为IP+端口)的最大连接数
  28. private static final int MAX_PER_ROUTE = 500;
  29. //单例:HTTP长连接管理器,也就是连接池
  30. private static PoolingHttpClientConnectionManager
  31. httpClientConnectionManager;
  32. //单例:全局的池化HTTP客户端实例
  33. private static CloseableHttpClient pooledHttpClient;
  34. //线程池:负责HTTP连接池的无效连接清理
  35. private static ScheduledExecutorService monitorExecutor = null;
  36. //创建全局连接池:HTTP连接管理器
  37. public static void createHttpClientConnectionManager()
  38. {
  39. //DNS解析器
  40. DnsResolver dnsResolver = SystemDefaultDnsResolver.INSTANCE;
  41. //负责HTTP传输的套接字工厂
  42. ConnectionSocketFactory plainSocketFactory =
  43. PlainConnectionSocketFactory.getSocketFactory();
  44. //负责HTTPS传输的安全套接字工厂
  45. LayeredConnectionSocketFactory sslSocketFactory =
  46. SSLConnectionSocketFactory.getSocketFactory();
  47. //根据应用层协议,为其注册传输层的套接字工厂
  48. Registry<ConnectionSocketFactory> registry =
  49. RegistryBuilder.<ConnectionSocketFactory>create()
  50. .register("http", plainSocketFactory)
  51. .register("https", sslSocketFactory)
  52. .build();
  53. //创建连接管理器
  54. httpClientConnectionManager =
  55. new PoolingHttpClientConnectionManager(
  56. registry, //传输层套接字注册器
  57. null,
  58. null,
  59. dnsResolver, //DNS解析器
  60. KEEP_ALIVE_DURATION, //长连接的连接保持时长
  61. TimeUnit.MILLISECONDS); //保持时长的时间单位
  62. //连接池内,连接不活跃多长时间后,需要进行一次验证
  63. //默认为2秒 TimeUnit.MILLISECONDS
  64. httpClientConnectionManager.setValidateAfterInactivity(
  65. VALIDATE_AFTER_INACTIVITY);
  66. //最大连接数,高于这个值时的新连接请求,需要阻塞和排队等待
  67. httpClientConnectionManager.setMaxTotal(POOL_MAXTOTAL);
  68. //设置每个route默认的最大连接数,路由是对MaxTotal的细分
  69. //每个路由实际最大连接数默认值是由DefaultMaxPerRoute控制的
  70. //MaxPerRoute设置过小,无法支持大并发
  71. httpClientConnectionManager.setDefaultMaxPerRoute(MAX_PER_ROUTE);
  72. }
  73. //省略其他方法
  74. }

客户端关闭异常连接的定时执行代码,大致如下:

  1. package com.crazymakercircle.util;
  2. //省略import
  3. //HTTP 协议处理帮助类
  4. @Slf4j
  5. public class HttpClientHelper
  6. {
  7. //省略其他方法
  8. /**
  9. * 定时处理线程:对异常连接进行关闭
  10. */
  11. private static void startExpiredConnectionsMonitor()
  12. {
  13. //空闲监测,配置文件默认为6秒,生产环境建议稍微放大一点
  14. int idleCheckGap = IDLE_CHECK_GAP;
  15. //设置保持连接的时长,根据实际情况调整配置
  16. long keepAliveTimeout = KEEP_ALIVE_DURATION;
  17. //开启监控线程,关闭异常和空闲线程
  18. monitorExecutor = Executors.newScheduledThreadPool(1);
  19. monitorExecutor.scheduleAtFixedRate(new TimerTask()
  20. {
  21. @Override
  22. public void run()
  23. {
  24. //关闭异常连接,包括被服务端关闭的长连接
  25. httpClientConnectionManager.closeExpiredConnections();
  26. //关闭keepAliveTimeout(保持连接时长)超时的不活跃连接
  27. httpClientConnectionManager.closeIdleConnections(
  28. keepAliveTimeout, TimeUnit.MILLISECONDS);
  29. //获取连接池的状态
  30. PoolStats status =
  31. httpClientConnectionManager.getTotalStats();
  32. //输出连接池的状态,仅供测试使用
  33. /*
  34. log.info(" manager.getRoutes().size():" +
  35. manager.getRoutes().size());
  36. log.info(" status.getAvailable():" + status.getAvailable());
  37. log.info(" status.getPending():" + status.getPending());
  38. log.info(" status.getLeased():" + status.getLeased());
  39. log.info(" status.getMax():" + status.getMax());
  40. */
  41. }
  42. }, idleCheckGap, idleCheckGap, TimeUnit.MILLISECONDS);
  43. }
  44. }

下面是一段创建带连接池的全局客户端实例pooledHttpClient的代码,大致如下:

  1. package com.crazymakercircle.util;
  2. //省略import
  3. //HTTP 协议处理帮助类
  4. @Slf4j
  5. public class HttpClientHelper
  6. {
  7. //省略其他方法
  8. /**
  9. * 创建带连接池的 pooledHttpClient 全局客户端实例
  10. */
  11. public static CloseableHttpClient pooledHttpClient()
  12. {
  13. if (null != pooledHttpClient)
  14. {
  15. return pooledHttpClient;
  16. }
  17. createHttpClientConnectionManager();
  18. log.info(" Apache httpclient 初始化HTTP连接池 starting===");
  19. //请求配置实例
  20. RequestConfig.Builder requestConfigBuilder =
  21. RequestConfig.custom();
  22. //读取数据的超时设置
  23. requestConfigBuilder.setSocketTimeout(SOCKET_TIMEOUT);
  24. //建立连接的超时设置
  25. requestConfigBuilder.setConnectTimeout(CONNECT_TIMEOUT);
  26. //从连接池获取连接的等待超时时间设置
  27. requestConfigBuilder.setConnectionRequestTimeout(
  28. REQUEST_TIMEOUT);
  29. RequestConfig config = requestConfigBuilder.build();
  30. //httpclient建造者实例
  31. HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
  32. //设置连接池管理器
  33. httpClientBuilder.setConnectionManager(
  34. httpClientConnectionManager);
  35. //设置HTTP请求配置信息
  36. httpClientBuilder.setDefaultRequestConfig(config);
  37. //httpclient默认提供了一个Keep-Alive策略
  38. //这里进行定制:确保客户端与服务端在长连接的保持时长一致
  39. httpClientBuilder.setKeepAliveStrategy(
  40. new ConnectionKeepAliveStrategy()
  41. {
  42. @Override
  43. public long getKeepAliveDuration(
  44. HttpResponse response, HttpContext context)
  45. {
  46. //获取响应头中HTTP.CONN_KEEP_ALIVE中的Keep-Alive部分值
  47. //如果服务端响应"Keep-Alive: timeout=60",表示保持时长为60秒
  48. //则客户端也设置连接的保持时长为60秒
  49. //目的:确保客户端与服务端在长连接的保持时长一致
  50. HeaderElementIterator it = new BasicHeaderElementIterator
  51. (response.headerIterator(HTTP.CONN_KEEP_ALIVE));
  52. while (it.hasNext())
  53. {
  54. HeaderElement he = it.nextElement();
  55. String param = he.getName();
  56. String value = he.getValue();
  57. if (value != null && param.equalsIgnoreCase
  58. ("timeout"))
  59. {
  60. try
  61. {
  62. return Long.parseLong(value) * 1000;
  63. } catch (final NumberFormatException ignore)
  64. {
  65. }
  66. }
  67. }
  68. //如果服务端响应头中没有设置保持时长,则使用客户端统一定义时长为600秒
  69. return KEEP_ALIVE_DURATION;
  70. }
  71. });
  72. //实例化:全局的池化HTTP客户端实例
  73. pooledHttpClient = httpClientBuilder.build();
  74. log.info(" Apache httpclient 初始化HTTP连接池 finished===");
  75. //启动定时处理线程:对异常和空闲连接进行关闭
  76. startExpiredConnectionsMonitor();
  77. return pooledHttpClient;
  78. }
  79. }

10.6.4 Apache HttpClient客户端长连接实验

10.6.5 Nginx承担客户端角色时的长连接技术

keepalive指令的使用示例大致如下:

  1. upstream memcached_backend {
  2. server 127.0.0.1:11211;
  3. server 10.0.0.2:11211;
  4. //可以理解为连接池可以缓存32个连接
  5. keepalive 32;
  6. }

综合以上两点,在Nginx上负责下游HTTP请求路由和转发的location配置区块中,需要使用proxy_http_version指令和proxy_set_header指令完成HTTP请求头的配置优化,具体代码如下:

  1. server {
  2. listen 8080 default_server;
  3. server_name "";
  4. //…
  5. //处理下游客户端请求转发的location配置区块
  6. location / {
  7. proxy_pass http://memcached_backend;
  8. //转发之前,进行请求头重置,重置HTTP协议的版本为1.1
  9. proxy_http_version 1.1;