nvidia-smi GPU性能状态(Performance State)含义

我正在使用Nvidia GTX Titan X进行深度学习实验。
我正在使用nvidia-smi来监视GPU的运行状态,但是提供的工具的性能(性能)状态没有意义。

我已经查看了nvidia-smi手册,它表示以下内容:

Performance State
The current performance state for the GPU. States range from P0 (maximum performance) to P12 (minimum performance).

如果不在GPU上运行任何进程(空闲状态),则GPU性能状态为p0。
但是,当运行一些计算繁重的过程时,状态变为p2。

我的问题是,为什么我的GPU闲置时处于P0状态,但是在执行繁重的计算任务时切换到P2? 不应该相反吗?

另外,有没有办法使我的GPU始终在P0状态下运行(最高性能)?


令人困惑。

但是,nvidia-smi手册是正确的。

当一个或一组GPU处于空闲状态时,在计算机上运行nvidia-smi的过程通常会使其中一个GPU退出空闲状态。这是由于该工具正在收集的信息-需要唤醒其中一个GPU。

此唤醒过程最初会将GPU置于P0状态(最高性能状态),但如果GPU空闲或不是特别忙碌,GPU驱动程序将监控该GPU,并最终开始降低性能状态以节省功耗。

另一方面,当GPU在工作负载下处于活动状态时,GPU驱动程序将根据其自身的启发式方法不断调整性能状态以提供最佳性能,同时使性能状态与实际工作负载相匹配。如果没有达到热或功率限制,则对于最活跃和最重的连续工作负载,性能状态应达到最高水平(P0)。

周期性很重但不连续的工作负载可能会导致GPU功耗状态在P0-P2级别附近波动。由于热(温度)或电源问题而"受限制"的GPU也可能会看到P状态降低。这种限制是显而易见的,并在nvidia-smi中单独报告,但是可能并非所有GPU类型都启用这种报告。

如果要在GPU上查看P0状态,我可以提供的最佳建议是运行短暂,繁重且连续的工作负载(例如,执行大型sgemm操作的工作),然后在该工作负载期间监视GPU。在这种情况下应该可以看到P0状态。

如果您使用的是正在使用cuDNN库的机器学习应用程序(例如Caffe),并且正在训练大型网络,则应该可以不时看到P0,因为cuDNN会执行类似于sgemm的操作通常情况下。

但是对于零星的工作负载,最常见的状态很有可能是P2。

要始终"强制" P0电源状态,可以尝试通过nvidia-smi工具尝试持久性模式和应用程序时钟。使用nvidia-smi --help或nvidia-smi的手册页了解选项。

尽管我认为这通常不适用于Tesla GPU,但除非特别设置更高的应用时钟,否则某些NVIDIA GPU可能会在计算负载下将自身限制为P2功耗状态。使用nvidia-smi -a命令查看可用于GPU的当前应用程序时钟,默认应用程序时钟和最大时钟。 (某些GPU(包括较旧的GPU)可能会在其中某些字段中显示N / A。这通常表明应用程序时钟无法通过nvidia-smi进行修改。)如果在计算负载期间卡似乎以P2状态运行,则可能通过将应用程序时钟增加到最大可用时钟(即最大时钟),可以将其增加到P0状态。使用nvidia-smi --help了解如何格式化命令以更改GPU上的应用程序时钟。修改应用程序时钟或启用可修改的应用程序时钟可能需要root / admin特权。设置GPU持久模式也可能是理想的或必要的。这将防止驱动程序在GPU活动期间"卸载",这可能导致驱动程序重新加载时重置应用程序时钟。

对于这种情况下受影响的卡,此默认行为是在计算负载下限制为P2,这是由GPU驱动程序设计的。

参考链接


ubuntu 21.10(GeForce GTX 3060 12GB)编译StyleGAN3

安装驱动:

# 清理全部的其他版本的nvidia驱动
$ sudo apt-get purge nvidia-*

$ sudo reboot

# nvidia-smi
$ sudo apt install nvidia-utils-470

# 驱动
$ sudo apt install nvidia-driver-470

# cuda 11.3 
$ sudo apt install nvidia-cuda-toolkit

$ sudo apt-get update

# 部分驱动可能会更新,需要执行更新,否则可能依旧不正常
$ sudo apt-get dist-upgrade

$ sudo apt-get autoremove

# 重启,否则部分驱动可能工作不正常
$ sudo reboot

Anaconda 上建立独立的编译环境,然后执行编译:

# wget https://repo.anaconda.com/archive/Anaconda3-2020.02-Linux-x86_64.sh
# 国内镜像下载
$ wget https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/Anaconda3-2021.11-Linux-x86_64.sh

$ bash Anaconda3-*-Linux-x86_64.sh

# 更新到最新版本
$ conda update -n base -c defaults conda

参考 Anaconda conda切换为国内源  加速下载。

编译配置StyleGAN3

$ sudo apt-get install git

$ git clone git@github.com:NVlabs/stylegan3.git

$ cd stylegan3

$ conda env create -f environment.yml

$ conda activate stylegan3

$ pip install psutil

# cudnn加速
$ conda install cudnn

# 目前测试 RTX 3060 12GB的情况下,batch建议是2,更高会报告OOM
# 并且当batch低于4的时候,需要同时指定 --mbstd-group=2
$ python train.py --outdir=~/training-runs --cfg=stylegan3-t --data=~/datasets/metfaces-1024x1024.zip --gpus=1 --batch=2 --mbstd-group=2 --gamma=8.2 --mirror=1 --metrics=none

如果报错:

Constructing networks...
Setting up PyTorch plugin "bias_act_plugin"... Failed!
Traceback (most recent call last):
  File "~/source/stylegan3/train.py", line 286, in <module>
    main() # pylint: disable=no-value-for-parameter
  File "~/anaconda3/envs/stylegan3/lib/python3.9/site-packages/click/core.py", line 1128, in __call__
    return self.main(*args, **kwargs)
  File "~/anaconda3/envs/stylegan3/lib/python3.9/site-packages/click/core.py", line 1053, in main
    rv = self.invoke(ctx)
  File "~/anaconda3/envs/stylegan3/lib/python3.9/site-packages/click/core.py", line 1395, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "~/anaconda3/envs/stylegan3/lib/python3.9/site-packages/click/core.py", line 754, in invoke
    return __callback(*args, **kwargs)
  File "~/source/stylegan3/train.py", line 281, in main
    launch_training(c=c, desc=desc, outdir=opts.outdir, dry_run=opts.dry_run)
  File "~/source/stylegan3/train.py", line 96, in launch_training
    subprocess_fn(rank=0, c=c, temp_dir=temp_dir)
  File "~/source/stylegan3/train.py", line 47, in subprocess_fn
    training_loop.training_loop(rank=rank, **c)
  File "~/source/stylegan3/training/training_loop.py", line 168, in training_loop
    img = misc.print_module_summary(G, [z, c])
  File "~/source/stylegan3/torch_utils/misc.py", line 216, in print_module_summary
    outputs = module(*inputs)
  File "~/anaconda3/envs/stylegan3/lib/python3.9/site-packages/torch/nn/modules/module.py", line 1071, in _call_impl
    result = forward_call(*input, **kwargs)
  File "~/source/stylegan3/training/networks_stylegan3.py", line 511, in forward
    ws = self.mapping(z, c, truncation_psi=truncation_psi, truncation_cutoff=truncation_cutoff, update_emas=update_emas)
  File "~/anaconda3/envs/stylegan3/lib/python3.9/site-packages/torch/nn/modules/module.py", line 1071, in _call_impl
    result = forward_call(*input, **kwargs)
  File "~/source/stylegan3/training/networks_stylegan3.py", line 151, in forward
    x = getattr(self, f'fc{idx}')(x)
  File "~/anaconda3/envs/stylegan3/lib/python3.9/site-packages/torch/nn/modules/module.py", line 1071, in _call_impl
    result = forward_call(*input, **kwargs)
  File "~/source/stylegan3/training/networks_stylegan3.py", line 100, in forward
    x = bias_act.bias_act(x, b, act=self.activation)
  File "~/source/stylegan3/torch_utils/ops/bias_act.py", line 84, in bias_act
    if impl == 'cuda' and x.device.type == 'cuda' and _init():
  File "~/source/stylegan3/torch_utils/ops/bias_act.py", line 41, in _init
    _plugin = custom_ops.get_plugin(
  File "~/source/stylegan3/torch_utils/custom_ops.py", line 136, in get_plugin
    torch.utils.cpp_extension.load(name=module_name, build_directory=cached_build_dir,
  File "~/anaconda3/envs/stylegan3/lib/python3.9/site-packages/torch/utils/cpp_extension.py", line 1080, in load
    return _jit_compile(
  File "~/anaconda3/envs/stylegan3/lib/python3.9/site-packages/torch/utils/cpp_extension.py", line 1318, in _jit_compile
    return _import_module_from_library(name, build_directory, is_python_module)
  File "~/anaconda3/envs/stylegan3/lib/python3.9/site-packages/torch/utils/cpp_extension.py", line 1701, in _import_module_from_library
    module = importlib.util.module_from_spec(spec)
  File "<frozen importlib._bootstrap>", line 565, in module_from_spec
  File "<frozen importlib._bootstrap_external>", line 1173, in create_module
  File "<frozen importlib._bootstrap>", line 228, in _call_with_frames_removed
ImportError: ~/anaconda3/envs/stylegan3/lib/python3.9/site-packages/torch/lib/../../../../libstdc++.so.6: version `GLIBCXX_3.4.29' not found (required by ~/.cache/torch_extensions/bias_act_plugin/3cb576a0039689487cfba59279dd6d46-nvidia-geforce-rtx-3060/bias_act_plugin.so)

上述报错产生的原因是在 Anaconda 下载的包,在进行编译的时候,使用了高版本的 libstdc++.so。而运行时却使用了Anaconda 环境里低版本的 libstdc++.so 导致报错。

了解了原因,解决方法就比较简单了,可以手工升级 Anaconda 环境下的 libstdc++.so 动态库。

如下:

$ conda activate stylegan3

$ conda install cmake

$ conda install make

# 关键升级命令,更新当前项目里面的 libstdc++.so
$ conda install -c conda-forge libstdcxx-ng

# 删除上次失败时候的编译缓存
$ rm -rf ~/.cache

# 目前测试 RTX 3060 12GB的情况下,batch建议是2,更高会报告OOM
# 当batch=4的时候会在第11天的时候报告OOM
# 并且当batch低于4的时候,需要同时指定 --mbstd-group=2
$ python train.py --outdir=~/training-runs --cfg=stylegan3-t --data=~/datasets/metfaces-1024x1024.zip --gpus=1 --batch=2 --mbstd-group=2 --gamma=8.2 --mirror=1 --metrics=none

目前测试发现,当batch=4的时候会在第11天的时候报告OOM,如下:

tick 444   kimg 1776.0   time 11d 17h 14m  sec/tick 2292.6  sec/kimg 573.16  maintenance 0.2    cpumem 5.40   gpumem 7.69   reserved 10.03  augment 0.344
Traceback (most recent call last):
  File "~/source/stylegan3/train.py", line 286, in <module>
    main() # pylint: disable=no-value-for-parameter
  File "~/anaconda3/envs/stylegan3/lib/python3.9/site-packages/click/core.py", line 1128, in __call__
    return self.main(*args, **kwargs)
  File "~/anaconda3/envs/stylegan3/lib/python3.9/site-packages/click/core.py", line 1053, in main
    rv = self.invoke(ctx)
  File "~/anaconda3/envs/stylegan3/lib/python3.9/site-packages/click/core.py", line 1395, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "~/anaconda3/envs/stylegan3/lib/python3.9/site-packages/click/core.py", line 754, in invoke
    return __callback(*args, **kwargs)
  File "~/source/stylegan3/train.py", line 281, in main
    launch_training(c=c, desc=desc, outdir=opts.outdir, dry_run=opts.dry_run)
  File "~/source/stylegan3/train.py", line 96, in launch_training
    subprocess_fn(rank=0, c=c, temp_dir=temp_dir)
  File "~/source/stylegan3/train.py", line 47, in subprocess_fn
    training_loop.training_loop(rank=rank, **c)
  File "~/source/stylegan3/training/training_loop.py", line 278, in training_loop
    loss.accumulate_gradients(phase=phase.name, real_img=real_img, real_c=real_c, gen_z=gen_z, gen_c=gen_c, gain=phase.interval, cur_nimg=cur_nimg)
  File "~/source/stylegan3/training/loss.py", line 81, in accumulate_gradients
    loss_Gmain.mean().mul(gain).backward()
  File "~/anaconda3/envs/stylegan3/lib/python3.9/site-packages/torch/_tensor.py", line 255, in backward
    torch.autograd.backward(self, gradient, retain_graph, create_graph, inputs=inputs)
  File "~/anaconda3/envs/stylegan3/lib/python3.9/site-packages/torch/autograd/__init__.py", line 147, in backward
    Variable._execution_engine.run_backward(
  File "~/anaconda3/envs/stylegan3/lib/python3.9/site-packages/torch/autograd/function.py", line 87, in apply
    return self._forward_cls.backward(self, *args)  # type: ignore[attr-defined]
  File "~/source/stylegan3/torch_utils/ops/grid_sample_gradfix.py", line 50, in backward
    grad_input, grad_grid = _GridSample2dBackward.apply(grad_output, input, grid)
  File "~/source/stylegan3/torch_utils/ops/grid_sample_gradfix.py", line 59, in forward
    grad_input, grad_grid = op(grad_output, input, grid, 0, 0, False)
RuntimeError: CUDA out of memory. Tried to allocate 1.39 GiB (GPU 0; 11.76 GiB total capacity; 7.06 GiB already allocated; 443.88 MiB free; 10.02 GiB reserved in total by PyTorch)

参考链接


VNC还是RDP? 云上的远程桌面究竟该如何选

这里说的VNC是什么?

简单来说,所谓的 VNC(Virtual Network Computing)是一种图形化的桌面共享系统,它使用远程帧缓冲协议 (RFB) 来远程控制另一台计算机。它将键盘和鼠标事件从一台计算机传输到另一台计算机,通过网络向另一个方向转发图形屏幕更新。

类似这样的技术VNC不是绝无仅有,但VNC 的流行和普及却因为其具有的过人之处 –

  • VNC是平台无关的—— 有多种客户端和服务器的实现,几乎涵盖了所有的主流平台。甚至一些VNC的实现被称“无客户端”,这是因为不需要安装插件或客户端软件而,而是依靠HTML5技术,只需要一个浏览器就可以访问远程桌面了。
  • VNC是开源的—— VNC最初是在英国剑桥的Olivetti & Oracle研究实验室开发的。原始的VNC源代码和许多现代的衍生品在GNU通用公共许可证下是开放源码的。
  • VNC的协议是简单、普适的—— VNC使用的是 RFB(Remote Framebuffer) 协议。这是一个开放且简单的协议。因为它在framebuffer级别工作,协议是基于像素的所以适用于所有窗口系统和应用程序,包括Microsoft Windows、macOS和X Window系统。这个协议的性能表现是很出色的。

说起来满满的都是优点,那么

访问云上的实例,为什么不选择VNC呢?


VNC 的优点很多,很多场景下都能看到VNC。例如,访问树莓派的桌面,对 headless 服务器的管理等等。但是,对于云上实例的远程图形化的访问VNC却不是好的选择。考虑到我们的使用场景是通过互联网来访问云上的主机,这就引出了否定 VNC 最主要的原因– 安全性。

默认情况下,RFB并不是一个安全的协议。虽然这个协议下密码不以明文方式发送,但如果能从网络中嗅出加密密钥和已编码的密码,还是有可能破解成功的。因此,建议密码至少有8个字符。另一方面,VNC的一些版本也有8个字符的限制; 如果发送的密码超过8个字符,则删除多余的字符,并将截断的字符串与密码进行比较。

在VNC生态系统中,”Big Four” 指的是LibVNC、UltraVNC、Tight VNC和TurboVNC 这四家提供VNC 产品的厂商。2019年,Kaspersky Lab 的研究人员对这四家公司进行了审计,以了解它们的安全性。他们的发现是令人失望的。总的来说,研究人员发现这四个程序的客户端和服务器部分共有37个严重缺陷。其中22个在 UltraVNC,另外10个在 LibVNC, 4个在 TightVNC,还有一个在 TurboVNC,但这是一个严重的漏洞,它会让攻击者在服务器端远程执行代码。

有人会建议通过 SSH 或 VPN 连接进行 VNC 的隧道化,通过这种方法增加一个具有更强加密功能的额外安全层。但是这种方法并不完美,除了增加了复杂性也容易引起其它的一些安全问题,例如中间人攻击等。

否定一个技术是简单的,但我们是否有替代技术呢?答案就是 Remote Desktop Protocol (RDP)。

那么,RDP又是什么?

有过 Windows 使用经验的人对于远程桌面(Remote Desktop Protocol ,RDP)一定不会陌生。RDP 是由微软公司开发的一种专有协议,它为用户提供了通过网络连接到另一台计算机的图形界面。在使用上,用户需要使用 RDP 客户端软件,而在远程另一台计算机则需要运行 RDP 服务器软件。

微软的Windows、Linux、macOS、iOS、Android等操作系统都有客户端。Windows操作系统内置RDP服务器;Linux与 macOS 可以安装一个 RDP 服务器。缺省配置下,服务器监听 TCP 端口 3389 和 UDP 端口 3389。

微软目前把他们的官方RDP客户端软件称为Remote Desktop Connection,以前叫做 “Terminal Services client”

与VNC 相比,RDP的安全性有很大的提升。主要的安全特性包括了:

  • 128位加密,使用RC4加密算法(版本6加入)
  • 提供了对TLS的支持(版本2加入)

此外,正如前面提到的VNC协议是基于像素的。尽管这带来了极大的灵活性,可以显示任何类型的桌面,但它的效率往往不如那些更好地理解底层图形布局(例 如: X11)或桌面(例如:RDP )的解决方案。这些协议以更简单的形式(例如:打开窗口)发送图形原语或高级命令,而 VNC 的 RFB 协议尽管支持压缩但只能是发送原始像素数据。

如何使用RDP?

在 Windows 环境下使用RDP是再简单不过的事情。我想谈的是在 Linux 环境下RDP 的安装部署与使用。虽然微软公司没有为 Linux 提供 RDP 的软件,但是我们可以使用开源的xRDP,这是RDP协议在Linux平台 的实现。

xRDP是一个开源的远程桌面协议服务器,它用来实现Linux接受来自 Windows、Mac 或者 Linux 远程桌面客户端或的连接。这意味着你不需要在我们所使用的 Windows 或 macOS 机器上安装额外的第三方应用程序。

Linux 安装 xRDP的方法如下。这里我以 Ubuntu 20.04 为例 –

  • 安装Gnome

GNOME是一个Linux 操作系统下的桌面环境,完全由免费和开源软件组成。缺省情况下在EC2上安装的Linux 操作系统都不包含Gnome,需要额外安装。Ubuntu 缺省的桌面窗口管理器就是Gnome,用以下命令安装。

$ sudo apt update

$ sudo apt install ubuntu-desktop

安装 xRDP除了Gnome 以外,我们还可以选择 LXDE、Xface 等等。相比之下,LXDE 是轻量级的窗口管理器,Xface 则具备类似 Windows 界面的风格。如果不在乎额外增加的大约 5GB 的磁盘存储的开销,我还是推荐使用Gnome。原因在于这与我们本地使用的 Ubuntu 具有一致性的体验。

$ sudo apt install -y xrdp
$ sudo systemctl status xrdp

输出结果如下:安装完成后,xRDP服务将自动启动。可以通过这个命令来检查其状态:

  • 接下来,要为Linux的用户(ubuntu)设置登录密码
$ sudo passwd ubuntu 

$ sudo adduser ubuntu ssl-cert

此外,如果我们的 EC2 实例绑定了Elastics IP 并且拥有自己的域名,我推荐使用Let’s Encrypt 发出的免费SSL证书替换缺省的自签发的证书。需要注意的一点,Let’s Encrypt 证书的有效期是90天。可以考虑配置crontab 使用cerbot 自动更新证书。这里设置的密码将被用来登录到目标的 EC2 实例,出于安全的理由务必使其符合密码安全的策略。第二条命令是将 用户ubuntu 加入到 ssl-cert 用户组中。这是因为默认情况下,xRDP 使用的是自签发的证书,这个证书保存在 /etc/ssl/private/ssl-cert-snakeoil目录下。证书的密钥文件只能由 “ssl-cert” 用户组的成员读取。

  • 修改安全组 RDP 协议缺省使用3389端口。务必记得在EC2的安全组中打开TCP与UDP在这个端口上的访问许可。

  • 登录

在这里,username 输入ubuntu,password 输入刚刚修改过的用户密码。成功登录之后熟悉的Ubuntu 桌面就会立刻出现在眼前。

按照我的体验,网络延迟在35ms以内xRDP 的与本地Linux 桌面的操作体验几乎没有差别。实测之下,通过我所使用的100M联通宽带访问AWS 中国(北京)区域的EC2 实例,网络延迟大约在5ms左右。

单以鼠标、键盘的使用体验而论,本地桌面与远程桌面的差别已经微乎其微了,我终于可以放心的将许多工作移到云端。无论是通过我的笔记本电脑、iPad 甚至是一台树莓派都能够让我接入顺利的接入远程桌面。如此一来,开发在云端岂不是再简单不过的事情了。

参考链接


解决KiCAD(系统macOS Big Sur)关联封装(Footprint)报错“No PCB footprint libraries are listed in the current footprint library table.”

最近在把macOS Big Sur系统上的KiCAD5.x升级到6.x之后,新建的项目在进行关联封装操作的时候,报错

No PCB footprint libraries are listed in the current footprint library table.

具体如下图:

造成这个问题的原因是KiCAD 6.x版本的全局封装库默认存储位置发生了变动,从低版本升级上来,还是会从以前的配置文件中读取原来配置的路径信息,导致找不到新版本的全局封装库路径。

解决方法是删除低版本配置的封装库相关的配置信息即可(删除之前,先关闭KiCAD软件,否则删除无效)。

macOS系统:

$ rm -rf ~/Library/Preferences/kicad

Windows系统:

$ del C:\Users\ …\AppData\Roaming\kicad

Linux系统:

$ rm -rf ~/.config/kicad

完成后,重启KiCAD软件即可。

参考链接


[华硕主板] 支持ECC内存的AMD Ryzen™处理器列表

下表列出了支持带ECC功能内存模组的Ryzen™处理器的列表;它们的官方名称;以及是否支持B550X570系列。

请注意,当涉及到APUsRyzen 3000/4000 G系列),只有PRO处理器(例如Ryzen 3 PRO 3200G)支持ECC内存。

官方名称

支持B550系列

支持X570系列

ECC功能

UDIMM ECC

REG ECC

AMD Ryzen™ 5000 Series Processors

V

V

支持

V

 

AMD Ryzen™ 4000 G-Series Processors

V

V

只有PRO支持

V

 

AMD Ryzen™ 3000 Series Processors

V

V

支持

V

 

AMD Ryzen™ 2000 Series Processors

 

V

支持

V

 

AMD Ryzen™ 3000 G-Series Processors

 

V

只有PRO支持

V

 

注意,Ryzen™处理器只支持二手UDIMM ECC,不支持更便宜的二手REG ECC

这个问题简单解释一下,新的REG ECC从性能到价格都是远远高于UDIMM ECC的,但是REG ECC不能用在家用主板上,导致大量淘汰的二手内存需求量不高,因而价格偏便宜。

继续阅读[华硕主板] 支持ECC内存的AMD Ryzen™处理器列表

运维中tcp_tw_recycle net.ipv4.tcp_timestamps引发的坑

NAT环境下,遇到因为tcp_tw_recycle=1和net.ipv4.tcp_timestamps=1引起 Nginx upstream timed out 后,一直没在遇见,今天在朋友的阿里云环境下又重新再一次出现;因此在这炒一次冷饭,让运维新手或者刚上云的朋友大概了解一下,避免再一次采坑。

故障情况:

阿里云账号A的A机房,内网里面部署两台Nginx,通过网络出口(NAT),代理用户访问到阿里云账号B的B机房服务。A机房的Nginx出现:upstream timed out 。

故障的诱因是:net.ipv4.tcp_timestamps=1

抓包图:

注意,这个选项生效的前提是,报文的发出方必须在TCP头部的可选项中增加时间戳字段,否则这个设置是不生效的。

直接上当年的笔记:

先看看TCP IP 对tw的一些解析:
RFC 1323里有这样的定义:

An additional mechanism could be added to the TCP, a per-host
cache of the last timestamp received from any connection.
This value could then be used in the PAWS mechanism to reject
old duplicate segments from earlier incarnations of the
connection, if the timestamp clock can be guaranteed to have
ticked at least once since the old connection was open.  This
would require that the TIME-WAIT delay plus the RTT together
must be at least one tick of the sender's timestamp clock.
Such an extension is not part of the proposal of this RFC.

大概的中文意思就是:TCP协议中有一种机制,缓存了每个主机(即ip)过来的连接最新的timestamp值。这个缓存的值可以用于PAWS(Protect Against Wrapped Sequence numbers,是一个简单的防止重复报文的机制)中,来丢弃当前连接中可能的旧的重复报文。而Linux实现这个机制的方法就是同时启用net.ipv4.tcp_timestamps和net.ipv4.tcp_tw_recycle 这两个选项。
这种机制在 客户端-服务器 一对一的时候,没有任何问题,但是当服务器在负载均衡器后面时,由于负载均衡器不会修改包内部的timestamp值,而互联网上的机器又不可能保持时间的一致性,再加上负载均衡是会重复多次使用同一个tcp端口向内部服务器发起连接的,就会导致什么情况呢:

负载均衡通过某个端口向内部的某台服务器发起连接,源地址为负载均衡的内部地址——同一假如恰巧先后两次连接源端口相同,这台服务器先后收到两个包,第一个包的timestamp被服务器保存着,第二个包又来了,一对比,发现第二个包的timestamp比第一个还老——客户端时间不一致。服务器基于PAWS,判断第二个包是重复报文,丢弃之。

反映出来的情况就是在服务器上抓包,发现有SYN包,但服务器就是不回ACK包,因为SYN包已经被丢弃了。为了验证这一结果,可以执行netstat -s | grep timestamp 命令,看输出里面passive connections rejected by timestamp 一项的数字变化。

tcp_ipv4.c中,在接收SYN之前,如果符合如下两个条件,需要检查peer是不是proven,即per-host PAWS检查:

  • 收到的报文有TCP option timestamp时间戳
  • 本机开启了内核参数net.ipv4.tcp_tw_recycle
		/* VJ's idea. We save last timestamp seen
		 * from the destination in peer table, when entering
		 * state TIME-WAIT, and check against it before
		 * accepting new connection request.
		 *
		 * If "isn" is not zero, this request hit alive
		 * timewait bucket, so that all the necessary checks
		 * are made in the function processing timewait state.
		 */
		if (tmp_opt.saw_tstamp &&
		    tcp_death_row.sysctl_tw_recycle &&
		    (dst = inet_csk_route_req(sk, &fl4, req)) != NULL &&
		    fl4.daddr == saddr) {
			if (!tcp_peer_is_proven(req, dst, true)) {
				NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_PAWSPASSIVEREJECTED);
				goto drop_and_release;
			}
		}

解决办法:

tcp_tw_recycle=0 或(和)net.ipv4.tcp_timestamps=0同时从4.10内核开始,官方修改了时间戳的生成机制,所以导致 tcp_tw_recycle 和新时间戳机制工作在一起不那么友好,同时 tcp_tw_recycle 帮助也不那么的大。

此处的时间戳并不是我们通常意义上面的绝对时间,而是一个相对时间。很多情况下,我们是没法保证时间戳单调递增的,比如业务服务器之前部署了NAT,LVS等情况。相信很多小伙伴上班的公司大概率实用实用各种公有云,而各种公有云的 LVS 网关都是 FullNAT 。所以可能导致在高并发的情况下,莫名其妙的 TCP 建联不是那么顺畅或者丢连接。

而这也是很多优化文章中并没有提及的一点,大部分文章都是简单的推荐将net.ipv4.tcp_tw_recycle设置为1,却忽略了该选项的局限性,最终造成严重的后果(比如我们之前就遇到过部署在nat后端的业务网站有的用户访问没有问题,但有的用户就是打不开网页)。

参考链接