一、踩坑现场:生产环境开了TLS,吞吐量直接“腰斩”
上周帮朋友排查生产环境的性能问题,他的服务用的是Undertow做Web容器,之前没开加密的时候,单台机器每秒能扛1200个请求,结果为了符合等保要求开了TLS,吞吐量直接掉到400左右,相当于砍了三分之二,用户反馈接口慢得像蜗牛,差点被投诉。
一开始我以为是服务器带宽不够,或者Undertow的配置没调对,结果查了半天,发现问题出在TLS本身的“开销”上。简单说,TLS就像给每次来往的快递套了个密封袋,还得双方互相验身份、换钥匙,每来一个新请求都得走一遍这个流程,Undertow默认的配置又刚好是“新请求必须重新走流程”,而且选的加密算法又特别“费力气”,自然就慢了。
1.1 先搞懂TLS的“额外工作”
为了让没接触过的开发者也能明白,我用生活化的话解释下:你和对方第一次打电话,得先报身份、对暗号、商量好后面说话用什么暗语(这就是TLS握手),这个过程是一次性的,但如果每次打电话都重新来一遍,那大部分时间都浪费在“互相确认”上了。
TLS握手的开销主要有两部分:一是验证对方证书的合法性(防止被冒名顶替),二是生成只有双方知道的“对称加密密钥”(用来加密后续的通信内容)。对于Undertow这种面向高并发的容器来说,默认配置下每个TCP连接、甚至每个请求都可能触发新的握手,这就成了性能瓶颈。
二、第一个优化:会话复用,省掉重复的握手流程
会话复用的核心逻辑,就是第一次握手完成后,把双方确认好的“密钥信息”存在某个地方,下次再联系的时候直接拿过来用,不用再重新做一遍“身份验证、密钥生成”的流程。就像你和朋友第一次见面后,下次见面直接打招呼就行,不用再互相介绍自己的身份证、工作单位。
2.1 Undertow的会话复用配置(技术栈:Java + Undertow)
我先给大家看Undertow默认的配置,这个配置是“会话复用关一半”的状态:
// 技术栈:Java 11 + Undertow 2.2.19.Final
UndertowServer server = UndertowServer.create()
.addHttpListener(8080, "0.0.0.0")
.addHttpsListener(8443, "0.0.0.0", new KeyStoreManager("server.p12", "your-password", "your-alias"))
// 开启SSL引擎配置
.setSslEngineConfig(new SslEngineConfig()
// 重点:开启会话缓存,默认是关闭的
.setSessionCacheEnabled(true)
// 会话缓存的大小,根据并发量调整,这里设为10000(能存1万个会话)
.setSessionCacheSize(10000)
// 会话的有效期,单位是秒,这里设为300秒(5分钟),不能太长,否则安全风险高
.setSessionTimeout(300)
)
.start();
这里要解释几个容易踩的坑:
- 会话缓存不能关:默认是false,必须手动开,不然Undertow不会存之前的握手信息。
- 缓存大小要合理:如果设得太小,并发高的时候,旧的会话会被挤掉,新的请求还是得重新握手;如果设得太大,会占用服务器内存,一般设为预期最大并发的1-2倍就行。
- 会话有效期不能太长:如果设成几小时甚至几天,万一之前的会话被黑客截获,就能用这个信息冒充用户,所以一般设5-15分钟比较合适,既省握手时间,又不会有太大安全风险。
2.2 验证会话复用是否生效
怎么知道会话复用真的起作用了?我给大家说个简单的验证方法,用openssl命令就能看:
# 技术栈:OpenSSL 1.1.1f(Ubuntu 20.04默认版本)
# 先连接一次服务,生成一个会话文件
openssl s_client -connect your-server-ip:8443 -sess_out session.txt
# 再用这个会话文件连接,看是否复用
openssl s_client -connect your-server-ip:8443 -sess_in session.txt
如果第二次连接的时候,输出里有“Reused, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384”(具体加密套件可能不一样),就说明会话复用成功了。
三、第二个优化:选对加密套件,兼顾安全和速度
加密套件就像“密封袋的材质”,有的材质特别结实但特别费力气去套,有的材质虽然没那么结实但套起来很快。我们要选的是“符合安全标准,同时套起来不费力气”的材质。
3.1 先搞懂加密套件的组成
一个加密套件由三部分组成,我用生活化的话解释:
- 密钥交换算法:就是“商量暗语”的方法,比如有的是用大数字加密(RSA),有的是用椭圆曲线(ECDH),后者比前者快很多。
- 认证算法:就是“验身份”的方法,比如用SHA-256还是SHA-512,后者更安全但计算量更大。
- 加密算法:就是“套密封袋”的方法,比如用AES-GCM还是CHACHA20-POLY1305,前者适合有硬件加速的服务器,后者适合没有硬件加速的服务器(比如ARM架构的服务器)。
Undertow默认的加密套件列表里,可能会有一些又慢又安全的套件,我们要把这些套件的优先级调低,甚至关掉,让服务优先用快的套件。
3.2 Undertow的加密套件配置(技术栈:Java + Undertow)
我给大家一个经过验证的配置,这个配置兼顾了安全和速度:
// 技术栈:Java 11 + Undertow 2.2.19.Final
UndertowServer server = UndertowServer.create()
.addHttpsListener(8443, "0.0.0.0", new KeyStoreManager("server.p12", "your-password", "your-alias"))
.setSslEngineConfig(new SslEngineConfig()
.setSessionCacheEnabled(true)
.setSessionCacheSize(10000)
.setSessionTimeout(300)
// 重点:自定义加密套件列表,按优先级排序,越靠前的越优先用
.setEnabledCipherSuites(new String[]{
// 优先用TLS 1.3的套件,因为TLS 1.3的握手比TLS 1.2快很多,而且加密套件更简单
"TLS_AES_256_GCM_SHA384",
"TLS_AES_128_GCM_SHA256",
"TLS_CHACHA20_POLY1305_SHA256",
// 然后用TLS 1.2的套件,适合不支持TLS 1.3的旧客户端
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256"
})
// 开启TLS 1.3,必须开,因为TLS 1.3的性能比TLS 1.2好很多
.setEnabledProtocols(new String[]{"TLSv1.3", "TLSv1.2"})
)
.start();
这里的配置逻辑是:
- 优先用TLS 1.3的套件:TLS 1.3把原来的“两次握手”改成了“一次握手”,性能提升特别明显,而且它的加密套件都是经过优化的,没有冗余的算法。
- 按硬件情况选加密算法:如果服务器有AES硬件加速(大部分x86服务器都有),优先用AES的套件;如果是ARM服务器或者没有硬件加速的服务器,优先用CHACHA20的套件。
- 不要用RSA密钥交换的套件:RSA的密钥交换比ECDH慢很多,只有旧客户端不支持ECDH的时候才会用到,所以把它放在后面。
3.3 验证加密套件是否生效
同样用openssl命令验证:
# 技术栈:OpenSSL 1.1.1f
# 连接服务,看用的加密套件
openssl s_client -connect your-server-ip:8443 | grep "Cipher"
如果输出的Cipher是配置里最前面的,比如“TLS_AES_256_GCM_SHA384”,就说明配置生效了。
四、应用场景、优缺点和注意事项
4.1 应用场景
这两个优化适合所有用Undertow做Web容器的生产环境,尤其是高并发的服务,比如电商的秒杀服务、API网关、微服务的服务端等。只要开了TLS,而且吞吐量不够,都可以试试这两个优化。
4.2 技术优缺点
会话复用的优缺点
优点:能大幅减少握手的次数,对于高并发的服务,性能提升特别明显,有的服务能提升30%-50%的吞吐量。 缺点:如果会话有效期太长,会有安全风险,比如之前的会话被黑客截获,就能冒充用户;如果会话缓存大小设得太小,会导致会话复用的效果不明显。
加密套件选择的优缺点
优点:能减少TLS握手和加密的计算量,性能提升也很明显,而且不会有太大的安全风险,只要选的套件符合安全标准就行。 缺点:如果选的套件太旧,会有安全风险;如果选的套件太新,旧客户端可能不支持,导致无法连接。
4.3 注意事项
- 会话有效期不能太长:一般设5-15分钟,具体根据业务的安全要求调整,如果业务对安全要求特别高,可以设得短一点,比如1分钟,但性能提升会小一点。
- 加密套件要符合安全标准:不要用太旧的套件,比如TLS 1.0、TLS 1.1,还有RSA密钥交换的套件,这些都有安全风险,尽量用TLS 1.3的套件。
- 要做压力测试:优化完之后一定要做压力测试,看吞吐量有没有提升,同时看安全性有没有问题,比如会不会有会话复用的安全漏洞,会不会有旧客户端无法连接的问题。
- 证书要合法:会话复用和加密套件的配置都不会影响证书的合法性,所以证书还是要从正规的CA机构申请,不能自己随便生成一个。
五、优化效果和总结
我朋友的服务优化完之后,吞吐量从400左右提升到了1100左右,几乎回到了没开TLS时的水平,用户反馈也正常了,而且符合等保的要求。
总结一下,Undertow开了TLS之后吞吐量下降,主要是因为默认的配置没有开启会话复用,而且选的加密套件太“费力气”。我们通过开启会话复用,省掉了重复的握手流程,通过选对加密套件,减少了TLS的计算量,从而在安全性和并发性能之间找到了最佳平衡点。
最后提醒大家,性能优化没有“万能公式”,一定要根据自己的业务情况、服务器的配置、客户端的情况来调整配置,而且一定要做压力测试,确保优化的效果和安全性。
评论
围绕“生产环境Undertow开启TLS后吞吐量骤降,通过会话复用机制与加密套件选择调整,在安全性与并发性能之间找到最佳平衡点”参与讨论