bigywater.tech
Java · 250

TLS 会话复用:减少重复握手成本

一个 TLS 连接建立之后,下一个连接可以复用会话,省掉大部分握手工作。JDK 的会话缓存怎么工作?TLS 1.2 与 1.3 有什么不同?什么条件下复用、什么条件下失效?这篇用本机的真实服务器和客户端逐项验证。

4.9 → 1.4 ms
TLS 1.3 本机握手:完整 → 复用(中位)
主机名:端口
客户端缓存会话的键(改用 localhost 就不复用)
票据开启时
服务器端 setSessionTimeout 与 setSessionCacheSize 不起作用

连续连接:TLS 1.3 与 1.2

实测 · 本机执行输出
  [TLSv1.3] 同一个客户端 SSLContext 连续 4 次连接到同一个服务器:
    第 1 次:TLSv1.3 TLS_AES_256_GCM_SHA384,session id 08238948…,创建时间与第 1 次相同 = false,握手+一次往返 85.9 ms
    第 2 次:TLSv1.3 TLS_AES_256_GCM_SHA384,session id c002a9b9…,创建时间与第 1 次相同 = true,握手+一次往返 6.9 ms
    第 3 次:TLSv1.3 TLS_AES_256_GCM_SHA384,session id 61e45198…,创建时间与第 1 次相同 = true,握手+一次往返 6.9 ms
    第 4 次:TLSv1.3 TLS_AES_256_GCM_SHA384,session id 2a4eb8aa…,创建时间与第 1 次相同 = true,握手+一次往返 6.2 ms
  [TLSv1.2] 同一个客户端 SSLContext 连续 4 次连接到同一个服务器:
    第 1 次:TLSv1.2 TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,session id 530ec12c…,创建时间与第 1 次相同 = false,握手+一次往返 23.0 ms
    第 2 次:TLSv1.2 TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,session id 530ec12c…,创建时间与第 1 次相同 = true,握手+一次往返 4.0 ms
    第 3 次:TLSv1.2 TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,session id 530ec12c…,创建时间与第 1 次相同 = true,握手+一次往返 2.2 ms
    第 4 次:TLSv1.2 TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,session id 530ec12c…,创建时间与第 1 次相同 = true,握手+一次往返 2.0 ms

完整握手与复用的耗时

实测 · 本机执行输出
  [TLSv1.3] 完整握手(每次新建客户端 SSLContext,40 次):中位 4.9 ms,最小 4.3;同一个客户端 SSLContext 复用会话(40 次,其中 40 次与第 1 次是同一个会话):中位 1.4 ms,最小 1.2
  [TLSv1.2] 完整握手(每次新建客户端 SSLContext,40 次):中位 4.4 ms,最小 3.9;同一个客户端 SSLContext 复用会话(40 次,其中 40 次与第 1 次是同一个会话):中位 0.5 ms,最小 0.4

复用的条件

实测 · 本机执行输出
  基线(TLS 默认协商出 TLSv1.3):第 2 次与第 1 次同一个会话 = true
  同一个客户端 SSLContext,改用 "localhost" 连接同一个端口:同一个会话 = false(客户端按 主机名:端口 缓存会话)
  再用 "127.0.0.1" 连接:同一个会话 = true
  另一个客户端 SSLContext(各自有自己的会话缓存)连同一个服务器:与第 1 个客户端的会话相同 = false
  客户端会话缓存:getSessionCacheSize = 20480(0 = 不限),getSessionTimeout = 86400 秒;服务器端 getSessionTimeout = 86400 秒,getSessionCacheSize = 20480
  对会话调用 invalidate() 之后再连接:与之前同一个会话 = false(创建了新会话)

服务器超时与缓存大小

实测 · 本机执行输出
  [默认]
  [TLSv1.3] 服务器端 setSessionTimeout(2 秒):立刻再连 → 同一个会话 = true;等 3.2 秒后再连 → 同一个会话 = true
  [TLSv1.3] 服务器会话缓存大小设为 1:客户端 1 连、客户端 2 连(新会话进缓存),客户端 1 再连 → 同一个会话 = true
  [TLSv1.2] 服务器端 setSessionTimeout(2 秒):立刻再连 → 同一个会话 = true;等 3.2 秒后再连 → 同一个会话 = true
  [TLSv1.2] 服务器会话缓存大小设为 1:客户端 1 连、客户端 2 连(新会话进缓存),客户端 1 再连 → 同一个会话 = true
  [-Djdk.tls.client.enableSessionTicketExtension=false -Djdk.tls.server.enableSessionTicketExtension=false]
  [TLSv1.3] 服务器端 setSessionTimeout(2 秒):立刻再连 → 同一个会话 = true;等 3.2 秒后再连 → 同一个会话 = false
  [TLSv1.3] 服务器会话缓存大小设为 1:客户端 1 连、客户端 2 连(新会话进缓存),客户端 1 再连 → 同一个会话 = false
  [TLSv1.2] 服务器端 setSessionTimeout(2 秒):立刻再连 → 同一个会话 = true;等 3.2 秒后再连 → 同一个会话 = false
  [TLSv1.2] 服务器会话缓存大小设为 1:客户端 1 连、客户端 2 连(新会话进缓存),客户端 1 再连 → 同一个会话 = false

分步演示

TLS 会话复用
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12,本机回环,自己用 keytool 生成的 RSA 3072 自签名证书;服务器与客户端是同一个 JVM 里的 SSLServerSocket 与 SSLSocket
    • 没有验证:跨网络的往返耗时、会话票据的加密细节与票据密钥轮换、客户端证书(mTLS)下的复用(见后面的篇目)、HttpClient 与连接池对复用的影响、SNI 对缓存键的影响、-Djavax.net.debug 的握手日志(没有展开)、为什么默认配置下服务器的缓存参数无效(只记录了现象,没有读源码)
    京ICP备2026067502号-1京公网安备11010502063487号