TCP 优化
Linux系统内核参数优化
[root@www ~]# cat /etc/redhat-release;uname -r
CentOS Linux release 7.5.1804 (Core)
3.10.0-862.11.6.el7.x86_64
TLS 的下一层是 TCP 协议,因此对 TCP 的优化是能够直接影响到 TLS 的性能效率。
在对 TCP 的优化中,主要涉及如下几个概念:
(1)拥塞控制(congestion control)机制:在一个 TCP 链接开始时,不知道对方的速度有多快。若是有足够大的带宽,服务器端能够用最快的速度传送数据,可是若是对方的网络很慢,服务器发送的数据太多的话,会压跨链接,致使链接中断。因此,每一个 TCP 链接都有一个称为拥塞窗口(cwnd = congestion window)的速度极限。这个窗口最初较小,在通讯的过程当中,若是双方都能接受这个速度,那么会加大这个拥塞窗口的值(初期增加很快,翻倍增加),这种机制被叫作慢启动(slow start)。
拥塞控制机制对于 TLS 链接的影响比较大,TLS 握手消耗了宝贵的初始链接字节(当拥塞窗口较小时);若是拥塞窗口足够大,那么慢启动不会有额外的延迟。可是,若是握手消息长度超过了拥塞窗口大小,发送方将必须把这个长信息拆分红两块,先发送一块,等待确认(1个往返),增长拥塞窗口,而后再发送剩下的部分。这样就增长了由 TLS 握手形成的延时。
(2) 慢启动阈值 ssthresh(避免 cwnd 增加过快,网络没法承担,形成丢包)
若是 cwnd 小于 ssthresh,表示在慢启动阶段,cwnd是翻倍增加的;若是 cwnd 大于 ssthresh,那么表示在拥塞避免阶段,这时候 cwnd 再也不像慢启动阶段那样翻倍增加,而是线性增加,尽可能避免网络拥塞。
(3) 接收窗口(rwnd),用来表示最多能保存多少数据,实际中接收窗口rwnd的合理值取决于BDP的大小,也就是带宽和延迟的乘积。若是带宽是 80Mbps,延迟是 100ms,那么计算过程以下:
BDP = 80Mbps 100ms = (80 / 8) (100 / 1000) = 1MB = 1024KB = 1048576B
TCP 用16位来记录窗口大小,也就是说最大值是64KB,若是超过这个值,就须要 tcp_window_scaling 机制(默认是开启)。配置内核参数中接收缓冲的大小,就能够控制接收窗口的大小:
net.ipv4.tcp_rmem = <MIN> <DEFAULT> <MAX>
Linux自己有一个缓冲大小自动调优的机制,窗口的实际大小会自动在最小值和最大值之间变化,找到性能和资源的平衡点。确认缓冲大小自动调优机制(0:关闭、1:开启):sysctl -a | grep tcp_moderate_rcvbuf。若是缓冲大小自动调优机制设置成关闭状态,那么就把缓冲的 DEFAULT 值设置为 BDP;若是缓冲大小自动调优机制设置成开启状态,那么就把缓冲的 MAX 设置为 BDP。
(4) 存储 TCP 链接自己一些信息的额外开销:net.ipv4.tcp_adv_win_scale 的值多是 1 或者 2,若是是 1 的话,则表示二分之一的缓冲被用来作额外开销,若是是 2 的话,则表示四分之一的缓冲被用来作额外开销。按照这个逻辑,缓冲最终的合理值的具体计算方法以下:result=BDP / (1 – 1 / 2^tcp_adv_win_scale)。
(5) 空闲链接回到慢启动:慢启动在一段时间内没有任何流量的链接上起做用,达到下降速度的效果,而且速度降低很是快。所谓的“一段时间”能够是很是小的,好比1秒钟,但在实际场景中,每个长链接(例如使用 HTTP 长链接)的速度都有可能被调到很慢!为了保持速度建议禁用这个功能。
在 Linux 上,能够在链接空闲时禁用慢启动: 0表示否,1表示开启慢启动,默认是1
能够经过一下命令使其临时生效,但重启之后就失效了,查看:sysctl -a | gerp slow_start_after_idle
临时:sysctl -w net.ipv4.tcp_slow_start_after_idle=0
永久生效:将 net.ipv4.tcp_slow_start_after_idle=0 设置添加到 /etc/sysctl.conf 配置文件中。
对拥塞窗口(cwnd)初始值调优:
启动速度限制被称为初始拥塞窗口(initial congestion window, initcwnd )。2013年4月发布的 RFC6928,google 建议默认状况下初始拥塞窗口设置为10个 MSS(约15 KB)。【Centos 7默认是10MSS】早期的建议是使用2或4个MSS(约3—6KB)。MSS 是 TCP 层上的概念,大小是 1460 字节。IP 层上是 MTU,1500字节。
[root@www ~]# sysctl -a |grep ssthresh
net.ipv4.tcp_max_ssthresh = 0 #在虚拟机中
[root@www ~]# sysctl -a |grep tcp_window_scaling
net.ipv4.tcp_window_scaling = 1
[root@www ~]# cat /proc/sys/net/ipv4/tcp_rmem # rwnd值
4096 87380 6291456
[root@www ~]# sysctl -a |grep tcp_moderate_rcvbuf
net.ipv4.tcp_moderate_rcvbuf = 1
[root@www ~]# sysctl -a |grep adv_win_scale
net.ipv4.tcp_adv_win_scale = 1
[root@www ~]# sysctl -a |grep start_after_idle
net.ipv4.tcp_slow_start_after_idle = 1
# 设置cwnd
[root@www ~]# ip route
default via 172.16.216.2 dev ens33
169.254.0.0/16 dev ens33 scope link metric 1002
172.16.216.0/24 dev ens33 proto kernel scope link src 172.16.216.188
[root@www ~]# ip route | while read p; do ip route change $p initcwnd 10; done
[root@www ~]# ip route
default via 172.16.216.2 dev ens33 initcwnd 10
169.254.0.0/16 dev ens33 scope link metric 1002 initcwnd 10
172.16.216.0/24 dev ens33 proto kernel scope link src 172.16.216.188 initcwnd 10
# initcwnd 10:初始化cwnd
# 单方面提高发送端 cwnd 的大小并不必定有效,由于网络中实际传输的未经确认的数据大小取决于 rwnd 和 cwnd 中的最小值,因此一旦接收方的 rwnd 比较小的话,会抑制 cwnd 的发挥。
# 设置initrwnd(linux kernel 2.6.33 and newer)
[root@www ~]# ip route
default via 172.16.216.2 dev ens33
169.254.0.0/16 dev ens33 scope link metric 1002
172.16.216.0/24 dev ens33 proto kernel scope link src 172.16.216.188
[root@www ~]# ip route | while read p; do ip route change $p initrwnd 10; done
[root@www ~]# ip route
default via 172.16.216.2 dev ens33 initrwnd 10
169.254.0.0/16 dev ens33 scope link metric 1002 initrwnd 10
172.16.216.0/24 dev ens33 proto kernel scope link src 172.16.216.188 initrwnd 10
# 一些系统的rwnd值:
# Linux 2.6.32 3*MSS (usually 4380)
# Linux 3.0.0 10*MSS (usually 14600)
# Windows NT 6.1 (Windows 7 or Server 2008 R2) 8192 ^ 2
-
优化 tcp time_wait ,减小time_wait 状态的链接。主动关闭的一方会出现time_wait状态。
time_wait 状态的链接要等待2个 MSL 的时间才会 close,会占用资源,尽可能避免链接进入 time_wait 状态。linux 里 MSL通常是30秒,2 个MSL 是1分钟,这个数值是硬编码在内核中的,除非从新编译内核,不然无法修改。注:MSL最长报文生命周期:Maximum Segment Lifetime,MSL 。
修改 fin_wait2 的值,减小 fin_wait2 的等待时间,超时之后会回收链接。
开启长链接:绝大可能是浏览器在开启长链接的状况下,接收到服务器断开链接的fin之后,会恢复一个 ack;而不会不发送本身这一端的 fin ,这样服务器一端就会等待 fin_timeout 时间后,回收链接。
若不开启长链接,服务器端关闭连接之后,连接的状态会从 fin_wait2 转换到 time_wait 。
还能够考虑促使客户端关闭连接,配置 keepalive_timeout 20s 10s; (nginx 配置),使客户端的超时小于服务器端,浏览器会先关闭连接,这样time_wait 状态就会在客户端,不过经过实验看出只有火狐浏览器支持,狐火浏览器会识别 Keep-Alive: timeout=time 这个参数,而别的浏览器不会。
不要设置回收 recycle=1 和 重用 reuse=1,NAT模式下会形成链接失败( SYN 包不会被响应)
time_wait 状态的链接被重用(reuse)的条件是以下2个之一:
1)初始序列号比time_wait状态的老链接最末的序列号大。
2)若是使用时间戳,那么新到来的链接的时间戳比老链接的时间戳大。
tcp_tw_reuse和tcp_tw_recycle要生效,必须 tcp_timestamps 是开启的,默认也是开启的。
-
参数优化
net.ipv4.tcp_max_syn_backlog = 1024 #SYN队列的长度,默认是1024,加大队列到8192或更大,可缓存更多等待的网络链接。
net.ipv4.tcp_max_tw_buckets = 180000 #保存 TIME_WAIT 状态的套接字的最大数量,一旦超过这个数,TIME_WAIT套接字将马上被清除,并发出警告。
net.ipv4.ip_local_port_range = 1024 65535 # 向外链接的端口范围。缺省值:32768到61000,能够扩大 1024 到 65535。
net.ipv4.tcp_syncookies = 1 #开启SYN Cookies,SYN等待队列溢出时,使用cookies来处理,可防范少许SYN***。
net.ipv4.tcp_retries2 = 15 #TCP失败重传的次数 ,默认15 ,能够调小一些,例如5。
还能够配置用于 TCP/IP 连接所使用的内存,配置总内存的话,单位是“页” ,具体的一个页的大小能够经过 getconf PAGE_SIZE 这个命令获取;读写所占用的内存单位是字节。
[root@www ~]# getconf PAGE_SIZE
4096
总内存
net.ipv4.tcp_mem = 93408 124544 186816
写(缓冲)
net.ipv4.tcp_wmem = 4096 16384 3985408
读(缓存)
net.ipv4.tcp_rmem = 4096 87380 3985408
[root@www ~]# cd /proc/sys/net/ipv4
[root@www ipv4]# cat tcp_fin_timeout
60
[root@www ~]# sysctl -a |grep timestamps
net.ipv4.tcp_timestamps = 1

TLS 协议优化
-
对TLS协议进行安全和速度调优
1.密钥交换
使用TLS最大的成本除了延迟之外(多了2次往返),就是用于安全参数协商的CPU密集型操做,也就是密钥交换(key exchange)。密钥交换的CPU消耗很大程度上取决于服务器选择的私钥算法、密钥长度和密钥交换算法。
破解密钥的难度取决于密钥的长度,密钥越长越安全。可是也要考虑加密与解密所消耗的计算资源。目前有两种私钥算法可使用:RSA和ECDSA。
如今RSA算法的密钥仍然大量存在,即便使用它进行密钥交换的时候不支持前向加密。可是RSA仍是能够用在身份认证上,当前RSA密钥算法推荐最小长度2048位,而且考虑升级到3072位(虽然升级后效率降低较多),随着RSA密钥的增加它开始变得愈来愈慢。ECDSA会快不少,愈来愈多的站点支持ECDSA,中等长度256位的ECDSA提供与3072位RSA同样的安全性,却有更好的性能。
推荐优先使用:ECDSA256_ECDHE256 与 RSA2048_ECDHE256 。

2. 证书
-
证书链
a、TLS握手的时候,服务器端会把证书链发送给客户端进行验证。
b、证书链尽量短。
c、证书链要完整。
d、尽可能使用椭圆曲线证书链。
-
证书吊销检查与OCSP服务
虽然证书吊销状态在不断变化,而且客户端(浏览器)对如何检查证书吊销差别很大,但做为服务器端,要作到尽量快的传递吊销信息。
- 使用带OCSP信息的证书。
OCSP被设计用于提供实时查询,容许客户端访问吊销信息。所以查询简短而快速(1个HTTP请求)。相比之下CRL是一个包含大量被吊销证书的列表。一些客户端只有当OCSP信息不可用的时候才下载CRL,在下载CRL的时候浏览器与服务器端的通讯将暂停,直到CRL下载完成,所消耗的时间可能会有几十秒。
- 选择具备快速且可靠的OCSP响应程序的CA
不一样CA之间的OCSP响应速度也不一样。缓慢和错误的OCSP响应程序会潜在地致使性能降低。在决定使用OCSP响应以后,要考察CA对OCSP响应的性能与正确性。另外一个选择CA的标准是看更新OCSP响应的速度,最好本身的证书一经颁发就加入到OCSP响应程序中,一旦出了安全隐患被吊销,OCSP响应也能迅速的更新。
[root@www ~]# openssl s_client -connect www.openssl.org:443 -status |grep -i ocsp
depth=1 C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3
verify error:num=20:unable to get local issuer certificate
OCSP response:
OCSP Response Data:
OCSP Response Status: successful (0x0)
Response Type: Basic OCSP Response

3. 会话恢复
TLS理解两种类型的握手:完整握手和简短握手。理论上完整握手只会在客户端与服务器创建TLS会话(TLS session)的时候进行一次。后续的链接,双方使用简短握手恢复以前协商的会话。简短握手由于不须要密钥交换与密钥生成等操做,因此会更快,而且少一次往返时间。
4.TLS的纪录协议形成的在网络传输中的额外开销
TLS协议的最小传输单位是一个TLS记录,它最多能够包含2^14=16384字节(16K)的数据。一条记录在不加密的状况下只有很小的开销;每一个记录以5字节的元数据开头,即内容类型(1字节)、协议版本(2字节)和数据长度(2字节)。流加密、分组加密和已验证密码套件加密后的TLS记录的额外开销。

尽可能避免发送小包数据。尽可能缓冲应用层数据避免额外的网络开销。
5.对称加密对CPU资源的消耗
加密操做有明显的CPU成本,成本由加密算法、加密模式和完整性校验算法三者决定。
6. TLS 记录的缓存延迟
TLS记录是TLS发送和接收数据的最小单位。TLS记录的大小与下一层TCP包的大小并不匹配,一个全尺寸的TLS记录16 KB须要被拆分红许多小的TCP包(大约12个),一般每一个小于1.5 KB(1.3KB)。整个TLS记录被分红小的TCP包后,各个小包会陆续到达,但在所有到齐以前是没法进行解密处理的。这是由于TLS记录一样是数据解密和完整性检验的最小单位。缓存延迟有时可能会比较大。
虽然经过TCP协议能够把丢失和延迟的数据包恢复,但仍然须要消耗一次往返。每一次额外的往返对于整个TLS记录都意味着延迟。 初始拥塞窗口另外一个触发额外往返的延迟是在链接初期发送大量数据致使初始拥塞窗口溢出。一旦拥塞窗口满了,发送端必须等待响应(1次往返),等到拥塞窗口增长再发送更多数据。
若是Web服务器支持TLS记录调整,就应该考虑将默认值(16 KB这么大的数值)改为更为合理的值,调整这个值由部署的密码套件和相应的传输开销决定,通常状况下设置成4 KB。若是将TLS记录大小设置为与TCP/IP包准确匹配,那就设置成1400字节左右,而后经过观察数据包逐步调整。IP报文理论上最大是65535个字节,是很大的,可是因为IP分片效果很很差,因此TCP在三次握手中互相得知对方的MSS(MTU减IP头部),不给IP层很大块的数据,避免IP数据报分片
例如,数据链路层最大传输单元(maximum transfer unit,MTU)是1500字节,那么能够预见:
1,500 bytes MTU 去除额外开销,所传数据 1379 —1419 bytes 。
-20 bytes IPv4 herder | - 40 bytes IPv6 header
- 32 bytes TCP header TCP 头部 最小是20字节可拓展的是40字节,最大为60字节
- 29 bytes TLS record | - 49 bytes TLS record
MSS 是1460 bytes :1460 - 32 - 29|49 = 1379 — 1399 bytes
首先MTU的值是变化的。虽然多数客户端继承以太网1500字节的限制,但也有一些协议支持更大的数据。好比,巨型帧(jumbo frame)容许多达9000字节。还有就是使用IPv4和IPv6(IPv4头是20字节,IPv6头是40字节)计算会略有不一样,所密码套件的变化也会影响这个数值。
另外一个问题是减少TLS记录的大小会增长传输开销,也就是吞吐量会降低。若是将TLS记录长度调大(最大16K),那么因为是加密的数据,得要全部的数据(全部的IP包)都到齐了,才会顺利的解密出明文,等待的时间会较长,吞吐率是上去了,响应的实时性就降低了。nginx上也有配置这个值的选项,只是不能动态调整。