性能优化:SSL 会话复用与 OCSP Stapling

说到 SSL/TLS 的握手延迟,我估计不少同学都吃过这个亏。我自己在早期做金融类 App 时,就遇到过用户反馈「加载太慢」的问题。查了半天,发现罪魁祸首就是每次请求都要重新握手。嗯,今天我们就来聊聊怎么解决这个问题。

SSL 会话复用:别每次都重新认识

先想想看,你和朋友见面,第一次需要自我介绍、交换名片。第二次见面呢?你肯定说「嗨,又见面了」。SSL 会话复用就是这个道理。

说白了,就是客户端和服务器在第一次完整握手后,把会话信息缓存起来。下次再连接时,直接复用之前的会话参数,省掉一大半的握手步骤。

两种复用方式

方式 原理 延迟节省
Session ID 服务端生成唯一 ID,客户端保存 约 40-60%
Session Ticket 服务端加密票据,客户端保存 约 50-70%

我个人更推荐 Session Ticket。为什么?因为 Session ID 需要服务端维护缓存,分布式环境下很麻烦。我在项目中就踩过这个坑——多台服务器之间没同步 Session ID,导致用户频繁重新握手。

关键配置示例(Android OkHttp):

// 启用会话复用
OkHttpClient client = new OkHttpClient.Builder()
    .connectionPool(new ConnectionPool(5, 5, TimeUnit.MINUTES))
    .build();

// 自定义 SSLSocketFactory 时注意
// 一定要复用同一个 SSLContext 实例
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagers, new SecureRandom());
// 同一个 SSLContext 创建的 SSLSocketFactory 才能复用会话

这里有个细节:同一个 SSLContext 创建的 SSLSocketFactory 才能复用会话。我曾经见过有人每次请求都 new 一个 SSLContext,结果会话复用完全没生效。

OCSP Stapling:把证明提前准备好

证书吊销检查,说白了就是验证你的证书有没有被拉黑。传统做法是客户端自己去查,这又增加一次网络请求。OCSP Stapling 的思路很简单:服务端自己提前查好,握手时直接塞给客户端。

你想想看,这就像你去机场,安检人员提前帮你查好了黑名单,你直接过就行。省了排队时间。

实现要点

  • 服务端需要支持 OCSP Stapling(Nginx 1.3+、Apache 2.3+)
  • Android 端需要 API 21+ 才原生支持
  • 低版本 Android 需要自己实现

避坑指南:我曾经在 Android 4.4 设备上遇到过 OCSP Stapling 不生效的问题。后来发现是系统没有实现对应的扩展。解决方案是手动校验 Stapling 响应。

// Android 端验证 OCSP Stapling 示例
SSLSocket socket = (SSLSocket) factory.createSocket(host, port);
socket.startHandshake();

// 获取服务端返回的 OCSP 响应
// 注意:API 24+ 才支持直接获取
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
    SSLParameters params = socket.getSSLParameters();
    // 检查是否启用了 OCSP Stapling
    // 实际项目中需要更完整的验证逻辑
}

实战中的性能数据

我在一个日活百万的 App 上做过测试,数据如下:

场景 握手耗时 提升比例
首次握手(无优化) 约 350ms -
会话复用 约 120ms 65%
会话复用 + OCSP Stapling 约 90ms 74%

嗯,数据很直观。但要注意,这些优化只在短连接场景下效果明显。如果是长连接(比如 WebSocket),握手的次数本来就少,优化空间有限。

注意:会话复用不是万能的。如果服务端证书更换了,或者会话超时了,还是要重新完整握手。另外,Session Ticket 的加密密钥要定期轮换,否则有安全风险。

整体优化流程

下面这张图是我自己总结的优化决策流程,你可以参考:

SSL/TLS 握手优化决策流程 开始握手 有缓存会话? 复用会话 完整握手 支持 OCSP Stapling? 启用 OCSP Stapling 客户端自行验证

这个流程我用了好几年,基本覆盖了大部分场景。核心思路就是:能复用就复用,能提前查就提前查

一些额外的建议

  • 连接池大小要合理:不是越大越好。我一般设 5-10 个连接,超过这个数反而增加管理开销。
  • 超时时间要匹配:会话超时和服务端配置要一致。我曾经遇到过客户端设了 10 分钟,服务端 5 分钟就过期了,导致复用失败。
  • 低版本兼容:Android 4.x 设备上,OCSP Stapling 基本不可用。这时候要回退到传统验证方式。

总结一下:SSL 会话复用和 OCSP Stapling 是减少握手延迟最有效的两个手段。前者省掉重复握手,后者省掉证书验证的网络请求。两者配合使用,能把握手延迟降低 70% 以上。但要注意,这些优化不是银弹,需要根据实际场景来配置。


公众号:蓝海资料掘金营,微信deep3321