最新资讯

  • 将 Linux 迁移到微软 Azure(二)

将 Linux 迁移到微软 Azure(二)

2025-06-15 01:01:01 3 阅读

原文:zh.annas-archive.org/md5/DFC4E6F489A560394D390945DB597424

译者:飞龙

协议:CC BY-NC-SA 4.0

第四章:故障排除性能问题

在第三章中,我们通过使用第一章中介绍的故障排除方法论,以及第二章中找到的几个基本故障排除命令和资源,来解决了 Web 应用程序的问题。

性能问题

在本章中,我们将继续在第三章中涵盖的情景,我们是一家新公司的新系统管理员。当我们到达开始我们的一天时,一位同事要求我们调查一个服务器变慢的问题。

在要求详细信息时,我们的同事只能提供主机名和被认为“慢”的服务器的 IP。我们的同行提到一个用户报告了这个问题,而用户并没有提供太多细节。

在这种情况下,与第三章中讨论的情况不同,我们没有太多信息可以开始。似乎我们也无法向用户提出故障排除问题。作为系统管理员,需要用很少的信息来排除问题并不罕见。事实上,这种类型的情况非常普遍。

它很慢

“它很慢”很难排除故障。关于服务器或服务变慢的投诉最大的问题是,“慢”是相对于遇到问题的用户而言的。

在处理任何关于性能的投诉时,重要的区别是环境设计的基准。在某些环境中,系统以 30%的 CPU 利用率运行可能是一种常规活动,而其他环境可能会保持系统以 10%的 CPU 利用率运行,30%的利用率会表示问题。

在排除故障和调查性能问题时,重要的是回顾系统的历史性能指标,以确保您对收集到的测量值有上下文。这将有助于确定当前系统利用率是否符合预期或异常。

性能

一般来说,性能问题可以分为五个领域:

  • 应用程序

  • CPU

  • 内存

  • 磁盘

  • 网络

任何一个领域的瓶颈通常也会影响其他领域;因此,了解每个领域是一个好主意。通过了解如何访问和交互每个资源,您将能够找到消耗多个资源的问题的根本原因。

由于报告的问题没有包括任何性能问题的细节,我们将探索和了解每个领域。完成后,我们将查看收集的数据并查看历史统计数据,以确定性能是否符合预期,或者系统性能是否真的下降了。

应用程序

在创建性能类别列表时,我按照我经常看到的领域进行了排序。每个环境都是不同的,但根据我的经验,应用程序通常是性能问题的主要来源。

虽然本章旨在涵盖性能问题,第九章,“使用系统工具排除应用程序”专门讨论了使用系统工具排除应用程序问题,包括性能问题。在本章中,我们将假设我们的问题与应用程序无关,并专注于系统性能。

CPU

CPU 是一个非常常见的性能瓶颈。有时,问题严格基于 CPU,而其他时候,增加的 CPU 使用率是另一个问题的症状。

调查 CPU 利用率最常见的命令是 top 命令。这个命令的主要作用是识别进程的 CPU 利用率。在第二章,“故障排除命令和有用信息的来源”中,我们讨论了使用ps命令进行这种活动。在本节中,我们将使用 top 和 ps 来调查 CPU 利用率,以解决我们的速度慢的问题。

Top – 查看所有内容的单个命令

top命令是系统管理员和用户运行的第一批命令之一,用于查看整体系统性能。原因在于 top 不仅显示了负载平均值、CPU 和内存的详细情况,还显示了利用这些资源的进程的排序列表。

top最好的部分是,当不带任何标志运行时,这些详细信息每 3 秒更新一次。

以下是不带任何标志运行时top输出的示例。

top - 17:40:43 up  4:07,  2 users,  load average: 0.32, 0.43, 0.44
Tasks: 100 total,   2 running,  98 sleeping,   0 stopped,   0 zombie
%Cpu(s): 37.3 us,  0.7 sy,  0.0 ni, 62.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem:    469408 total,   228112 used,   241296 free,      764 buffers
KiB Swap:  1081340 total,        0 used,  1081340 free.    95332 cached Mem

 PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
 3023 vagrant   20   0    7396    720    504 S 37.6  0.2  91:08.04 lookbusy
 11 root      20   0       0      0      0 R  0.3  0.0   0:13.28 rcuos/0
 682 root      20   0  322752   1072    772 S  0.3  0.2   0:05.60 VBoxService
 1 root      20   0   50784   7256   2500 S  0.0  1.5   0:01.39 systemd
 2 root      20   0       0      0      0 S  0.0  0.0   0:00.00 kthreadd
 3 root      20   0       0      0      0 S  0.0  0.0   0:00.24 ksoftirqd/0
 5 root       0 -20       0      0      0 S  0.0  0.0   0:00.00 kworker/0:0H
 6 root      20   0       0      0      0 S  0.0  0.0   0:00.04 kworker/u2:0
 7 root      rt   0       0      0      0 S  0.0  0.0   0:00.00 migration/0
 8 root      20   0       0      0      0 S  0.0  0.0   0:00.00 rcu_bh
 9 root      20   0       0      0      0 S  0.0  0.0   0:00.00 rcuob/0
 10 root      20   0       0      0      0 S  0.0  0.0   0:05.44 rcu_sched

top的默认输出中显示了相当多的信息。在本节中,我们将专注于 CPU 利用率信息。

%Cpu(s): 37.3 us,  0.7 sy,  0.0 ni, 62.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st

top命令输出的第一部分显示了当前 CPU 利用率的详细情况。列表中的每一项代表了 CPU 的不同使用方式。为了更好地理解输出结果,让我们来看看每个数值的含义:

  • us – User:这个数字表示用户模式中进程所消耗的 CPU 百分比。在这种模式下,应用程序无法访问底层硬件,必须使用系统 API(也称为系统调用)来执行特权操作。在执行这些系统调用时,执行将成为系统 CPU 利用率的一部分。

  • sy – System:这个数字表示内核模式执行所消耗的 CPU 百分比。在这种模式下,系统可以直接访问底层硬件;这种模式通常保留给受信任的操作系统进程。

  • ni – Nice user processes:这个数字表示由设置了 nice 值的用户进程所消耗的 CPU 时间百分比。us%值特指那些未修改过 niceness 值的进程。

  • id – Idle:这个数字表示 CPU 空闲的时间百分比。基本上,它是 CPU 未被利用的时间。

  • wa – Wait:这个数字表示 CPU 等待的时间百分比。当有很多进程在等待 I/O 设备时,这个值通常很高。I/O 等待状态不仅指硬盘,而是指所有 I/O 设备,包括硬盘。

  • hi – Hardware interrupts:这个数字表示由硬件中断所消耗的 CPU 时间百分比。硬件中断是来自系统硬件(如硬盘或网络设备)的信号,发送给 CPU。这些中断表示有事件需要 CPU 时间。

  • si - 软件中断:这个数字是被软件中断消耗的 CPU 时间的百分比。软件中断类似于硬件中断;但是,它们是由运行进程发送给内核的信号触发的。

  • st - 被窃取:这个数字特别适用于作为虚拟机运行的 Linux 系统。这个数字是被主机从这台机器上窃取的 CPU 时间的百分比。当主机机器本身遇到 CPU 争用时,通常会出现这种情况。在一些云环境中,这也可能发生,作为强制执行资源限制的一种方法。

我之前提到top的输出默认每 3 秒刷新一次。CPU 百分比行也每 3 秒刷新一次;top将显示自上次刷新间隔以来每个状态的 CPU 时间百分比。

这个输出告诉我们关于我们的问题的什么?

如果我们回顾之前top命令的输出,我们可以对这个系统了解很多。

%Cpu(s): 37.3 us,  0.7 sy,  0.0 ni, 62.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st

从前面的输出中,我们可以看到 CPU 时间的37.3%被用户模式下的进程消耗。另外0.7%的 CPU 时间被内核执行模式下的进程使用;这是基于ussy的值。id值告诉我们剩下的 CPU 没有被利用,这意味着总体上,这台服务器上有充足的 CPU 可用。

top命令显示的另一个事实是 CPU 时间没有花在等待 I/O 上。我们可以从wa值为0.0看出。这很重要,因为它告诉我们报告的性能问题不太可能是由于高 I/O。在本章后面,当我们开始探索磁盘性能时,我们将深入探讨 I/O 等待。

来自 top 的单个进程

top命令输出中的 CPU 行是整个服务器的摘要,但 top 还包括单个进程的 CPU 利用率。为了更清晰地聚焦,我们可以再次执行 top,但这次,让我们专注于正在运行的top进程。

$ top -n 1
top - 15:46:52 up  3:21,  2 users,  load average: 1.03, 1.11, 1.06
Tasks: 108 total,   3 running, 105 sleeping,   0 stopped,   0 zombie
%Cpu(s): 34.1 us,  0.7 sy,  0.0 ni, 65.1 id,  0.0 wa,  0.0 hi,  0.1 si,  0.0 st
KiB Mem:    502060 total,   220284 used,   281776 free,      764 buffers
KiB Swap:  1081340 total,        0 used,  1081340 free.    92940 cached Mem

 PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
 3001 vagrant   20   0    7396    720    504 R  98.4  0.1 121:08.67 lookbusy
 3002 vagrant   20   0    7396    720    504 S   6.6  0.1  19:05.12 lookbusy
 1 root      20   0   50780   7264   2508 S   0.0  1.4   0:01.69 systemd
 2 root      20   0       0      0      0 S   0.0  0.0   0:00.01 kthreadd
 3 root      20   0       0      0      0 S   0.0  0.0   0:00.97 ksoftirqd/0
 5 root       0 -20       0      0      0 S   0.0  0.0   0:00.00 kworker/0:0H
 6 root      20   0       0      0      0 S   0.0  0.0   0:00.00 kworker/u4:0
 7 root      rt   0       0      0      0 S   0.0  0.0   0:00.67 migration/0

这次执行top命令时,使用了-n(数字)标志。这个标志告诉top只刷新指定次数,这里是 1 次。在尝试捕获top的输出时,这个技巧可能会有所帮助。

如果我们回顾上面top命令的输出,我们会看到一些非常有趣的东西。

 PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
 3001 vagrant   20   0    7396    720    504 R  98.4  0.1 121:08.67 lookbusy

默认情况下,top命令按照进程利用的 CPU 百分比对进程进行排序。这意味着列表中的第一个进程是在该时间间隔内消耗 CPU 最多的进程。

如果我们看一下进程 ID 为3001的顶部进程,我们会发现它正在使用 CPU 时间的98.4%。然而,根据 top 命令的系统范围 CPU 统计数据,CPU 时间的65.1%处于空闲状态。这种情况实际上是许多系统管理员困惑的常见原因。

%Cpu(s): 34.1 us,  0.7 sy,  0.0 ni, 65.1 id,  0.0 wa,  0.0 hi,  0.1 si,  0.0 st

一个单个进程如何使用几乎 100%的 CPU 时间,而系统本身显示 CPU 时间的 65%是空闲的?答案其实很简单;当top在其标题中显示 CPU 利用率时,比例是基于整个系统的。然而,对于单个进程,CPU 利用率的比例是针对一个 CPU 的。这意味着我们的进程 3001 实际上几乎使用了一个完整的 CPU,而我们的系统很可能有多个 CPU。

通常会看到能够利用多个 CPU 的进程显示的百分比高于 100%。例如,完全利用三个 CPU 的进程将显示 300%。这也可能会让不熟悉top命令服务器总体和每个进程输出差异的用户感到困惑。

确定可用 CPU 数量

先前,我们确定了这个系统必须有多个可用的 CPU。我们没有确定的是有多少个。确定可用 CPU 数量的最简单方法是简单地读取/proc/cpuinfo文件。

# cat /proc/cpuinfo
processor  : 0
vendor_id  : GenuineIntel
cpu family  : 6
model    : 58
model name  : Intel(R) Core(TM) i7-3615QM CPU @ 2.30GHz
stepping  : 9
microcode  : 0x19
cpu MHz    : 2348.850
cache size  : 6144 KB
physical id  : 0
siblings  : 2
core id    : 0
cpu cores  : 2
apicid    : 0
initial apicid  : 0
fpu    : yes
fpu_exception  : yes
cpuid level  : 5
wp    : yes
flags    : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx rdtscp lm constant_tsc rep_good nopl pni ssse3 lahf_lm
bogomips  : 4697.70
clflush size  : 64
cache_alignment  : 64
address sizes  : 36 bits physical, 48 bits virtual
power management:

processor  : 1
vendor_id  : GenuineIntel
cpu family  : 6
model    : 58
model name  : Intel(R) Core(TM) i7-3615QM CPU @ 2.30GHz
stepping  : 9
microcode  : 0x19
cpu MHz    : 2348.850
cache size  : 6144 KB
physical id  : 0
siblings  : 2
core id    : 1
cpu cores  : 2
apicid    : 1
initial apicid  : 1
fpu    : yes
fpu_exception  : yes
cpuid level  : 5
wp    : yes
flags    : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx rdtscp lm constant_tsc rep_good nopl pni ssse3 lahf_lm
bogomips  : 4697.70
clflush size  : 64
cache_alignment  : 64
address sizes  : 36 bits physical, 48 bits virtual
power management:

/proc/cpuinfo文件包含了关于系统可用 CPU 的大量有用信息。它显示了 CPU 的类型到型号,可用的标志,CPU 的速度,最重要的是可用的 CPU 数量。

系统中每个可用的 CPU 都将在cpuinfo文件中列出。这意味着您可以简单地在cpuinfo文件中计算处理器的数量,以确定服务器可用的 CPU 数量。

从上面的例子中,我们可以确定这台服务器有 2 个可用的 CPU。

线程和核心

使用cpuinfo来确定可用 CPU 数量的一个有趣的注意事项是,当使用具有多个核心并且是超线程的 CPU 时,细节有点误导。cpuinfo文件将 CPU 上的核心和线程都报告为它可以利用的处理器。这意味着即使您的系统上安装了一个物理芯片,如果该芯片是一个四核超线程 CPU,cpuinfo文件将显示八个处理器。

lscpu – 查看 CPU 信息的另一种方法

虽然/proc/cpuinfo是许多管理员和用户用来确定 CPU 信息的方法;在基于 RHEL 的发行版上,还有另一条命令也会显示这些信息。

$ lscpu
Architecture:          x86_64
CPU op-mode(s):        32-bit, 64-bit
Byte Order:            Little Endian
CPU(s):                2
On-line CPU(s) list:   0,1
Thread(s) per core:    1
Core(s) per socket:    2
Socket(s):             1
NUMA node(s):          1
Vendor ID:             GenuineIntel
CPU family:            6
Model:                 58
Model name:            Intel(R) Core(TM) i7-3615QM CPU @ 2.30GHz
Stepping:              9
CPU MHz:               2348.850
BogoMIPS:              4697.70
L1d cache:             32K
L1d cache:             32K
L2d cache:             6144K
NUMA node0 CPU(s):     0,1

/proc/cpuinfolscpu命令之间的一个区别是,lscpu使得很容易识别核心、插槽和线程的数量。从/proc/cpuinfo文件中识别这些信息通常会有点困难。

ps – 通过 ps 更深入地查看单个进程

虽然top命令可以用来查看单个进程,但我个人认为ps命令更适合用于调查运行中的进程。在第二章中,我们介绍了ps命令以及它如何用于查看运行进程的许多不同方面。

在本章中,我们将使用ps命令更深入地查看我们用top命令确定为利用最多 CPU 时间的进程3001

$ ps -lf 3001
F S UID        PID  PPID  C PRI  NI ADDR SZ WCHAN  STIME TTY TIME CMD
1 S vagrant   3001  3000 73  80   0 -  1849 hrtime 01:34 pts/1 892:23 lookbusy --cpu-mode curve --cpu-curve-peak 14h -c 20-80

在第二章中,我们讨论了使用ps命令来显示运行中的进程。在前面的例子中,我们指定了两个标志,这些标志在第二章中显示,-l(长列表)和–f(完整格式)。在本章中,我们讨论了这些标志如何为显示的进程提供额外的细节。

为了更好地理解上述进程,让我们分解一下这两个标志提供的额外细节。

  • 当前状态:S(可中断睡眠)

  • 用户:vagrant

  • 进程 ID:3001

  • 父进程 ID:3000

  • 优先级值:80

  • 优先级级别:0

  • 正在执行的命令:lookbusy –cpu-mode-curve –cpu-curve-peak 14h –c 20-80

早些时候,使用top命令时,这个进程几乎使用了一个完整的 CPU,这意味着这个进程是导致报告的缓慢的嫌疑对象。通过查看上述细节,我们可以确定这个进程的一些情况。

首先,它是进程3000的子进程;这是我们通过父进程 ID 确定的。其次,当我们运行ps命令时,它正在等待一个任务完成;我们可以通过进程当前处于可中断睡眠状态来确定这一点。

除了这两项之外,我们还可以看出该进程没有高调度优先级。我们可以通过查看优先级值来确定这一点,在这种情况下是 80。调度优先级系统的工作方式如下:数字越高,进程在系统调度程序中的优先级越低。

我们还可以看到 niceness 级别设置为0,即默认值。这意味着用户没有调整 niceness 级别以获得更高(或更低)的优先级。

这些都是收集有关进程的重要数据点,但单独来看,它们并不能回答这个进程是否是报告的缓慢的原因。

使用 ps 来确定进程的 CPU 利用率

由于我们知道进程3001是进程3000的子进程,我们不仅应该查看进程3000的相同信息,还应该使用ps来确定进程3000利用了多少 CPU。我们可以通过使用-o(选项)标志和ps来一次完成所有这些。这个标志允许您指定自己的输出格式;它还允许您查看通过常见的ps标志通常不可见的字段。

在下面的命令中,使用-o标志来格式化ps命令的输出,使用前一次运行的关键字段并包括%cpu字段。这个额外的字段将显示进程的 CPU 利用率。该命令还将使用-p标志来指定进程3000和进程3001

$ ps -o state,user,pid,ppid,nice,%cpu,cmd -p 3000,3001
S USER       PID  PPID  NI %CPU CMD
S vagrant   3000  2980   0  0.0 lookbusy --cpu-mode curve --cpu- curve-peak 14h -c 20-80
R vagrant   3001  3000   0 71.5 lookbusy --cpu-mode curve --cpu- curve-peak 14h -c 20-80

虽然上面的命令非常长,但它展示了-o标志有多么有用。在给定正确的选项的情况下,只用ps命令就可以找到大量关于进程的信息。

从上面命令的输出中,我们可以看到进程3000lookbusy命令的另一个实例。我们还可以看到进程3000是进程2980的子进程。在进一步进行之前,我们应该尝试识别与进程3001相关的所有进程。

我们可以使用ps命令和--forest标志来做到这一点,该标志告诉ps以树状格式打印父进程和子进程。当提供-e(所有)标志时,ps命令将以这种树状格式打印所有进程。

提示

默认情况下,ps命令只会打印与执行命令的用户相关的进程。-e标志改变了这种行为,以打印所有可能的进程。

下面的输出被截断,以特别识别lookbusy进程。

$ ps --forest -eo user,pid,ppid,%cpu,cmd
root      1007     1  0.0 /usr/sbin/sshd -D
root      2976  1007  0.0  _ sshd: vagrant [priv]
vagrant   2979  2976  0.0      _ sshd: vagrant@pts/1
vagrant   2980  2979  0.0          _ -bash
vagrant   3000  2980  0.0              _ lookbusy --cpu-mode curve - -cpu-curve-peak 14h -c 20-80
vagrant   3001  3000 70.4                  _ lookbusy --cpu-mode curve --cpu-curve-peak 14h -c 20-80
vagrant   3002  3000 14.6                  _ lookbusy --cpu-mode curve --cpu-curve-peak 14h -c 20-80

从上面的ps输出中,我们可以看到 ID 为3000lookbusy进程产生了两个进程,分别是30013002。我们还可以看到当前通过 SSH 登录的 vagrant 用户启动了lookbusy进程。

由于我们还使用了-o标志和ps来显示 CPU 利用率,我们可以看到进程3002正在利用单个 CPU 的14.6%

提示

重要的是要注意,ps命令还显示了单个处理器的 CPU 时间百分比,这意味着利用多个处理器的进程可能具有高于 100%的值。

把它们都放在一起

现在我们已经通过命令来识别系统的 CPU 利用率,让我们把它们放在一起总结一下找到的东西。

用 top 快速查看

我们识别与 CPU 性能相关的问题的第一步是执行top命令。

$ top

top - 01:50:36 up 23:41,  2 users,  load average: 0.68, 0.56, 0.48
Tasks: 107 total,   4 running, 103 sleeping,   0 stopped,   0 zombie
%Cpu(s): 34.5 us,  0.7 sy,  0.0 ni, 64.9 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem:    502060 total,   231168 used,   270892 free,      764 buffers
KiB Swap:  1081340 total,        0 used,  1081340 free.    94628 cached Mem

 PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
 3001 vagrant   20   0    7396    724    508 R  68.8  0.1 993:06.80 lookbusy
 3002 vagrant   20   0    7396    724    508 S   1.0  0.1 198:58.16 lookbusy
 12 root      20   0       0      0      0 S   0.3  0.0   3:47.55 rcuos/0
 13 root      20   0       0      0      0 R   0.3  0.0   3:38.85 rcuos/1
 2718 vagrant   20   0  131524   2536   1344 R   0.3  0.5   0:02.28 sshd

top的输出中,我们可以识别以下内容:

  • 总体而言,系统大约 60%–70%的时间处于空闲状态

  • 有两个正在运行lookbusy命令/程序的进程,其中一个似乎正在使用单个 CPU 的 70%

  • 鉴于这个单独进程的 CPU 利用率和系统 CPU 利用率,所涉及的服务器很可能有多个 CPU

  • 我们可以用lscpu命令确认存在多个 CPU

  • 进程 3001 和 3002 是该系统上利用 CPU 最多的两个进程

  • CPU 等待状态百分比为 0,这意味着问题不太可能与磁盘 I/O 有关

通过 ps 深入挖掘

由于我们从top命令的输出中确定了进程30013002是可疑的,我们可以使用ps命令进一步调查这些进程。为了快速进行调查,我们将使用ps命令和-o--forest标志来用一个命令识别最大可能的信息。

$ ps --forest -eo user,pid,ppid,%cpu,cmd
root      1007     1  0.0 /usr/sbin/sshd -D
root      2976  1007  0.0  _ sshd: vagrant [priv]
vagrant   2979  2976  0.0      _ sshd: vagrant@pts/1
vagrant   2980  2979  0.0          _ -bash
vagrant   3000  2980  0.0              _ lookbusy --cpu-mode curve --cpu-curve-peak 14h -c 20-80
vagrant   3001  3000 69.8                  _ lookbusy --cpu-mode curve --cpu-curve-peak 14h -c 20-80
vagrant   3002  3000 13.9                  _ lookbusy --cpu-mode curve --cpu-curve-peak 14h -c 20-80

从这个输出中,我们可以确定以下内容:

  • 进程 3001 和 3002 是进程 3000 的子进程

  • 进程 3000 是由vagrant用户启动的

  • lookbusy命令似乎是一个利用大量 CPU 的命令

  • 启动lookbusy的方法并不表明这是一个系统进程,而是一个用户运行的临时命令。

根据上述信息,vagrant用户启动的lookbusy进程有可能是性能问题的根源。如果这个系统通常的 CPU 利用率较低,这是一个合理的根本原因的假设。然而,考虑到我们对这个系统不太熟悉,lookbusy进程几乎使用了整个 CPU 也是可能的。

考虑到我们对系统的正常运行条件不太熟悉,我们应该在得出结论之前继续调查性能问题的其他可能来源。

内存

在应用程序和 CPU 利用率之后,内存利用率是性能下降的一个非常常见的来源。在 CPU 部分,我们广泛使用了top,虽然top也可以用来识别系统和进程的内存利用率,但在这一部分,我们将使用其他命令。

free – 查看空闲和已用内存

如第二章中所讨论的,故障排除命令和有用信息来源 free命令只是简单地打印系统当前的内存可用性和使用情况。

当没有标志时,free命令将以千字节为单位输出其值。为了使输出以兆字节为单位,我们可以简单地使用-m(兆字节)标志执行free命令。

$ free -m
 total       used       free     shared    buffers     cached
Mem:     490         92        397          1          0         17
-/+ buffers/cache:         74        415
Swap:         1055         57        998

free命令显示了关于这个系统以及内存使用情况的大量信息。为了更好地理解这个命令,让我们对输出进行一些分解。

由于输出中有多行,我们将从输出标题之后的第一行开始:

Mem:         490        92       397         1         0         17

这一行中的第一个值是系统可用的物理内存总量。在我们的情况下,这是 490 MB。第二个值是系统使用的内存量。第三个值是系统上未使用的内存量;请注意,我使用了“未使用”而不是“可用”这个术语。第四个值是用于共享内存的内存量;除非您的系统经常使用共享内存,否则这通常是一个较低的数字。

第五个值是用于缓冲区的内存量。Linux 通常会尝试通过将频繁使用的磁盘信息放入物理内存来加快磁盘访问速度。缓冲区内存通常是文件系统元数据。缓存内存,也就是第六个值,是经常访问文件的内容。

Linux 内存缓冲区和缓存

Linux 通常会尝试使用“未使用”的内存来进行缓冲和缓存。这意味着为了提高效率,Linux 内核将频繁访问的文件数据和文件系统元数据存储在未使用的内存中。这使得系统能够利用本来不会被使用的内存来增强磁盘访问,而磁盘访问通常比系统内存慢。

这就是为什么第三个值“未使用”内存通常比预期的数字要低的原因。

然而,当系统的未使用内存不足时,Linux 内核将根据需要释放缓冲区和缓存内存。这意味着即使从技术上讲,用于缓冲区和缓存的内存被使用了,但在需要时它从技术上讲是可用的。

这将我们带到了 free 输出的第二行。

-/+ buffers/cache:         74        415

第二行有两个值,第一个是Used列的一部分,第二个是Free或“未使用”列的一部分。这些值是在考虑缓冲区和缓存内存的可用或未使用内存值之后得出的。

简单来说,第二行的已使用值是从第一行的已使用内存值减去缓冲区和缓存值得到的结果。对于我们的示例,这是 92 MB(已使用)减去 17 MB(cached)。

第二行的 free 值是第一行的 Free 值加上缓冲区和缓存内存的结果。使用我们的示例数值,这将是 397 MB(free)加上 17 MB(cached)。

交换内存

free命令的输出的第三行是用于交换内存的。

Swap:         1055         57        998

在这一行中,有三列:可用、已使用和空闲。交换内存的值相当容易理解。可用交换值是系统可用的交换内存量,已使用值是当前分配的交换量,而空闲值基本上是可用交换减去已分配的交换量。

有许多环境不赞成分配大量的交换空间,因为这通常是系统内存不足并使用交换空间来补偿的指标。

free 告诉我们关于我们系统的信息

如果我们再次查看 free 的输出,我们可以确定关于这台服务器的很多事情。

$ free -m
 total       used       free     shared    buffers     cached
Mem:       490        105        385          1          0         25
-/+ buffers/cache:         79        410
Swap:         1055         56        999

我们可以确定实际上只使用了很少的内存(79 MB)。这意味着总体上,系统应该有足够的内存可用于进程。

然而,还有一个有趣的事实,在第三行显示,56 MB 的内存已被写入交换空间。尽管系统当前有大量可用内存,但已经有 56 MB 被写入交换空间。这意味着在过去的某个时刻,这个系统可能内存不足,足够低到系统不得不将内存页面从物理内存交换到交换内存。

检查 oomkill

当 Linux 系统的物理内存耗尽时,它首先尝试重用分配给缓冲区和缓存的内存。如果没有额外的内存可以从这些来源中回收,那么内核将从物理内存中获取旧的内存页面并将它们写入交换内存。一旦物理内存和交换内存都被分配,内核将启动内存不足杀手oomkill)进程。oomkill进程旨在找到使用大量内存的进程并将其杀死(停止)。

一般来说,在大多数环境中,oomkill进程是不受欢迎的。一旦调用,oomkill进程可以杀死许多不同类型的进程。无论进程是系统的一部分还是用户级别的,oomkill都有能力杀死它们。

对于可能影响内存利用的性能问题,检查oomkill进程最近是否被调用是一个很好的主意。确定oomkill最近是否运行的最简单方法是简单地查看系统的控制台,因为这个进程的启动会直接记录在系统控制台上。然而,在云和虚拟环境中,控制台可能不可用。

另一个确定最近是否调用了oomkill的好方法是搜索/var/log/messages日志文件。我们可以通过执行grep命令并搜索字符串Out of memory来做到这一点。

# grep "Out of memory" /var/log/messages

对于我们的示例系统,最近没有发生oomkill调用。如果我们的系统调用了oomkill进程,我们可能会收到类似以下消息:

# grep "Out of memory" /var/log/messages
Feb  7 19:38:45 localhost kernel: Out of memory: Kill process 3236 (python) score 838 or sacrifice child

在第十一章中,从常见故障中恢复,我们将再次调查内存问题,并深入了解oomkill及其工作原理。对于本章,我们可以得出结论,系统尚未完全耗尽其可用内存。

ps - 检查单个进程的内存利用率

到目前为止,系统上的内存使用似乎很小,但是我们从 CPU 验证步骤中知道,运行lookbusy的进程是可疑的,可能导致性能问题。由于我们怀疑lookbusy进程存在问题,我们还应该查看这些进程使用了多少内存。为了做到这一点,我们可以再次使用带有-o标志的ps命令。

$ ps -eo user,pid,ppid,%mem,rss,vsize,comm | grep lookbusy
vagrant   3000  2980  0.0     4   7396 lookbusy
vagrant   3001  3000  0.0   296   7396 lookbusy
vagrant   3002  3000  0.0   220   7396 lookbusy
vagrant   5380  2980  0.0     8   7396 lookbusy
vagrant   5381  5380  0.0   268   7396 lookbusy
vagrant   5382  5380  0.0   268   7396 lookbusy
vagrant   5383  5380 40.7 204812 212200 lookbusy
vagrant   5531  2980  0.0    40   7396 lookbusy
vagrant   5532  5531  0.0   288   7396 lookbusy
vagrant   5533  5531  0.0   288   7396 lookbusy
vagrant   5534  5531 34.0 170880 222440 lookbusy

然而,这一次我们以稍有不同的方式运行了我们的ps命令,因此得到了不同的结果。这一次执行ps命令时,我们使用了-e(everything)标志来显示所有进程。然后将结果传输到grep,以便将它们缩小到只匹配lookbusy模式的进程。

这是使用ps命令的一种非常常见的方式;事实上,这比在命令行上指定进程 ID 更常见。除了使用grep之外,这个ps命令示例还介绍了一些新的格式选项。

  • %mem:这是进程正在使用的系统内存的百分比。

  • rss:这是进程的常驻集大小,基本上是指进程使用的不可交换内存量。

  • vsize:这是虚拟内存大小,它包含进程完全使用的内存量,无论这些内存是物理内存的一部分还是交换内存的一部分。

  • comm:此选项类似于 cmd,但不显示命令行参数。

ps示例显示了有趣的信息,特别是以下几行:

vagrant   5383  5380 40.7 204812 212200 lookbusy
vagrant   5534  5531 34.0 170880 222440 lookbusy

似乎已经启动了几个额外的lookbusy进程,并且这些进程正在利用系统内存的 40%和 34%(通过使用%mem列)。从 rss 列中,我们可以看到这两个进程正在使用总共 490MB 物理内存中的约 374MB。

看起来这些进程在我们开始调查后开始利用大量内存。最初,我们的 free 输出表明只使用了 70MB 内存;然而,这些进程似乎利用了更多。我们可以通过再次运行 free 来确认这一点。

$ free -m
 total       used       free     shared    buffers     cached
Mem:       490        453         37          0          0          3
-/+ buffers/cache:        449         41
Swap:         1055        310        745

事实上,我们的系统现在几乎利用了所有的内存;事实上,我们还使用了 310MB 的交换空间。

vmstat - 监控内存分配和交换

由于这个系统的内存利用率似乎有所波动,有一个非常有用的命令可以定期显示内存分配和释放以及换入和换出的页面数。这个命令叫做vmstat

$ vmstat -n 10 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
5  0 204608  31800      0   7676    8    6    12     6  101  131 44 1 55  0  0
1  0 192704  35816      0   2096 1887  130  4162   130 2080 2538 53 6 39  2  0
1  0 191340  32324      0   3632 1590   57  3340    57 2097 2533 54 5 41  0  0
4  0 191272  32260      0   5400  536    2  2150     2 1943 2366 53 4 43  0  0
3  0 191288  34140      0   4152  392    0   679     0 1896 2366 53 3 44  0  0

在上面的示例中,vmstat命令是使用-n(一个标题)标志执行的,后面跟着延迟时间(10 秒)和要生成的报告数(5)。这些选项告诉vmstat仅为此次执行输出一个标题行,而不是为每个报告输出一个新的标题行,每 10 秒运行一次报告,并将报告数量限制为 5。如果省略了报告数量的限制,vmstat将简单地持续运行,直到使用CTRL+C停止。

vmstat的输出一开始可能有点压倒性,但如果我们分解输出,就会更容易理解。vmstat的输出有六个输出类别,即进程、内存、交换、IO、系统和 CPU。在本节中,我们将专注于这两个类别:内存和交换。

  • 内存

  • swpd:写入交换的内存量

  • free:未使用的内存量

  • buff:用作缓冲区的内存量

  • cache:用作缓存的内存量

  • inact:非活动内存量

  • active:活动内存量

  • 交换

  • si:从磁盘交换的内存量

  • so:交换到磁盘的内存量

现在我们已经了解了这些值的定义,让我们看看vmstat的输出告诉我们关于这个系统内存使用情况的信息。

procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 5  0 204608  31800      0   7676    8    6    12     6  101  131 44  1 55  0  0
 1  0 192704  35816      0   2096 1887  130  4162   130 2080 2538 53  6 39  2  0

如果我们比较vmstat输出的第一行和第二行,我们会看到一个相当大的差异。特别是,我们可以看到在第一个间隔中,缓存内存是7676,而在第二个间隔中,这个值是 2096。我们还可以看到第一行中的si或交换入值是 8,而第二行中是 1887。

这种差异的原因是,vmstat的第一个报告总是自上次重启以来的统计摘要,而第二个报告是自上一个报告以来的统计摘要。每个后续的报告将总结前一个报告,这意味着第三个报告将总结自第二个报告以来的统计数据。vmstat的这种行为经常会让新的系统管理员和用户感到困惑;因此,它通常被认为是一种高级故障排除工具。

由于vmstat生成第一个报告的方法,通常的做法是丢弃它并从第二个报告开始。我们将遵循这一原则,特别关注第二个和第三个报告。

procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 5  0 204608  31800      0   7676    8    6    12     6  101  131 44  1 55  0  0
 1  0 192704  35816      0   2096 1887  130  4162   130 2080 2538 53  6 39  2  0
 1  0 191340  32324      0   3632 1590   57  3340    57 2097 2533 54  5 41  0  0

在第二个和第三个报告中,我们可以看到一些有趣的数据。

最引人注目的是,从第一个报告的生成时间到第二个报告的生成时间,交换了 1,887 页,交换出了 130 页。第二个报告还显示,只有 35 MB 的内存是空闲的,缓冲区中没有内存,缓存中有 2 MB 的内存。根据 Linux 内存的利用方式,这意味着系统上实际上只有 37 MB 的可用内存。

这种低可用内存量解释了为什么我们的系统已经交换了大量页面。我们可以从第三行看到这种趋势正在持续,我们继续交换了相当多的页面,我们的可用内存已经减少到大约 35 MB。

从这个vmstat的例子中,我们可以看到我们的系统现在已经用尽了物理内存。因此,我们的系统正在从物理 RAM 中取出内存页面并将其写入我们的交换设备。

把所有东西放在一起

现在我们已经探索了用于故障排除内存利用的工具,让我们把它们都放在一起来解决系统性能缓慢的问题。

用 free 查看系统的内存利用

给我们提供系统内存利用快照的第一个命令是free命令。这个命令将为我们提供在哪里进一步查找任何内存利用问题的想法。

$ free -m
 total       used       free     shared    buffers     cached
Mem:       490        293        196          0          0         18
-/+ buffers/cache:        275        215
Swap:         1055        183        872

free的输出中,我们可以看到目前有 215 MB 的内存可用。我们可以通过第二行的free列看到这一点。我们还可以看到,总体上,这个系统有 183 MB 的内存已经被交换到我们的交换设备。

观察 vmstat 的情况

由于系统在某个时候已经进行了交换(或者说分页),我们可以使用vmstat命令来查看系统当前是否正在进行交换。

这次执行vmstat时,我们将不指定报告值的数量,这将导致vmstat持续报告内存统计,类似于 top 命令的输出。

$ vmstat -n 10
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 4  0 188008 200320      0  19896   35    8    61     9  156    4 44 1 55  0  0
 4  0 188008 200312      0  19896    0    0     0     0 1361 1314 36 2 62  0  0
 2  0 188008 200312      0  19896    0    0     0     0 1430 1442 37 2 61  0  0
 0  0 188008 200312      0  19896    0    0     0     0 1431 1418 37 2 61  0  0
 0  0 188008 200280      0  19896    0    0     0     0 1414 1416 37 2 61  0  0
 2  0 188008 200280      0  19896    0    0     0     0 1456 1480 37 2 61  0  0

这个vmstat输出与我们之前的执行不同。从这个输出中,我们可以看到虽然有相当多的内存被交换,但系统目前并没有进行交换。我们可以通过si(交换入)和 so(交换出)列中的 0 值来确定这一点。

实际上,在这次vmstat运行期间,内存利用率似乎很稳定。每个vmstat报告中,free内存值都相当一致,缓存和缓冲内存统计也是如此。

使用 ps 找到内存利用最多的进程

我们的系统有 490MB 的物理内存,freevmstat都显示大约 215MB 的可用内存。这意味着我们系统内存的一半以上目前被使用;在这种使用水平下,找出哪些进程正在使用我们系统的内存是一个好主意。即使没有别的,这些数据也将有助于显示系统当前的状态。

要识别使用最多内存的进程,我们可以使用ps命令以及 sort 和 tail。

# ps -eo rss,vsize,user,pid,cmd | sort -nk 1 | tail -n 5
 1004 115452 root      5073 -bash
 1328 123356 root      5953 ps -eo rss,vsize,user,pid,cmd
 2504 525652 root       555 /usr/sbin/NetworkManager --no-daemon
 4124  50780 root         1 /usr/lib/systemd/systemd --switched-root --system --deserialize 23
204672 212200 vagrant  5383 lookbusy -m 200MB -c 10

上面的例子使用管道将ps的输出重定向到 sort 命令。sort 命令执行数字(-n)对第一列(-k 1)的排序。这将对输出进行排序,将具有最高rss大小的进程放在底部。在sort命令之后,输出也被管道传递到tail命令,当指定了-n(数字)标志后跟着一个数字,将限制输出只包括指定数量的结果。

提示

如果将命令与管道一起链接的概念是新的,我强烈建议练习这一点,因为它对日常的sysadmin任务以及故障排除非常有用。我们将在本书中多次讨论这个概念,并提供示例。

204672 212200 vagrant  5383 lookbusy -m 200MB -c 10

ps的输出中,我们可以看到进程 5383 正在使用大约 200MB 的内存。我们还可以看到该进程是另一个lookbusy进程,再次由 vagrant 用户生成。

freevmstatps的输出中,我们可以确定以下内容:

  • 系统当前大约有 200MB 的可用内存

  • 虽然系统目前没有交换,但过去曾经有过,根据我们之前从vmstat看到的情况,我们知道它最近进行了交换

  • 我们发现进程5383正在使用大约 200MB 的内存

  • 我们还可以看到进程5383是由vagrant用户启动的,并且正在运行lookbusy进程

  • 使用free命令,我们可以看到这个系统有 490MB 的物理内存

根据以上信息,似乎由vagrant用户执行的lookbusy进程不仅是 CPU 的可疑使用者,还是内存的可疑使用者。

磁盘

磁盘利用率是另一个常见的性能瓶颈。一般来说,性能问题很少是由于磁盘空间的问题。虽然我曾经看到由于大量文件或大文件的性能问题,但一般来说,磁盘性能受到写入和读取磁盘的限制。因此,在故障排除性能问题时,了解文件系统是否已满很重要,但仅仅根据文件系统的使用情况并不总是能指示是否存在问题。

iostat - CPU 和设备输入/输出统计

iostat命令是用于故障排除磁盘性能问题的基本命令,类似于 vmstat,它提供的使用和信息都是相似的。像vmstat一样,执行iostat命令后面跟着两个数字,第一个是报告生成的延迟,第二个是要生成的报告数。

$ iostat -x 10 3
Linux 3.10.0-123.el7.x86_64 (blog.example.com)   02/08/2015 _x86_64_  (2 CPU)

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
 43.58    0.00    1.07    0.16    0.00   55.19

Device:         rrqm/s   wrqm/s     r/s     w/s    rkB/s    wkB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
sda              12.63     3.88    8.47    3.47   418.80   347.40 128.27     0.39   32.82    0.80  110.93   0.47   0.56
dm-0              0.00     0.00   16.37    3.96    65.47    15.82 8.00     0.48   23.68    0.48  119.66   0.09   0.19
dm-1              0.00     0.00    4.73    3.21   353.28   331.71 172.51     0.39   48.99    1.07  119.61   0.54   0.43

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
 20.22    0.00   20.33   22.14    0.00   37.32

Device:         rrqm/s   wrqm/s     r/s     w/s    rkB/s    wkB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
sda               0.10    13.67  764.97  808.68 71929.34 78534.73 191.23    62.32   39.75    0.74   76.65   0.42  65.91
dm-0              0.00     0.00    0.00    0.10     0.00     0.40 8.00     0.01   70.00    0.00   70.00  70.00   0.70
dm-1              0.00     0.00  765.27  769.76 71954.89 78713.17 196.31    64.65   42.25    0.74   83.51   0.43  66.46

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
 18.23    0.00   15.56   29.26    0.00   36.95

Device:         rrqm/s   wrqm/s     r/s     w/s    rkB/s    wkB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
sda               0.10     7.10  697.50  440.10 74747.60 42641.75 206.38    74.13   66.98    0.64  172.13   0.58  66.50
dm-0              0.00     0.00    0.00    0.00     0.00     0.00 0.00     0.00    0.00    0.00    0.00   0.00   0.00
dm-1              0.00     0.00  697.40  405.00 74722.00 40888.65 209.74    75.80   70.63    0.66  191.11   0.61  67.24

在上面的例子中,提供了-x(扩展统计)标志以打印扩展统计信息。扩展统计非常有用,并提供了额外的信息,对于识别性能瓶颈至关重要。

CPU 详情

iostat命令将显示 CPU 统计信息以及 I/O 统计信息。这是另一个可以用来排除 CPU 利用率的命令。当 CPU 利用率指示高 I/O 等待时间时,这是特别有用的。

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
 20.22    0.00   20.33   22.14    0.00   37.32

以上是从top命令显示的相同信息;在 Linux 中找到多个输出类似信息的命令并不罕见。由于这些细节已在 CPU 故障排除部分中涵盖,我们将专注于iostat命令的 I/O 统计部分。

审查 I/O 统计

要开始审查 I/O 统计,让我们从前两份报告开始。我在下面包括了 CPU 利用率,以帮助指示每份报告的开始位置,因为它是每份统计报告中的第一项。

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
 43.58    0.00    1.07    0.16    0.00   55.19

Device:         rrqm/s   wrqm/s     r/s     w/s    rkB/s    wkB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
sda              12.63     3.88    8.47    3.47   418.80   347.40 128.27     0.39   32.82    0.80  110.93   0.47   0.56
dm-0              0.00     0.00   16.37    3.96    65.47    15.82 8.00     0.48   23.68    0.48  119.66   0.09   0.19
dm-1              0.00     0.00    4.73    3.21   353.28   331.71 172.51     0.39   48.99    1.07  119.61   0.54   0.43

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
 20.22    0.00   20.33   22.14    0.00   37.32

Device:         rrqm/s   wrqm/s     r/s     w/s    rkB/s    wkB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
sda               0.10    13.67  764.97  808.68 71929.34 78534.73 191.23    62.32   39.75    0.74   76.65   0.42  65.91
dm-0              0.00     0.00    0.00    0.10     0.00     0.40 8.00     0.01   70.00    0.00   70.00  70.00   0.70
dm-1              0.00     0.00  765.27  769.76 71954.89 78713.17 196.31    64.65   42.25    0.74   83.51   0.43  66.46

通过比较前两份报告,我们发现它们之间存在很大的差异。在第一个报告中,sda设备的%util值为0.56,而在第二个报告中为65.91

这种差异的原因是,与vmstat一样,第一次执行iostat的统计是基于服务器最后一次重启的时间。第二份报告是基于第一份报告之后的时间。这意味着第二份报告的输出是基于第一份报告生成之间的 10 秒。这与vmstat中看到的行为相同,并且是其他收集性能统计信息的工具的常见行为。

vmstat一样,我们将丢弃第一个报告,只看第二个报告。

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
 20.22    0.00   20.33   22.14    0.00   37.32

Device:         rrqm/s   wrqm/s     r/s     w/s    rkB/s    wkB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
sda               0.10    13.67  764.97  808.68 71929.34 78534.73 191.23    62.32   39.75    0.74   76.65   0.42  65.91
dm-0              0.00     0.00    0.00    0.10     0.00     0.40 8.00     0.01   70.00    0.00   70.00  70.00   0.70
dm-1              0.00     0.00  765.27  769.76 71954.89 78713.17 196.31    64.65   42.25    0.74   83.51   0.43  66.46

从上面,我们可以确定这个系统的几个情况。最重要的是 CPU 行中的%iowait值。

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
 20.22    0.00   20.33   22.14    0.00   37.32

早些时候在执行 top 命令时,等待 I/O 的时间百分比相当小;然而,在运行iostat时,我们可以看到 CPU 实际上花了很多时间等待 I/O。虽然 I/O 等待并不一定意味着等待磁盘,但这个输出的其余部分似乎表明磁盘活动相当频繁。

Device:         rrqm/s   wrqm/s     r/s     w/s    rkB/s    wkB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
sda               0.10    13.67  764.97  808.68 71929.34 78534.73 191.23    62.32   39.75    0.74   76.65   0.42  65.91
dm-0              0.00     0.00    0.00    0.10     0.00     0.40 8.00     0.01   70.00    0.00   70.00  70.00   0.70
dm-1              0.00     0.00  765.27  769.76 71954.89 78713.17 196.31    64.65   42.25    0.74   83.51   0.43  66.46

扩展统计输出有许多列,为了使这个输出更容易理解,让我们分解一下这些列告诉我们的内容。

  • rrqm/s:每秒合并和排队的读取请求数

  • wrqm/s:每秒合并和排队的写入请求数

  • r/s:每秒完成的读取请求数

  • w/s:每秒完成的写入请求数

  • rkB/s:每秒读取的千字节数

  • wkB/s:每秒写入的千字节数

  • avgr-sz:发送到设备的请求的平均大小(以扇区为单位)

  • avgqu-sz:发送到设备的请求的平均队列长度

  • await:请求等待服务的平均时间(毫秒)

  • r_await:读取请求等待服务的平均时间(毫秒)

  • w_await:写入请求等待服务的平均时间(毫秒)

  • svctm:此字段无效,将被删除;不应被信任或使用

  • %util:在此设备服务 I/O 请求时所花费的 CPU 时间百分比。设备最多只能利用 100%

对于我们的示例,我们将专注于r/sw/sawait%util值,因为这些值将告诉我们关于这个系统的磁盘利用率的很多信息,同时保持我们的示例简单。

经过审查iostat输出后,我们可以看到sdadm-1设备都具有最高的%util值,这意味着它们最接近达到容量。

Device:         rrqm/s   wrqm/s     r/s     w/s    rkB/s    wkB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
sda               0.10    13.67  764.97  808.68 71929.34 78534.73 191.23    62.32   39.75    0.74   76.65   0.42  65.91
dm-1              0.00     0.00  765.27  769.76 71954.89 78713.17 196.31    64.65   42.25    0.74   83.51   0.43  66.46

从这份报告中,我们可以看到sda设备平均完成了 764 次读取(r/s)和 808 次写入(w/s)每秒。我们还可以确定这些请求平均需要 39 毫秒(等待时间)来完成。虽然这些数字很有趣,但并不一定意味着系统处于异常状态。由于我们对这个系统不熟悉,我们并不一定知道读取和写入的水平是否出乎意料。然而,收集这些信息是很重要的,因为这些统计数据是故障排除过程中数据收集阶段的重要数据。

iostat中我们可以看到另一个有趣的统计数据是,sdadm-1设备的%util值都约为 66%。这意味着在第一次报告生成和第二次报告之间的 10 秒内,66%的 CPU 时间都是在等待sddm-1设备。

识别设备

对于磁盘设备来说,66%的利用率通常被认为是很高的,虽然这是非常有用的信息,但它并没有告诉我们是谁或什么在利用这个磁盘。为了回答这些问题,我们需要弄清楚sdadm-1到底被用来做什么。

由于iostat命令输出的设备通常是磁盘设备,识别这些设备的第一步是运行mount命令。

$ mount
proc on /proc type proc (rw,nosuid,nodev,noexec,relatime)
sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,relatime,seclabel)
devtmpfs on /dev type devtmpfs (rw,nosuid,seclabel,size=244828k,nr_inodes=61207,mode=755)
securityfs on /sys/kernel/security type securityfs (rw,nosuid,nodev,noexec,relatime)
tmpfs on /dev/shm type tmpfs (rw,nosuid,nodev,seclabel)
devpts on /dev/pts type devpts (rw,nosuid,noexec,relatime,seclabel,gid=5,mode=620,ptmxmode=000)
tmpfs on /run type tmpfs (rw,nosuid,nodev,seclabel,mode=755)
tmpfs on /sys/fs/cgroup type tmpfs (rw,nosuid,nodev,noexec,seclabel,mode=755)
configfs on /sys/kernel/config type configfs (rw,relatime)
/dev/mapper/root on / type xfs (rw,relatime,seclabel,attr2,inode64,noquota)
hugetlbfs on /dev/hugepages type hugetlbfs (rw,relatime,seclabel)
mqueue on /dev/mqueue type mqueue (rw,relatime,seclabel)
debugfs on /sys/kernel/debug type debugfs (rw,relatime)
/dev/sda1 on /boot type xfs (rw,relatime,seclabel,attr2,inode64,noquota)

mount命令在没有任何选项的情况下运行时,将显示所有当前挂载的文件系统。mount输出中的第一列是已经挂载的设备。在上面的输出中,我们可以看到sda设备实际上是一个磁盘设备,并且它有一个名为sda1的分区,挂载为/boot

然而,我们没有看到dm-1设备。由于这个设备没有出现在mount命令的输出中,我们可以通过另一种方式,在/dev文件夹中查找dm-1设备。

系统上的所有设备都被呈现为/dev文件夹结构中的一个文件。dm-1设备也不例外。

$ ls -la /dev/dm-1
brw-rw----. 1 root disk 253, 1 Feb  1 18:47 /dev/dm-1

虽然我们已经找到了dm-1设备的位置,但我们还没有确定它的用途。然而,关于这个设备,有一件事引人注目,那就是它的名字dm-1。当设备以dm开头时,这表明该设备是由设备映射器创建的逻辑设备。

设备映射器是一个 Linux 内核框架,允许系统创建虚拟磁盘设备,这些设备“映射”回物理设备。这个功能用于许多特性,包括软件 RAID、磁盘加密和逻辑卷。

设备映射器框架中的一个常见做法是为这些特性创建符号链接,这些符号链接指向单个逻辑设备。由于我们可以用ls命令看到dm-1是一个块设备,通过输出的第一列的“b”值(brw-rw----.),我们知道dm-1不是一个符号链接。我们可以利用这些信息以及 find 命令来识别任何指向dm-1块设备的符号链接。

# find -L /dev -samefile /dev/dm-1
/dev/dm-1
/dev/rhel/root
/dev/disk/by-uuid/beb5220d-5cab-4c43-85d7-8045f870ba7d
/dev/disk/by-id/dm-uuid-LVM-qj3iMeektIlL3Z0g4WMPMJRbzacnpS9IVOCzB60GSHCEgbRKYW9ZKXR5prUPEE1e
/dev/disk/by-id/dm-name-root
/dev/block/253:1
/dev/mapper/root

在前面的章节中,我们使用 find 命令来识别配置和日志文件。在上面的例子中,我们使用了-L(跟随链接)标志,后面跟着/dev路径和--samefile标志,告诉 find 搜索/dev文件夹结构,搜索任何符号链接的文件,以识别任何与/dev/dm-1相同的文件。

--samefile标志标识具有相同inode号的文件。当命令中包含-L标志时,输出包括符号链接,而这个例子似乎返回了几个结果。最引人注目的符号链接文件是/dev/mapper/root;这个文件之所以引人注目,是因为它也出现在挂载命令的输出中。

/dev/mapper/root on / type xfs (rw,relatime,seclabel,attr2,inode64,noquota)

看起来/dev/mapper/root似乎是一个逻辑卷。在 Linux 中,逻辑卷本质上是存储虚拟化。这个功能允许您创建伪设备(作为设备映射器的一部分),它映射到一个或多个物理设备。

例如,可以将四个不同的硬盘组合成一个逻辑卷。逻辑卷然后可以用作单个文件系统的磁盘。甚至可以在以后通过使用逻辑卷添加另一个硬盘。

确认/dev/mapper/root设备实际上是一个逻辑卷,我们可以执行lvdisplay命令,该命令用于显示系统上的逻辑卷。

# lvdisplay
 --- Logical volume ---
 LV Path                /dev/rhel/swap
 LV Name                swap
 VG Name                rhel
 LV UUID                y1ICUQ-l3uA-Mxfc-JupS-c6PN-7jvw-W8wMV6
 LV Write Access        read/write
 LV Creation host, time localhost, 2014-07-21 23:35:55 +0000
 LV Status              available
 # open                 2
 LV Size                1.03 GiB
 Current LE             264
 Segments               1
 Allocation             inherit
 Read ahead sectors     auto
 - currently set to     256
 Block device           253:0

 --- Logical volume ---
 LV Path                /dev/rhel/root
 LV Name                root
 VG Name                rhel
 LV UUID                VOCzB6-0GSH-CEgb-RKYW-9ZKX-R5pr-UPEE1e
 LV Write Access        read/write
 LV Creation host, time localhost, 2014-07-21 23:35:55 +0000
 LV Status              available
 # open                 1
 LV Size                38.48 GiB
 Current LE             9850
 Segments               1
 Allocation             inherit
 Read ahead sectors     auto
 - currently set to     256
 Block device           253:1

lvdisplay的输出中,我们可以看到一个名为/dev/rhel/root的有趣路径,这个路径也存在于我们的find命令的输出中。让我们用ls命令来查看这个设备。

# ls -la /dev/rhel/root
lrwxrwxrwx. 1 root root 7 Aug  3 16:27 /dev/rhel/root -> ../dm-1

在这里,我们可以看到/dev/rhel/root是一个指向/dev/dm-1的符号链接;这证实了/dev/rhel/root/dev/dm-1是相同的,这些实际上是逻辑卷设备,这意味着这些并不是真正的物理设备。

要显示这些逻辑卷背后的物理设备,我们可以使用pvdisplay命令。

# pvdisplay
 --- Physical volume ---
 PV Name               /dev/sda2
 VG Name               rhel
 PV Size               39.51 GiB / not usable 3.00 MiB
 Allocatable           yes (but full)
 PE Size               4.00 MiB
 Total PE              10114
 Free PE               0
 Allocated PE          10114
 PV UUID               n5xoxm-kvyI-Z7rR-MMcH-1iJI-D68w-NODMaJ

我们可以从pvdisplay的输出中看到,dm-1设备实际上映射到sda2,这解释了为什么dm-1sda的磁盘利用率非常接近,因为对dm-1的任何活动实际上都是在sda上执行的。

谁在向这些设备写入?

现在我们已经找到了 I/O 的利用情况,我们需要找出谁在利用这个 I/O。找出哪些进程最多地写入磁盘的最简单方法是使用iotop命令。这个工具是一个相对较新的命令,现在默认包含在 Red Hat Enterprise Linux 7 中。然而,在以前的 RHEL 版本中,这个命令并不总是可用的。

在采用iotop之前,查找使用 I/O 最多的进程的方法涉及使用ps命令并浏览/proc文件系统。

ps - 使用 ps 命令识别利用 I/O 的进程

在收集与 CPU 相关的数据时,我们涵盖了ps命令的输出中的状态字段。我们没有涵盖的是进程可能处于的各种状态。以下列表包含了ps命令将显示的七种可能的状态:

  • 不间断睡眠(D):进程通常在等待 I/O 时处于睡眠状态

  • 运行或可运行R):运行队列上的进程

  • 可中断睡眠S):等待事件完成但不阻塞 CPU 或 I/O 的进程

  • 已停止T):被作业控制系统停止的进程,如 jobs 命令

  • 分页P):当前正在分页的进程;但是,在较新的内核上,这不太相关

  • 死亡X):已经死亡的进程,不应该出现在运行ps

  • 僵尸Z):已终止但保留在不死状态的僵尸进程

在调查 I/O 利用率时,重要的是要识别状态列为D不间断睡眠。由于这些进程通常在等待 I/O,它们是最有可能过度利用磁盘 I/O 的进程。

为了做到这一点,我们将使用ps命令和-e(所有)、-l(长格式)和-f(完整格式)标志。我们还将再次使用管道将输出重定向到grep命令,并将输出过滤为只显示具有D状态的进程。

# ps -elf | grep " D "
1 D root     13185     2  2  80   0 -     0 get_re 00:21 ? 00:01:32 [kworker/u4:1]
4 D root     15639 15638 30  80   0 -  4233 balanc 01:26 pts/2 00:00:02 bonnie++ -n 0 -u 0 -r 239 -s 478 -f -b -d /tmp

从上面的输出中,我们看到有两个进程目前处于不间断睡眠状态。一个进程是kworker,这是一个内核系统进程,另一个是bonnie++,是由 root 用户启动的进程。由于kworker进程是一个通用的内核进程,我们将首先关注bonnie++进程。

为了更好地理解这个过程,我们将再次运行ps命令,但这次使用--forest选项。

# ps -elf –forest
4 S root      1007     1  0  80   0 - 20739 poll_s Feb07 ? 00:00:00 /usr/sbin/sshd -D
4 S root     11239  1007  0  80   0 - 32881 poll_s Feb08 ? 00:00:00  _ sshd: vagrant [priv]
5 S vagrant  11242 11239  0  80   0 - 32881 poll_s Feb08 ? 00:00:02      _ sshd: vagrant@pts/2
0 S vagrant  11243 11242  0  80   0 - 28838 wait   Feb08 pts/2 00:00:01          _ -bash
4 S root     16052 11243  0  80   0 - 47343 poll_s 01:39 pts/2 00:00:00              _ sudo bonnie++ -n 0 -u 0 -r 239 -s 478 -f -b -d /tmp
4 S root     16053 16052 32  80   0 - 96398 hrtime 01:39 pts/2 00:00:03                  _ bonnie++ -n 0 -u 0 -r 239 -s 478 -f -b -d /tmp

通过审查上述输出,我们可以看到bonnie++进程实际上是进程16052的子进程,后者是11243的另一个子进程,后者是vagrant用户的 bash shell。

前面的ps命令告诉我们,进程 ID 为16053bonnie++进程正在等待 I/O 任务。但是,这并没有告诉我们这个进程正在使用多少 I/O;为了确定这一点,我们可以读取/proc文件系统中的一个特殊文件,名为io

# cat /proc/16053/io
rchar: 1002448848
wchar: 1002438751
syscr: 122383
syscw: 122375
read_bytes: 1002704896
write_bytes: 1002438656
cancelled_write_bytes: 0

每个运行的进程在/proc中都有一个与进程id同名的子文件夹;对于我们的示例,这是/proc/16053。这个文件夹由内核维护,用于每个运行的进程,在这些文件夹中存在许多包含有关运行进程信息的文件。

这些文件非常有用,它们实际上是ps命令信息的来源之一。其中一个有用的文件名为ioio文件包含有关进程执行的读取和写入次数的统计信息。

从 cat 命令的输出中,我们可以看到这个进程已经读取和写入了大约 1GB 的数据。虽然这看起来很多,但可能是在很长一段时间内完成的。为了了解这个进程向磁盘写入了多少数据,我们可以再次读取这个文件以捕捉差异。

# cat /proc/16053/io
cat: /proc/16053/io: No such file or directory

然而,当我们第二次执行 cat 命令时,我们收到了一个错误,即io文件不再存在。如果我们再次运行ps命令并使用grep在输出中搜索bonnie++进程,我们会发现bonnie++进程正在运行;但是,它是一个新的进程,具有新的进程ID

# ps -elf | grep bonnie
4 S root     17891 11243  0  80   0 - 47343 poll_s 02:34 pts/2 00:00:00 sudo bonnie++ -n 0 -u 0 -r 239 -s 478 -f -b -d /tmp
4 D root     17892 17891 33  80   0 -  4233 sleep_ 02:34 pts/2 00:00:02 bonnie++ -n 0 -u 0 -r 239 -s 478 -f -b -d /tmp

由于bonnie++子进程是短暂的进程,通过读取io文件来跟踪 I/O 统计可能会非常困难。

iotop - 一个用于磁盘 I/O 的类似 top 的命令

由于这些进程频繁启动和停止,我们可以使用iotop命令来确定哪些进程最多地利用了 I/O。

# iotop
Total DISK READ :     102.60 M/s | Total DISK WRITE :      26.96 M/s
Actual DISK READ:     102.60 M/s | Actual DISK WRITE:      42.04 M/s
 TID  PRIO  USER     DISK READ  DISK WRITE  SWAPIN     IO> COMMAND
16395 be/4 root        0.00 B/s    0.00 B/s  0.00 % 45.59 % [kworker/u4:0]
18250 be/4 root      101.95 M/s   26.96 M/s  0.00 % 42.59 % bonnie++ -n 0 -u 0 -r 239 -s 478 -f -b -d /tmp

iotop的输出中,我们可以看到一些有趣的 I/O 统计信息。通过iotop,我们不仅可以看到系统范围的统计信息,比如每秒的总磁盘读取总磁盘写入,还可以看到单个进程的许多统计信息。

从每个进程的角度来看,我们可以看到bonnie++进程正在以 101.96 MBps 的速度从磁盘读取数据,并以 26.96 MBps 的速度向磁盘写入数据。

16395 be/4 root        0.00 B/s    0.00 B/s  0.00 % 45.59 % [kworker/u4:0]
18250 be/4 root      101.95 M/s   26.96 M/s  0.00 % 42.59 % bonnie++ -n 0 -u 0 -r 239 -s 478 -f -b -d /tmp

iotop命令与 top 命令非常相似,它会每隔几秒刷新报告的结果。这样做的效果是实时显示 I/O 统计信息。

提示

诸如topiotop之类的命令在书本格式中很难展示。我强烈建议在具有这些命令的系统上执行这些命令,以了解它们的工作方式。

整合起来

现在我们已经介绍了一些用于故障排除磁盘性能和利用率的工具,让我们在解决报告的缓慢时将它们整合起来。

使用 iostat 来确定是否存在 I/O 带宽问题

我们将首先运行的命令是iostat,因为这将首先为我们验证是否确实存在问题。

# iostat -x 10 3
Linux 3.10.0-123.el7.x86_64 (blog.example.com)   02/09/2015 _x86_64_  (2 CPU)

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
 38.58    0.00    3.22    5.46    0.00   52.75

Device:         rrqm/s   wrqm/s     r/s     w/s    rkB/s    wkB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
sda              10.86     4.25  122.46  118.15 11968.97 12065.60 199.78    13.27   55.18    0.67  111.67   0.51  12.21
dm-0              0.00     0.00   14.03    3.44    56.14    13.74 8.00     0.42   24.24    0.51  121.15   0.46   0.80
dm-1              0.00     0.00  119.32  112.35 11912.79 12051.98 206.89    13.52   58.33    0.68  119.55   0.52  12.16

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
 7.96    0.00   14.60   29.31    0.00   48.12

Device:         rrqm/s   wrqm/s     r/s     w/s    rkB/s    wkB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
sda               0.70     0.80  804.49  776.85 79041.12 76999.20 197.35    64.26   41.41    0.54   83.73   0.42  66.38
dm-0              0.00     0.00    0.90    0.80     3.59     3.19 8.00     0.08   50.00    0.00  106.25  19.00   3.22
dm-1              0.00     0.00  804.29  726.35 79037.52 76893.81 203.75    64.68   43.03    0.53   90.08   0.44  66.75

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
 5.22    0.00   11.21   36.21    0.00   47.36

Device:         rrqm/s   wrqm/s     r/s     w/s    rkB/s    wkB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
sda               1.10     0.30  749.40  429.70 84589.20 43619.80 217.47    76.31   66.49    0.43  181.69   0.58  68.32
dm-0              0.00     0.00    1.30    0.10     5.20     0.40 8.00     0.00    2.21    1.00   18.00   1.43   0.20
dm-1              0.00     0.00  749.00  391.20 84558.40 41891.80 221.80    76.85   69.23    0.43  200.95   0.60  68.97

iostat的输出中,我们可以确定以下信息:

  • 该系统的 CPU 目前花费了相当多的时间在等待 I/O,占 30%–40%。

  • 看起来dm-1sda设备是利用率最高的设备

  • iostat来看,这些设备的利用率为 68%,这个数字似乎相当高

基于这些数据点,我们可以确定存在潜在的 I/O 利用率问题,除非 68%的利用率是预期的。

使用 iotop 来确定哪些进程正在消耗磁盘带宽

现在我们已经确定了大量的 CPU 时间被用于等待 I/O,我们现在应该关注哪些进程最多地利用了磁盘。为了做到这一点,我们将使用iotop命令。

# iotop
Total DISK READ :     100.64 M/s | Total DISK WRITE :      23.91 M/s
Actual DISK READ:     100.67 M/s | Actual DISK WRITE:      38.04 M/s
 TID  PRIO  USER     DISK READ  DISK WRITE  SWAPIN     IO> COMMAND
19358 be/4 root        0.00 B/s    0.00 B/s  0.00 % 40.38 % [kworker/u4:1]
20262 be/4 root      100.35 M/s   23.91 M/s  0.00 % 33.65 % bonnie++ -n 0 -u 0 -r 239 -s 478 -f -b -d /tmp
 363 be/4 root        0.00 B/s    0.00 B/s  0.00 %  2.51 % [xfsaild/dm-1]
 32 be/4 root        0.00 B/s    0.00 B/s  0.00 %  1.74 % [kswapd0]

iotop命令中,我们可以看到进程20262,它正在运行bonnie++命令,具有高利用率以及大量的磁盘读写值。

iotop中,我们可以确定以下信息:

  • 系统的每秒总磁盘读取量为 100.64 MBps

  • 系统的每秒总磁盘写入量为 23.91 MBps

  • 运行bonnie++命令的进程20262正在读取 100.35 MBps,写入 23.91 MBps

  • 比较总数,我们发现进程20262是磁盘读写的主要贡献者

鉴于上述情况,似乎我们需要更多地了解进程20262的信息。

使用 ps 来更多地了解进程

现在我们已经确定了一个使用大量 I/O 的进程,我们可以使用ps命令来调查这个进程的详细信息。我们将再次使用带有--forest标志的ps命令来显示父进程和子进程的关系。

# ps -elf --forest
1007  0  80   0 - 32881 poll_s Feb08 ?        00:00:00  _ sshd: vagrant [priv]
5 S vagrant  11242 11239  0  80   0 - 32881 poll_s Feb08 ? 00:00:05      _ sshd: vagrant@pts/2
0 S vagrant  11243 11242  0  80   0 - 28838 wait   Feb08 pts/2 00:00:02          _ -bash
4 S root     20753 11243  0  80   0 - 47343 poll_s 03:52 pts/2 00:00:00              _ sudo bonnie++ -n 0 -u 0 -r 239 -s 478 -f -b -d /tmp
4 D root     20754 20753 52  80   0 -  4233 sleep_ 03:52 pts/2 00:00:01                  _ bonnie++ -n 0 -u 0 -r 239 -s 478 -f -b -d /tmp

使用ps命令,我们可以确定以下内容:

  • iotop识别的bonnie++进程20262不见了;然而,其他bonnie++进程存在

  • vagrant用户已经通过使用sudo命令启动了父bonnie++进程

  • vagrant用户与早期观察中讨论的 CPU 和内存部分的用户相同

鉴于上述细节,似乎vagrant用户是我们性能问题的嫌疑人。

网络

性能问题的最后一个常见资源是网络。有许多工具可以用来排除网络问题;然而,这些命令中很少有专门针对网络性能的。大多数这些工具都是为深入的网络故障排除而设计的。

由于第五章,网络故障排除专门用于解决网络问题,本节将专门关注性能。

ifstat - 查看接口统计

在网络方面,有大约四个指标可以用来衡量吞吐量。

  • 接收数据包:接口接收的数据包数量

  • 发送数据包:接口发送的数据包数量

  • 接收数据:接口接收的数据量

  • 发送数据:接口发送的数据量

有许多命令可以提供这些指标,从ifconfigipnetstat都有。一个非常有用的专门输出这些指标的实用程序是ifstat命令。

# ifstat
#21506.1804289383 sampling_interval=5 time_const=60
Interface   RX Pkts/Rate   TX Pkts/Rate   RX Data/Rate   TX Data/Rate
 RX Errs/Drop   TX Errs/Drop   RX Over/Rate   TX Coll/Rate
lo              47 0            47 0         4560 0          4560 0
 0 0             0 0            0 0             0 0
enp0s3       70579 1         50636 0      17797K 65        5520K 96
 0 0             0 0            0 0             0 0
enp0s8       23034 0            43 0       2951K 18          7035 0
 0 0             0 0            0 0             0 0

vmstatiostat类似,ifstat生成的第一个报告是基于服务器上次重启以来的统计数据。这意味着上面的报告表明enp0s3接口自上次重启以来已接收了 70,579 个数据包。

当第二次执行ifstat时,结果将与第一个报告有很大的差异。原因是第二个报告是基于自第一个报告以来的时间。

# ifstat
#21506.1804289383 sampling_interval=5 time_const=60
Interface   RX Pkts/Rate    TX Pkts/Rate   RX Data/Rate  TX Data/Rate
 RX Errs/Drop    TX Errs/Drop   RX Over/Rate  TX Coll/Rate
lo                0 0             0 0             0 0             0 0
 0 0             0 0             0 0             0 0
enp0s3           23 0            18 0         1530 59         1780 80
 0 0             0 0             0 0             0 0
enp0s8            1 0             0 0           86 10             0 0
 0 0             0 0             0 0             0 0

在上面的例子中,我们可以看到我们的系统通过enp0s3接口接收了 23 个数据包(RX Pkts)并发送了 18 个数据包(TX Pkts)。

通过ifstat命令,我们可以确定以下关于我们的系统的内容:

  • 目前的网络利用率相当小,不太可能对整个系统造成影响

  • 早期显示的vagrant用户的进程不太可能利用大量网络资源

根据ifstat所见的统计数据,在这个系统上几乎没有网络流量,不太可能导致感知到的缓慢。

对我们已经确定的内容进行快速回顾

在继续之前,让我们回顾一下到目前为止我们从性能统计数据中学到的东西:

注意

vagrant用户一直在启动运行bonnie++lookbusy应用程序的进程。

lookbusy应用程序似乎要么一直占用整个系统 CPU 的 20%–30%。

这个服务器有两个 CPU,lookbusy似乎一直占用一个 CPU 的大约 60%。

lookbusy应用程序似乎也一直使用大约 200 MB 的内存;然而,在故障排除期间,我们确实看到这些进程几乎使用了系统的所有内存,导致系统交换。

在启动bonnie++进程时,vagrant用户的系统经历了高 I/O 等待时间。

在运行时,bonnie++进程利用了大约 60%–70%的磁盘吞吐量。

vagrant用户正在执行的活动似乎对网络利用率几乎没有影响。

比较历史指标

从迄今为止我们了解到的所有事实来看,我们下一个最佳行动方案似乎是建议联系vagrant用户,以确定lookbusybonnie++应用程序是否应该以如此高的资源利用率运行。

尽管先前的观察显示了高资源利用率,但这种利用率水平可能是预期的。在开始联系用户之前,我们应该首先审查服务器的历史性能指标。在大多数环境中,都会有一些服务器性能监控软件,如 Munin、Cacti 或许多云 SaaS 提供商之一,用于收集和存储系统统计信息。

如果您的环境使用了这些服务,您可以使用收集的性能数据来将以前的性能统计与我们刚刚收集到的信息进行比较。例如,在过去 30 天中,CPU 性能从未超过 10%,那么lookbusy进程可能在那个时候没有运行。

即使您的环境没有使用这些工具之一,您仍然可以执行历史比较。为此,我们将使用一个默认安装在大多数 Red Hat Enterprise Linux 系统上的工具;这个工具叫做sar

sar – 系统活动报告

在第二章,故障排除命令和有用信息来源中,我们简要讨论了使用sar命令来查看历史性能统计信息。

当安装了部署sar实用程序的sysstat软件包时,它将部署/etc/cron.d/sysstat文件。在这个文件中有两个cron作业,运行sysstat命令,其唯一目的是收集系统性能统计信息并生成收集信息的报告。

$ cat /etc/cron.d/sysstat
# Run system activity accounting tool every 10 minutes
*/2 * * * * root /usr/lib64/sa/sa1 1 1
# 0 * * * * root /usr/lib64/sa/sa1 600 6 &
# Generate a daily summary of process accounting at 23:53
53 23 * * * root /usr/lib64/sa/sa2 -A

当执行这些命令时,收集的信息将存储在/var/log/sa/文件夹中。

# ls -la /var/log/sa/
total 1280
drwxr-xr-x. 2 root root   4096 Feb  9 00:00 .
drwxr-xr-x. 9 root root   4096 Feb  9 03:17 ..
-rw-r--r--. 1 root root  68508 Feb  1 23:20 sa01
-rw-r--r--. 1 root root  40180 Feb  2 16:00 sa02
-rw-r--r--. 1 root root  28868 Feb  3 05:30 sa03
-rw-r--r--. 1 root root  91084 Feb  4 20:00 sa04
-rw-r--r--. 1 root root  57148 Feb  5 23:50 sa05
-rw-r--r--. 1 root root  34524 Feb  6 23:50 sa06
-rw-r--r--. 1 root root 105224 Feb  7 23:50 sa07
-rw-r--r--. 1 root root 235312 Feb  8 23:50 sa08
-rw-r--r--. 1 root root 105224 Feb  9 06:00 sa09
-rw-r--r--. 1 root root  56616 Jan 23 23:00 sa23
-rw-r--r--. 1 root root  56616 Jan 24 20:10 sa24
-rw-r--r--. 1 root root  24648 Jan 30 23:30 sa30
-rw-r--r--. 1 root root  11948 Jan 31 23:20 sa31
-rw-r--r--. 1 root root  44476 Feb  5 23:53 sar05
-rw-r--r--. 1 root root  27244 Feb  6 23:53 sar06
-rw-r--r--. 1 root root  81094 Feb  7 23:53 sar07
-rw-r--r--. 1 root root 180299 Feb  8 23:53 sar08

sysstat软件包生成的数据文件使用遵循“sa<两位数的日期>”格式的文件名。例如,在上面的输出中,我们可以看到“sa24”文件是在 1 月 24 日生成的。我们还可以看到这个系统有从 1 月 23 日到 2 月 9 日的文件。

sar命令是一个允许我们读取这些捕获的性能指标的命令。本节将向您展示如何使用sar命令来查看与iostattopvmstat等命令之前查看的相同统计信息。然而,这次sar命令将提供最近和历史信息。

CPU

要使用sar命令查看 CPU 统计信息,我们可以简单地使用–u(CPU 利用率)标志。

# sar -u
Linux 3.10.0-123.el7.x86_64 (blog.example.com)   02/09/2015   _x86_64_  (2 CPU)

12:00:01 AM     CPU     %user     %nice   %system   %iowait    %steal %idle
12:10:02 AM     all      7.42      0.00     13.46     37.51      0.00 41.61
12:20:01 AM     all      7.59      0.00     13.61     38.55      0.00 40.25
12:30:01 AM     all      7.44      0.00     13.46     38.50      0.00 40.60
12:40:02 AM     all      8.62      0.00     15.71     31.42      0.00 44.24
12:50:02 AM     all      8.77      0.00     16.13     29.66      0.00 45.44
01:00:01 AM     all      8.88      0.00     16.20     29.43      0.00 45.49
01:10:01 AM     all      7.46      0.00     13.64     37.29      0.00 41.61
01:20:02 AM     all      7.35      0.00     13.52     37.79      0.00 41.34
01:30:01 AM     all      7.40      0.00     13.36     38.60      0.00 40.64
01:40:01 AM     all      7.42      0.00     13.53     37.86      0.00 41.19
01:50:01 AM     all      7.44      0.00     13.58     38.38      0.00 40.60
04:20:02 AM     all      7.51      0.00     13.72     37.56      0.00 41.22
04:30:01 AM     all      7.34      0.00     13.36     38.56      0.00 40.74
04:40:02 AM     all      7.40      0.00     13.41     37.94      0.00 41.25
04:50:01 AM     all      7.45      0.00     13.81     37.73      0.00 41.01
05:00:02 AM     all      7.49      0.00     13.75     37.72      0.00 41.04
05:10:01 AM     all      7.43      0.00     13.30     39.28      0.00 39.99
05:20:02 AM     all      7.24      0.00     13.17     38.52      0.00 41.07
05:30:02 AM     all     13.47      0.00     11.10     31.12      0.00 44.30
05:40:01 AM     all     67.05      0.00      1.92      0.00      0.00 31.03
05:50:01 AM     all     68.32      0.00      1.85      0.00      0.00 29.82
06:00:01 AM     all     69.36      0.00      1.76      0.01      0.00 28.88
06:10:01 AM     all     70.53      0.00      1.71      0.01      0.00 27.76
Average:        all     14.43      0.00     12.36     33.14      0.00 40.07

如果我们从上面的头信息中查看,我们可以看到带有-u标志的sar命令与iostat和 top CPU 详细信息相匹配。

12:00:01 AM     CPU     %user     %nice   %system   %iowait    %steal %idle

sar -u的输出中,我们可以发现一个有趣的趋势:从 00:00 到 05:30,CPU I/O 等待时间保持在 30%–40%。然而,从 05:40 开始,I/O 等待时间减少,但用户级 CPU 利用率增加到 65%–70%。

尽管这两个测量并没有明确指向任何一个过程,但它们表明 I/O 等待时间最近已经减少,而用户 CPU 时间已经增加。

为了更好地了解历史统计信息,我们需要查看前一天的 CPU 利用率。幸运的是,我们可以使用–f(文件名)标志来做到这一点。–f标志将允许我们为sar命令指定一个历史文件。这将允许我们有选择地查看前一天的统计信息。

# sar -f /var/log/sa/sa07 -u
Linux 3.10.0-123.el7.x86_64 (blog.example.com)   02/07/2015 _x86_64_  (2 CPU)

12:00:01 AM     CPU     %user     %nice   %system   %iowait    %steal %idle
12:10:01 AM     all     24.63      0.00      0.71      0.00      0.00 74.66
12:20:01 AM     all     25.31      0.00      0.70      0.00      0.00 73.99
01:00:01 AM     all     27.59      0.00      0.68      0.00      0.00 71.73
01:10:01 AM     all     29.64      0.00      0.71      0.00      0.00 69.65
05:10:01 AM     all     44.09      0.00      0.63      0.00      0.00 55.28
05:20:01 AM     all     60.94      0.00      0.58      0.00      0.00 38.48
05:30:01 AM     all     62.32      0.00      0.56      0.00      0.00 37.12
05:40:01 AM     all     63.74      0.00      0.56      0.00      0.00 35.70
05:50:01 AM     all     65.08      0.00      0.56      0.00      0.00 34.35
0.00     76.07
Average:        all     37.98      0.00      0.65      0.00      0.00 61.38

在 2 月 7 日的报告中,我们可以看到 CPU 利用率与我们之前的故障排除所发现的情况有很大的不同。一个突出的问题是,在 7 日的报告中,没有 CPU 时间花费在 I/O 等待状态。

然而,我们看到用户 CPU 时间根据一天中的时间波动从 20%到 65%不等。这可能表明预期会有更高的用户 CPU 时间利用率。

内存

要显示内存统计信息,我们可以使用带有-r(内存)标志的sar命令。

# sar -r
Linux 3.10.0-123.el7.x86_64 (blog.example.com)   02/09/2015 _x86_64_  (2 CPU)

12:00:01 AM kbmemfree kbmemused  %memused kbbuffers  kbcached kbcommit   %commit  kbactive   kbinact   kbdirty
12:10:02 AM     38228    463832     92.39         0    387152 446108     28.17    196156    201128         0
12:20:01 AM     38724    463336     92.29         0    378440 405128     25.59    194336    193216     73360
12:30:01 AM     38212    463848     92.39         0    377848 405128     25.59      9108    379348     58996
12:40:02 AM     37748    464312     92.48         0    387500 446108     28.17    196252    201684         0
12:50:02 AM     33028    469032     93.42         0    392240 446108     28.17    196872    205884         0
01:00:01 AM     34716    467344     93.09         0    380616 405128     25.59    195900    195676     69332
01:10:01 AM     31452    470608     93.74         0    384092 396660     25.05    199100    196928     74372
05:20:02 AM     38756    463304     92.28         0    387120 399996     25.26    197184    198456         4
05:30:02 AM    187652    314408     62.62         0     19988 617000     38.97    222900     22524         0
05:40:01 AM    186896    315164     62.77         0     20116 617064     38.97    223512     22300         0
05:50:01 AM    186824    315236     62.79         0     20148 617064     38.97    223788     22220         0
06:00:01 AM    182956    319104     63.56         0     24652 615888     38.90    226744     23288         0
06:10:01 AM    176992    325068     64.75         0     29232 615880     38.90    229356     26500         0
06:20:01 AM    176756    325304     64.79         0     29480 615884     38.90    229448     26588         0
06:30:01 AM    176636    325424     64.82         0     29616 615888     38.90    229516     26820         0
Average:        77860    424200     84.49         0    303730 450102     28.43    170545    182617     29888

再次,如果我们查看sar的内存报告标题,我们可以看到一些熟悉的值。

12:00:01 AM kbmemfree kbmemused  %memused kbbuffers  kbcached kbcommit   %commit  kbactive   kbinact   kbdirty

从这份报告中,我们可以看到在 05:40 时,系统突然释放了 150MB 的物理内存。从kbcached列可以看出,这 150MB 的内存被分配给了磁盘缓存。这是基于 05:40 时,缓存内存从 196MB 下降到 22MB 的事实。

有趣的是,这与 CPU 利用率的变化在 05:40 也是一致的。如果我们希望回顾历史内存利用情况,我们也可以使用带有-f(文件名)标志和-r(内存)标志。然而,由于我们可以看到 05:40 有一个相当明显的趋势,我们现在将重点放在这个时间上。

磁盘

要显示今天的磁盘统计信息,我们可以使用-d(块设备)标志。

# sar -d
Linux 3.10.0-123.el7.x86_64 (blog.example.com)   02/09/2015 _x86_64_  (2 CPU)

12:00:01 AM       DEV       tps  rd_sec/s  wr_sec/s  avgrq-sz  avgqu-sz     await     svctm     %util
12:10:02 AM    dev8-0   1442.64 150584.15 146120.49    205.67 82.17     56.98      0.51     74.17
12:10:02 AM  dev253-0      1.63     11.11      1.96      8.00 0.06     34.87     19.72      3.22
12:10:02 AM  dev253-1   1402.67 150572.19 146051.96    211.47 82.73     58.98      0.53     74.68
04:20:02 AM    dev8-0   1479.72 152799.09 150240.77    204.80 81.27     54.89      0.50     73.86
04:20:02 AM  dev253-0      1.74     10.98      2.96      8.00 0.06     31.81     14.60      2.54
04:20:02 AM  dev253-1   1438.57 152788.11 150298.01    210.69 81.84     56.83      0.52     74.38
05:30:02 AM  dev253-0      1.00      7.83      0.17      8.00 0.00      3.81      2.76      0.28
05:30:02 AM  dev253-1   1170.61 123647.27 122655.72    210.41 69.12     59.04      0.53     62.20
05:40:01 AM    dev8-0      0.08      1.00      0.34     16.10 0.00      1.88      1.00      0.01
05:40:01 AM  dev253-0      0.11      0.89      0.00      8.00 0.00      1.57      0.25      0.00
05:40:01 AM  dev253-1      0.05      0.11      0.34      8.97 0.00      2.77      1.17      0.01
05:50:01 AM    dev8-0      0.07      0.49      0.28     11.10 0.00      1.71      1.02      0.01
05:50:01 AM  dev253-0      0.06      0.49      0.00      8.00 0.00      2.54      0.46      0.00
05:50:01 AM  dev253-1      0.05      0.00      0.28      6.07 0.00      1.96      0.96      0.00

Average:          DEV       tps  rd_sec/s  wr_sec/s  avgrq-sz avgqu-sz     await     svctm     %util
Average:       dev8-0   1215.88 125807.06 123583.62    205.11 66.86     55.01      0.50     60.82
Average:     dev253-0      2.13     12.48      4.53      8.00 0.10     44.92     17.18      3.65
Average:     dev253-1   1181.94 125794.56 123577.42    210.99 67.31     56.94      0.52     61.17

默认情况下,sar命令将打印设备名称为“dev-”,这可能有点令人困惑。如果添加了-p(持久名称)标志,设备名称将使用持久名称,与挂载命令中的设备匹配。

# sar -d -p
Linux 3.10.0-123.el7.x86_64 (blog.example.com)   08/16/2015 _x86_64_  (4 CPU)

01:46:42 AM       DEV       tps  rd_sec/s  wr_sec/s  avgrq-sz  avgqu-sz     await     svctm     %util
01:48:01 AM       sda      0.37      0.00      3.50      9.55 0.00      1.86      0.48      0.02
01:48:01 AM rhel-swap      0.00      0.00      0.00      0.00 0.00      0.00      0.00      0.00
01:48:01 AM rhel-root      0.37      0.00      3.50      9.55 0.00      2.07      0.48      0.02

即使名称以不可识别的格式显示,我们也可以看到dev253-1似乎在 05:40 之前有相当多的活动,磁盘tps(每秒事务)从 1170 下降到 0.11。磁盘 I/O 利用率的大幅下降似乎表明今天在 05:40 发生了相当大的变化。

网络

要显示网络统计信息,我们需要使用带有-n DEV标志的sar命令。

# sar -n DEV
Linux 3.10.0-123.el7.x86_64 (blog.example.com)   02/09/2015 _x86_64_  (2 CPU)

12:00:01 AM     IFACE   rxpck/s   txpck/s    rxkB/s    txkB/s rxcmp/s   txcmp/s  rxmcst/s
12:10:02 AM    enp0s3      1.51      1.18      0.10      0.12 0.00      0.00      0.00
12:10:02 AM    enp0s8      0.14      0.00      0.02      0.00 0.00      0.00      0.07
12:10:02 AM        lo      0.00      0.00      0.00      0.00 0.00      0.00      0.00
12:20:01 AM    enp0s3      0.85      0.85      0.05      0.08 0.00      0.00      0.00
12:20:01 AM    enp0s8      0.18      0.00      0.02      0.00 0.00      0.00      0.08
12:20:01 AM        lo      0.00      0.00      0.00      0.00 0.00      0.00      0.00
12:30:01 AM    enp0s3      1.45      1.16      0.10      0.11 0.00      0.00      0.00
12:30:01 AM    enp0s8      0.18      0.00      0.03      0.00 0.00      0.00      0.08
12:30:01 AM        lo      0.00      0.00      0.00      0.00 0.00      0.00      0.00
05:20:02 AM        lo      0.00      0.00      0.00      0.00 0.00      0.00      0.00
05:30:02 AM    enp0s3      1.23      1.02      0.08      0.11 0.00      0.00      0.00
05:30:02 AM    enp0s8      0.15      0.00      0.02      0.00 0.00      0.00      0.04
05:30:02 AM        lo      0.00      0.00      0.00      0.00 0.00      0.00      0.00
05:40:01 AM    enp0s3      0.79      0.78      0.05      0.14 0.00      0.00      0.00
05:40:01 AM    enp0s8      0.18      0.00      0.02      0.00 0.00      0.00      0.08
05:40:01 AM        lo      0.00      0.00      0.00      0.00 0.00      0.00      0.00
05:50:01 AM    enp0s3      0.76      0.75      0.05      0.13 0.00      0.00      0.00
05:50:01 AM    enp0s8      0.16      0.00      0.02      0.00 0.00      0.00      0.07
05:50:01 AM        lo      0.00      0.00      0.00      0.00 0.00      0.00      0.00
06:00:01 AM    enp0s3      0.67      0.60      0.04      0.10 0.00      0.00      0.00

在网络统计报告中,我们看到整天都没有变化。这表明,总体上,这台服务器从未出现与网络性能瓶颈相关的问题。

通过比较历史统计数据来回顾我们所学到的内容

通过使用sar查看历史统计数据和使用psiostatvmstattop等命令查看最近的统计数据后,我们可以得出关于我们的“性能慢”的以下结论。

由于我们被同事要求调查这个问题,我们的结论将以电子邮件回复的形式发送给这位同事。

嗨鲍勃!

我调查了一个用户说服务器“慢”的服务器。看起来用户 vagrant 一直在运行两个主要程序的多个实例。第一个是 lookbusy 应用程序,似乎始终使用大约 20%–40%的 CPU。然而,至少有一个实例中,lookbusy 应用程序还使用了大量内存,耗尽了物理内存并迫使系统大量交换。然而,这个过程并没有持续很长时间。

第二个程序是 bonnie++应用程序,似乎利用了大量的磁盘 I/O 资源。当 vagrant 用户运行 bonnie++应用程序时,它占用了大约 60%的 dm-1 和 sda 磁盘带宽,导致了大约 30%的高 I/O 等待。通常,这个系统的 I/O 等待为 0%(通过 sar 确认)。

看起来 vagrant 用户可能正在运行超出预期水平的应用程序,导致其他用户的性能下降。

总结

在本章中,我们开始使用一些高级的 Linux 命令,这些命令在第二章中进行了探索,例如iostatvmstat。我们还对 Linux 中的一个基本实用程序ps命令非常熟悉,同时解决了一个模糊的性能问题。

在第三章中,故障排除 Web 应用程序,我们能够从数据收集到试错的完整故障排除过程,而在本章中,我们的行动主要集中在数据收集和建立假设阶段。发现自己只是在解决问题而不是执行纠正措施是非常常见的。有许多问题应该由系统的用户而不是系统管理员来解决,但管理员的角色仍然是识别问题的来源。

在第五章中,网络故障排除,我们将解决一些非常有趣的网络问题。网络对于任何系统都至关重要;问题有时可能很简单,而有时则非常复杂。在下一章中,我们将探讨网络和如何使用诸如netstattcpdump之类的工具来排除网络问题。

第五章:网络故障排除

在第三章中,故障排除 Web 应用程序,我们深入研究了故障排除 Web 应用程序;虽然我们解决了一个复杂的应用程序错误,但我们完全跳过了 Web 应用程序的网络方面。在本章中,我们将调查一个报告的问题,这将引导我们了解 DNS、路由,当然还有 RHEL 系统的网络配置等概念。

对于任何 Linux 系统管理员来说,网络是一项必不可少的技能。引用一位过去的讲师的话:

没有网络的服务器对每个人都是无用的。

作为系统管理员,您管理的每台服务器或台式机都将有某种网络连接。无论这种网络连接是在隔离的公司网络内还是直接连接到互联网,都涉及到网络。

由于网络是一个如此关键的主题,本章将涵盖网络和网络连接的许多方面;然而,它不会涵盖防火墙。防火墙故障排除和配置实际上将在第六章中进行,诊断和纠正防火墙问题

数据库连接问题

在第三章中,故障排除 Web 应用程序,我们正在解决公司博客的问题。在本章中,我们将再次解决这个博客;然而,今天的问题有点不同。

到达当天后,我们接到一位开发人员的电话,他说:“WordPress 博客返回了一个无法连接到数据库的错误”。

数据收集

根据我们一直遵循的故障排除过程,下一步是尽可能收集关于问题的数据。信息的最佳来源之一是报告问题的人;对于这种情况,我们将问两个基本问题:

  • 我如何复制问题并查看错误?

  • 最近 WordPress 应用有什么变化吗?

当被问及时,开发人员表示我们只需在网页浏览器中访问博客就可以看到错误。在第二个问题上,开发人员告诉我们,数据库服务最近从 Web 服务器移动到了一个新的专用数据库服务器。他还提到这个移动是在几天前发生的,并且应用程序一直到今天都在工作。

由于数据库服务是几天前移动的,而且应用程序直到今天早上都在工作,所以这个改变不太可能引起问题。然而,我们不应该排除这种可能性。

复制问题

正如前几章讨论的,关键的数据收集任务是复制问题。我们这样做不仅是为了验证报告的问题是否确实存在,还为了找出可能没有被报告的任何其他错误。

由于开发人员表示我们可以直接访问博客来复制这个问题,我们将在网页浏览器中进行操作。

似乎我们可以很容易地复制这个问题。根据这个错误,似乎应用程序只是在说它在建立数据库连接时出现了问题。虽然这本身并不意味着问题与网络有关,但也可能是。问题也可能只是数据库服务本身的问题。

为了确定问题是网络问题还是数据库服务问题,我们首先需要找出应用程序配置为连接到哪个服务器。

查找数据库服务器

与上一章类似,我们将通过查看应用程序配置文件来确定应用程序使用的服务器。根据我们在第三章中的先前故障排除,故障排除 Web 应用程序,我们知道 WordPress 应用程序托管在blog.example.com上。首先,我们将登录到博客的 Web 服务器并查看 WordPress 配置文件。

$ ssh blog.example.com -l vagrant
vagrant@blog.example.com's password:
Last login: Sat Feb 28 18:49:40 2015 from 10.0.2.2
[blog]$

提示

由于我们将针对多个系统执行命令,因此本章的示例将在命令行提示中包含主机名,如blogdb

我们在第三章中学到,WordPress 数据库配置存储在/var/www/html/wp-config.php文件中。为了快速搜索该文件以获取数据库信息,我们可以使用grep命令搜索字符串DB,因为在我们先前的事件中,该字符串出现在数据库配置中。

[blog]$ grep DB wp-config.php
define('DB_NAME', 'wordpress');
define('DB_USER', 'wordpress');
define('DB_PASSWORD', 'password');
define('DB_HOST', 'db.example.com');
define('DB_CHARSET', 'utf8');
define('DB_COLLATE', '');

通过上述内容,我们可以看到该应用程序当前配置为连接到db.example.com。简单的第一步故障排除是尝试手动连接到数据库。手动测试数据库连接的简单方法是使用telnet命令。

测试连接

telnet命令是一个非常有用的网络和网络服务故障排除工具,因为它旨在简单地建立到指定主机和端口的基于 TCP 的网络连接。在我们的例子中,我们将尝试连接到主机db.example.com的端口3306

端口3306是 MySQL 和 MariaDB 的默认端口;在上一章中,我们已经确定了这个 Web 应用程序需要这两个数据库服务中的一个。由于在wp-config.php文件的配置中没有看到特定的端口,我们将假设数据库服务正在运行在这个默认端口上。

从 blog.example.com 进行 Telnet

首先,我们将从博客服务器本身执行telnet命令。从应用程序运行的同一服务器进行测试非常重要,因为这样可以在应用程序接收到错误的相同网络条件下进行测试。

为了使用 telnet 连接到我们的数据库服务器,我们将执行telnet命令,后面跟着我们希望连接到的主机名(db.example.com)和端口(3306)。

[blog]$ telnet db.example.com 3306
Trying 192.168.33.12...
telnet: connect to address 192.168.33.12: No route to host

Telnet 连接似乎失败了。有趣的是提供的错误;无法连接到主机错误似乎清楚地指示了潜在的网络问题。

从我们的笔记本电脑进行 Telnet

由于从博客服务器的连接尝试失败,并指示存在与网络相关的问题,我们可以尝试从我们的笔记本电脑进行相同的连接,以确定问题是在博客服务器端还是db服务器端。

为了从我们的笔记本电脑测试这种连接,我们可以再次使用telnet命令。尽管我们的笔记本电脑不一定运行 Linux 操作系统,但我们仍然可以使用这个命令。原因是telnet命令是一个跨平台实用程序;在本章中,我们将利用几个跨平台命令。虽然这样的命令可能不多,但一般来说,有几个命令适用于大多数操作系统,包括那些传统上没有广泛命令行功能的系统。

虽然一些操作系统已经从默认安装中删除了telnet客户端,但该软件仍然可以安装。在我们的例子中,笔记本电脑正在运行 OS X,该系统目前部署了telnet客户端。

[laptop]$ telnet db.example.com 3306
Trying 10.0.0.50...
Connected to 10.0.0.50.
Escape character is '^]'.
Connection closed by foreign host.

看起来我们的笔记本也无法连接到数据库服务;然而,这次错误不同。这次似乎表明连接尝试被远程服务关闭。我们也没有看到来自远程服务的消息,这表明连接从未完全建立。

使用telnet命令建立端口可用性的一个注意事项是,telnet命令将显示连接为已连接;然而,此时连接可能并没有真正建立。在使用 telnet 时的一般规则是,在收到来自远程服务的消息之前,不要假设连接成功。在我们的例子中,我们没有收到来自远程服务的消息。

Ping

由于博客服务器和我们的笔记本都无法从“db”服务器进行 telnet 连接,我们应该检查问题是否仅限于数据库服务或整个服务器的连接。测试服务器之间的连接的一个工具是ping命令,就像telnet命令一样是一个跨平台实用程序。

要使用ping命令测试与主机的连接性,我们只需执行命令,然后跟随我们希望ping的主机。

[blog]$ ping db.example.com
PING db.example.com (192.168.33.12) 56(84) bytes of data.
From blog.example.com (192.168.33.11) icmp_seq=1 Destination Host Unreachable
From blog.example.com (192.168.33.11) icmp_seq=2 Destination Host Unreachable
From blog.example.com (192.168.33.11) icmp_seq=3 Destination Host Unreachable
From blog.example.com (192.168.33.11) icmp_seq=4 Destination Host Unreachable
^C
--- db.example.com ping statistics ---
6 packets transmitted, 0 received, +4 errors, 100% packet loss, time 5008ms

ping命令的错误似乎与telnet命令的错误非常相似。为了更好地理解这个错误,让我们首先更好地了解ping命令的工作原理。

首先,在执行任何其他操作之前,ping命令将尝试解析提供的主机名。这意味着在执行任何其他操作之前,我们的 ping 执行尝试识别db.example.com的 IP 地址。

PING db.example.com (192.168.33.12) 56(84) bytes of data.

从结果中,我们可以看到ping命令将此主机解析为192.168.33.12。一旦 ping 有了 IP 地址,它将向该 IP 发送一个ICMP回显请求网络数据包。在这种情况下,这意味着它正在向192.168.33.12发送一个ICMP回显请求。

ICMP 是一种用作控制系统的网络协议。当远程主机,比如192.168.33.12接收到ICMP回显请求网络数据包时,它应该发送一个ICMP回显回复网络数据包回到请求的主机。这种活动允许两个主机通过进行简单的网络版本的“乒乓球”来验证网络连接。

From blog.example.com (192.168.33.11) icmp_seq=1 Destination Host Unreachable

如果我们的ICMP回显请求数据包从192.168.33.12服务器没有传输过来,我们的ping命令就不会有任何输出。然而,我们收到了一个错误;这意味着另一端的系统是开启的,但两个主机之间的连接存在问题,阻止了完全的双向交流。

围绕这个问题出现的一个问题是,这个错误是否适用于博客服务器的所有网络连接,还是仅限于blogdb服务器之间的通信。我们可以通过向另一个通用地址执行ping请求来测试这一点。由于我们的系统连接到互联网,我们可以简单地使用一个常见的互联网域名。

# ping google.com
PING google.com (216.58.216.46) 56(84) bytes of data.
64 bytes from lax02s22-in-f14.1e100.net (216.58.216.46): icmp_seq=1 ttl=63 time=23.5 ms
64 bytes from lax02s22-in-f14.1e100.net (216.58.216.46): icmp_seq=2 ttl=63 time=102 ms
64 bytes from lax02s22-in-f14.1e100.net (216.58.216.46): icmp_seq=3 ttl=63 time=26.9 ms
64 bytes from lax02s22-in-f14.1e100.net (216.58.216.46): icmp_seq=4 ttl=63 time=25.6 ms
64 bytes from lax02s22-in-f14.1e100.net (216.58.216.46): icmp_seq=5 ttl=63 time=25.6 ms
^C
--- google.com ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4106ms
rtt min/avg/max/mdev = 23.598/40.799/102.156/30.697 ms

前面的例子是一个工作的ping请求和回复的例子。在这里,我们不仅可以看到Google.com解析为的 IP,还可以看到返回的ping请求。这意味着,当我们的博客服务器发送一个“ICMP 回显请求”时,远程服务器216.58.216.46会发送一个“ICMP 回显回复”。

故障排除 DNS

除了网络连接之外,pingtelnet命令告诉我们的另一件有趣的事情是db.example.com主机名的 IP 地址。然而,当我们从我们的笔记本执行这些操作时,结果似乎与从博客服务器执行这些操作时不同。

从博客服务器,我们的telnet尝试连接到192.168.33.12,与我们的ping命令相同的地址。

[blog]$ telnet db.example.com 3306
Trying 192.168.33.12...
However, from the laptop, our telnet tried to connect to 10.0.0.50, a completely different IP address.
[laptop]$ telnet db.example.com 3306
Trying 10.0.0.50...

原因很简单;看起来我们的笔记本得到了与我们的博客服务器不同的 DNS 结果。然而,如果是这种情况,这可能意味着我们的问题可能只是与 DNS 问题有关。

使用 dig 检查 DNS

DNS 是现代网络的重要组成部分。我们当前的问题就是它重要性的一个完美例子。在 WordPress 配置文件中,我们的数据库服务器设置为db.example.com。这意味着在应用服务器建立数据库连接之前,必须首先查找 IP 地址。

在许多情况下,可以相当安全地假设ping识别的 IP 地址很可能是 DNS 呈现的 IP 地址。然而,并非总是如此,正如我们可能很快发现的那样。

dig命令是一个非常有用的 DNS 故障排除命令;它非常灵活,可以用来执行许多不同类型的 DNS 请求。要验证db.example.com的 DNS,我们只需执行dig命令,然后跟上我们要查询的主机名:db.example.com

[blog]$ dig db.example.com

; <<>> DiG 9.9.4-RedHat-9.9.4-14.el7_0.1 <<>> db.example.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 15857
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;db.example.com.      IN  A

;; ANSWER SECTION:
db.example.com.    15  IN  A  10.0.0.50

;; Query time: 39 msec
;; SERVER: 10.0.2.3#53(10.0.2.3)
;; WHEN: Sun Mar 01 20:51:22 UTC 2015
;; MSG SIZE  rcvd: 59

如果我们查看dig返回的数据,我们可以看到 DNS 名称db.example.com解析为192.168.33.12,而不是10.0.0.50。我们可以在dig命令的输出的ANSWER SECTION中看到这一点。

;; ANSWER SECTION:
db.example.com.    15  IN  A  10.0.0.50

dig的一个非常有用的选项是指定要查询的服务器。在之前执行的dig中,我们可以看到服务器10.0.2.3是提供10.0.0.50地址的服务器。

;; Query time: 39 msec
;; SERVER: 10.0.2.3#53(10.0.2.3)

由于我们对这个 DNS 服务器不熟悉,我们可以通过查询谷歌的公共 DNS 服务器来进一步验证返回的结果。我们可以通过在 DNS 服务器 IP 或主机名后面添加@来实现这一点。在下面的例子中,我们请求8.8.8.8,这是谷歌公共 DNS 基础设施的一部分。

[blog]$ dig @8.8.8.8 db.example.com

; <<>> DiG 9.9.4-RedHat-9.9.4-14.el7_0.1 <<>> @8.8.8.8 example.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 42743
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;db.example.com.      IN  A

;; ANSWER SECTION:
db.example.com.    18639  IN  A  10.0.0.50

;; Query time: 39 msec
;; SERVER: 8.8.8.8#53(8.8.8.8)
;; WHEN: Sun Mar 01 22:14:53 UTC 2015
;; MSG SIZE  rcvd: 56
It seems that Google's public DNS has the same results as 10.0.2.3.

使用 nslookup 查找 DNS

另一个用于故障排除 DNS 的好工具是nslookupnslookup命令已经存在了相当长的时间。实际上,它是另一个跨平台命令,几乎存在于所有主要操作系统上。

要使用nslookup进行简单的 DNS 查找,我们只需运行命令,然后跟上要查询的 DNS 名称,类似于dig

[blog]$ nslookup db.example.com
Server:    10.0.2.3
Address:  10.0.2.3#53

Non-authoritative answer:
Name:  db.example.com
Address: 10.0.0.50

dig命令可以用于查询特定的 DNS 服务器。这可以通过两种方法来实现。第一种方法是在命令的末尾添加服务器地址。

[blog]$ nslookup db.example.com 8.8.8.8
Server:    8.8.8.8
Address:  8.8.8.8#53

Non-authoritative answer:
Name:  db.example.com
Address: 10.0.0.50

第二种方法是在交互模式下使用nslookup。要进入交互模式,只需执行nslookup而不使用其他选项。

# nslookup
>

进入交互模式后,通过输入server 来指定要使用的服务器。

# nslookup
> server 8.8.8.8
Default server: 8.8.8.8
Address: 8.8.8.8#53
>

最后,要查找 DNS 名称,我们只需输入要查询的域。

# nslookup
> server 8.8.8.8
Default server: 8.8.8.8
Address: 8.8.8.8#53
> db.example.com
Server:    8.8.8.8
Address:  8.8.8.8#53

Non-authoritative answer:
Name:  db.example.com
Address: 10.0.0.50
>
To leave the interactive mode, simply type exit.
> exit

那么为什么使用nslookup而不是dig呢?虽然dig命令非常有用,但它不是一个跨平台命令,通常只存在于 Unix 和 Linux 系统上。另一方面,nslookup命令是跨平台的,可以在大多数环境中找到,而dig命令可能不可用。作为系统管理员,熟悉许多命令是很重要的,能够使用任何可用的命令来执行任务是非常有用的。

dignslookup告诉了我们什么?

现在我们已经使用dignslookup来查询 DNS 名称db.example.com,让我们回顾一下我们找到了什么。

  • db.example.com实际上解析为10.0.0.50

  • ping命令返回了域db.example.com192.168.33.12地址。

ping命令返回一个地址,而 DNS 返回另一个地址,这是怎么回事?一个可能的原因是/etc/hosts文件中的配置。这是我们可以用简单的grep命令快速验证的事情。

[blog]$ grep example.com /etc/hosts
192.168.33.11 blog.example.com
192.168.33.12 db.example.com

关于/etc/hosts的一点说明

在创建诸如Bind这样的 DNS 服务器之前,本地的hosts文件被用来管理域名到 IP 的映射。这个文件包含了系统需要连接的每个域地址的列表。然而,随着网络从几个主机发展到成千上万甚至数百万个主机,这种方法随着时间的推移变得复杂起来。

在 Linux 和大多数 Unix 发行版中,hosts文件位于/etc/hosts。默认情况下,/etc/hosts文件中的任何条目都将覆盖 DNS 请求。这意味着,默认情况下,如果/etc/hosts文件中存在域到 IP 的映射,系统将使用该映射,而不会从另一个 DNS 系统中获取相同的域。

这是 Linux 的默认行为;但是,我们可以通过阅读/etc/nsswitch.conf文件来检查该服务器是否使用此默认配置。

[blog]$ grep hosts /etc/nsswitch.conf
hosts:      files dns

nsswitch.conf文件是一个允许管理员配置要使用哪些后端系统来查找用户、组、网络组、主机名和服务等项目的配置。例如,如果我们想要配置系统使用ldap来查找用户组,我们可以通过更改/etc/nsswitch.conf文件中的值来实现。

[blog]$ grep group /etc/nsswitch.conf
group:      files sss

根据前面grep命令的输出,博客系统配置为首先使用本地组文件,然后使用 SSSD 服务来查找用户组。要将ldap添加到此配置中,只需按所需顺序(即“ldap 文件 sss”)将其添加到列表中。

对于由hosts配置指定的 DNS,似乎我们的服务器配置为首先基于文件查找主机,然后再查找 DNS。这意味着我们的系统会在通过 DNS 查找域之前优先使用/etc/hosts文件。

DNS 总结

现在我们已经确认了 DNS 和/etc/hosts文件,我们知道有人配置了此应用服务器,使其认为db.example.com解析为192.168.33.12。这是一个错误还是一种在不使用 DNS 的情况下连接到数据库服务器的方式?

此时,现在还为时过早,但我们知道主机192.168.33.12没有向我们的博客服务器发送“ICMP 回显应答”来响应我们的“ICMP 回显请求”。

从另一个位置进行 ping

在处理网络问题时,最好尝试从多个位置或服务器进行连接。这对于数据收集类型的故障排除者可能似乎是显而易见的,但是受过教育的猜测型故障排除者可能会忽视这一极其有用的步骤。

在我们的示例中,我们将从笔记本电脑运行一个测试ping192.168.33.12

[laptop]$ ping 192.168.33.12
PING 192.168.33.12 (192.168.33.12): 56 data bytes
64 bytes from 192.168.33.12: icmp_seq=0 ttl=64 time=0.573 ms
64 bytes from 192.168.33.12: icmp_seq=1 ttl=64 time=0.425 ms
64 bytes from 192.168.33.12: icmp_seq=2 ttl=64 time=0.461 ms
^C
--- 192.168.33.12 ping statistics ---
3 packets transmitted, 3 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 0.425/0.486/0.573/0.063 ms

ping请求的结果来看,我们的笔记本电脑似乎能够无问题地连接到192.168.33.12

这告诉我们什么?实际上告诉我们很多!它告诉我们所讨论的服务器正在运行;它还确认了存在连接问题,特别是在blog.example.comdb.example.com之间。如果问题是由于db.example.com服务器宕机或配置错误引起的,我们的笔记本电脑也会受到影响。

然而事实并非如此。实际上恰恰相反;似乎我们的笔记本电脑与服务器之间的连接正常工作。

使用 cURL 测试端口连接

早些时候,当使用telnet从我们的笔记本电脑测试 MariaDB 端口时,telnet命令正在测试服务器10.0.0.50。然而,根据/etc/hosts配置,似乎期望的数据库服务器是192.168.33.12

为了验证数据库服务实际上是否正常运行,我们应该使用192.168.33.12地址执行相同的telnet测试。但是,这一次我们将使用curl而不是telnet来执行此测试。

我见过许多环境(尤其是最近)禁止安装telnet客户端或默认情况下不执行安装。对于这样的环境,有一些可以测试端口连接的工具是很重要的。如果 telnet 不可用,可以使用curl命令作为替代。

在第三章中,“故障排除 Web 应用程序”,我们使用curl命令请求网页。实际上,curl命令可以与许多不同的协议一起使用;我们在这种情况下感兴趣的协议是 Telnet 协议。

以下是使用curl从我们的笔记本连接到db.example.com服务器的端口3306的示例。

[laptop]$  curl -v telnet://192.168.33.12:3306
* Rebuilt URL to: telnet://192.168.33.12:3306/
* Hostname was NOT found in DNS cache
*   Trying 192.168.33.12...
* Connected to 192.168.33.12 (192.168.33.12) port 3306 (#0)
* RCVD IAC 106
^C

从示例中,似乎不仅笔记本能够连接到端口3306的服务器,而且curl命令还收到了来自RCVD IAC 106服务的消息。

在进行 Telnet 测试时,使用curl时,需要使用-v(详细)标志将 curl 置于详细模式。没有详细标志,curl将简单地隐藏连接细节,而连接细节正是我们要寻找的。

在前面的例子中,我们可以看到从我们的笔记本成功连接;为了进行比较,我们可以使用相同的命令从博客服务器测试连接。

[blog]$ curl -v telnet://192.168.33.12:3306
* About to connect() to 192.168.33.12 port 3306 (#0)
*   Trying 192.168.33.12...
* No route to host
* Failed connect to 192.168.33.12:3306; No route to host
* Closing connection 0
curl: (7) Failed connect to 192.168.33.12:3306; No route to host

连接尝试失败,正如预期的那样。

从上面使用curl的测试中,我们可以确定数据库服务器正在监听并接受端口3306上的连接;但是,博客服务器无法连接到数据库服务器。我们不知道的是问题是在博客服务器端还是在数据库服务器端。要确定连接的哪一端存在问题,我们需要查看网络连接的详细信息。为此,我们将使用两个命令,第一个是netstat,第二个是tcpdump

使用 netstat 显示当前网络连接

netstat命令是一个非常全面的工具,可以用于排除网络问题的许多方面。在这种情况下,我们将使用两个基本标志来打印现有的网络连接。

[blog]# netstat -na
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address State 
tcp        0      0 127.0.0.1:25            0.0.0.0:* LISTEN 
tcp        0      0 0.0.0.0:52903           0.0.0.0:* LISTEN 
tcp        0      0 0.0.0.0:3306            0.0.0.0:* LISTEN 
tcp        0      0 0.0.0.0:111             0.0.0.0:* LISTEN 
tcp        0      0 0.0.0.0:22              0.0.0.0:* LISTEN 
tcp        0      0 10.0.2.16:22            10.0.2.2:50322 ESTABLISHED
tcp        0      0 192.168.33.11:22        192.168.33.1:53359 ESTABLISHED
tcp6       0      0 ::1:25                  :::* LISTEN 
tcp6       0      0 :::57504                :::* LISTEN 
tcp6       0      0 :::111                  :::* LISTEN 
tcp6       0      0 :::80                   :::* LISTEN 
tcp6       0      0 :::22                   :::* LISTEN 
udp        0      0 0.0.0.0:5353            0.0.0.0:*
udp        0      0 0.0.0.0:68              0.0.0.0:*
udp        0      0 0.0.0.0:111             0.0.0.0:*
udp        0      0 0.0.0.0:52594           0.0.0.0:*
udp        0      0 127.0.0.1:904           0.0.0.0:*
udp        0      0 0.0.0.0:49853           0.0.0.0:*
udp        0      0 0.0.0.0:53449           0.0.0.0:*
udp        0      0 0.0.0.0:719             0.0.0.0:*
udp6       0      0 :::54762                :::*
udp6       0      0 :::58674                :::*
udp6       0      0 :::111                  :::*
udp6       0      0 :::719                  :::*
raw6       0      0 :::58                   :::*

在前面的例子中,我们使用了-n(无 dns)标志执行了netstat命令,告诉netstat不要查找 IP 的 DNS 主机名或将端口号转换为服务名称,以及-a(全部)标志,告诉netstat打印监听和非监听套接字。

这些标志的效果类似于netstat,显示所有应用程序绑定的所有网络连接和端口。

示例netstat命令显示了相当多的信息。为了更好地理解这些信息,让我们更仔细地检查一下输出。

Proto Recv-Q Send-Q Local Address         Foreign Address      State
tcp       0     0 127.0.0.1:25            0.0.0.0:*            LISTEN

The second column Recv-Q is a count of bytes received but not copied by the application by using this socket. This is basically the number of bytes waiting between the kernel receiving the data from the network and the application accepting it.
shows the local host address as 127.0.0.1 and the port as 25.

第五列是Foreign Address或远程地址。此列列出了远程服务器的 IP 和端口。由于我们之前使用的示例类型,这被列为 IP0.0.0.0和端口*,这是一个通配符,表示任何内容。

第六列,我们的最后一列,是状态套接字。对于 TCP 连接,状态将告诉我们 TCP 连接的当前状态。对于我们之前的例子,状态列为LISTEN;这告诉我们列出的套接字用于接受 TCP 连接。

如果我们将所有列放在一起,这一行告诉我们,我们的服务器正在通过 IP127.0.0.1监听端口25上的新连接,并且这是基于 TCP 的连接。

使用 netstat 来监视新连接

现在我们对netstat的输出有了更多的了解,我们可以使用它来查找应用程序服务器到数据库服务器的新连接。要使用netstat监视新连接,我们将使用netstat经常被忽视的一个功能。

vmstat命令类似,可以将netstat置于连续模式中,每隔几秒打印相同的输出。要做到这一点,只需在命令的末尾放置间隔。

在下一个例子中,我们将使用相同的netstat标志,间隔为5秒;但是,我们还将将输出导向到grep并使用grep来过滤端口3306

[blog]# netstat -na 5 | grep 3306
tcp        0      1 192.168.33.11:59492     192.168.33.12:3306 SYN_SENT 
tcp        0      1 192.168.33.11:59493     192.168.33.12:3306 SYN_SENT 
tcp        0      1 192.168.33.11:59494     192.168.33.12:3306 SYN_SENT

除了运行netstat命令,我们还可以在浏览器中导航到blog.example.com地址。我们可以这样做,以强制 Web 应用程序尝试连接到数据库。

一般来说,Web 应用程序对数据库有两种类型的连接,一种是持久连接,它们始终保持与数据库的连接,另一种是非持久连接,只有在需要时才建立。由于我们不知道这个 WordPress 安装使用哪种类型,因此在这种类型的故障排除中,假设它们是非持久的更安全。这意味着,为了触发数据库连接,必须有流量到 WordPress 应用程序。

netstat的输出中,我们可以看到对数据库的连接尝试,而且不仅仅是任何数据库,而是192.168.33.12上的数据库服务。这些信息证实,当 Web 应用程序尝试建立连接时,它使用的是hosts文件中的 IP,而不是来自 DNS。直到这一点,我们怀疑这是基于telnetping,但没有证据表明应用程序的连接。

然而,有趣的事实是netstat输出显示 TCP 连接处于SYN_SENT状态。这个SYN_SENT状态是在首次建立网络连接时使用的状态。netstat命令可以打印许多不同的连接状态;每个状态告诉我们连接所处的过程中的位置。这些信息对于识别网络连接问题的根本原因至关重要。

netstat状态的详细说明

在深入研究之前,我们应该快速查看一下不同的netstat状态及其含义。以下是netstat使用的所有状态的完整列表:

  • ESTABLISHED:连接已建立,可用于数据传输

  • SYN_SENT:TCP 套接字正在尝试与远程主机建立连接

  • SYN_RECV:已从远程主机接收到 TCP 连接请求

  • FIN_WAIT1:TCP 连接正在关闭

  • FIN_WAIT2:TCP 连接正在等待远程主机关闭连接

  • TIME_WAIT:套接字在关闭后等待任何未完成的网络数据包

  • CLOSE:套接字不再被使用

  • CLOSE_WAIT:远程端已关闭其连接,本地套接字正在关闭

  • LAST_ACK:远程端已启动关闭连接,本地系统正在等待最终确认

  • LISTEN:套接字正在用于监听传入连接

  • CLOSING:本地和远程套接字都已关闭,但并非所有数据都已发送

  • UNKNOWN:用于处于未知状态的套接字

从上面的列表中,我们可以确定应用程序到数据库的连接从未变为ESTABLISHED。这意味着应用程序服务器在SYN_SENT状态下开始连接,但从未转换到下一个状态。

使用 tcpdump 捕获网络流量

为了更好地理解网络流量,我们将使用第二个命令来查看网络流量的详细信息——tcpdump。在这里,netstat命令用于打印套接字的状态;tcpdump命令用于创建网络流量的“转储”或“跟踪”。这些转储允许用户查看捕获的网络流量的所有方面。

通过tcpdump,可以查看完整的 TCP 数据包细节,从数据包头部到实际传输的数据。tcpdump不仅可以捕获这些数据,还可以将捕获的数据写入文件。数据写入文件后,可以保存或移动,并且稍后可以使用tcpdump命令或其他网络数据包分析工具(例如wireshark)进行读取。

以下是运行tcpdump捕获网络流量的简单示例。

[blog]# tcpdump -nvvv
tcpdump: listening on enp0s3, link-type EN10MB (Ethernet), capture size 65535 bytes
16:18:04.125881 IP (tos 0x10, ttl 64, id 20361, offset 0, flags [DF], proto TCP (6), length 156)
 10.0.2.16.ssh > 10.0.2.2.52618: Flags [P.], cksum 0x189f (incorrect -> 0x62a4), seq 3643405490:3643405606, ack 245510335, win 26280, length 116
16:18:04.126203 IP (tos 0x0, ttl 64, id 9942, offset 0, flags [none], proto TCP (6), length 40)
 10.0.2.2.52618 > 10.0.2.16.ssh: Flags [.], cksum 0xbc71 (correct), seq 1, ack 116, win 65535, length 0
16:18:05.128497 IP (tos 0x10, ttl 64, id 20362, offset 0, flags [DF], proto TCP (6), length 332)
 10.0.2.16.ssh > 10.0.2.2.52618: Flags [P.], cksum 0x194f (incorrect -> 0xecc9), seq 116:408, ack 1, win 26280, length 292
16:18:05.128784 IP (tos 0x0, ttl 64, id 9943, offset 0, flags [none], proto TCP (6), length 40)
 10.0.2.2.52618 > 10.0.2.16.ssh: Flags [.], cksum 0xbb4d (correct), seq 1, ack 408, win 65535, length 0
16:18:06.129934 IP (tos 0x10, ttl 64, id 20363, offset 0, flags [DF], proto TCP (6), length 156)
 10.0.2.16.ssh > 10.0.2.2.52618: Flags [P.], cksum 0x189f (incorrect -> 0x41d5), seq 408:524, ack 1, win 26280, length 116
16:18:06.130441 IP (tos 0x0, ttl 64, id 9944, offset 0, flags [none], proto TCP (6), length 40)
 10.0.2.2.52618 > 10.0.2.16.ssh: Flags [.], cksum 0xbad9 (correct), seq 1, ack 524, win 65535, length 0
16:18:07.131131 IP (tos 0x10, ttl 64, id 20364, offset 0, flags [DF], proto TCP (6), length 140)

在前面的示例中,我为tcpdump命令提供了几个标志。第一个标志–n(无 dns)告诉tcpdump不要查找它找到的任何 IP 的主机名。其余的标志–vvv(详细)告诉tcpdump非常“非常”详细。tcpdump命令有三个详细级别;每个添加到命令行的–v都会增加使用的详细级别。在前面的示例中,tcpdump处于最详细的模式。

前面的示例是运行tcpdump的最简单方式之一;然而,它并没有捕获我们需要的流量。

查看服务器的网络接口

当在具有多个网络接口的系统上执行tcpdump时,除非定义了接口,否则该命令将选择最低编号的接口进行连接。在前面的示例中,选择的接口是enp0s3;然而,这可能不是用于数据库连接的接口。

在使用tcpdump来调查我们的网络连接问题之前,我们首先需要确定用于此连接的网络接口;为了做到这一点,我们将使用ip命令。

[blog]# ip link show
1: lo:  mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT
 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: enp0s3:  mtu 1500 qdisc pfifo_fast state UP mode DEFAULT qlen 1000
 link/ether 08:00:27:20:5d:4b brd ff:ff:ff:ff:ff:ff
3: enp0s8:  mtu 1500 qdisc pfifo_fast state UP mode DEFAULT qlen 1000
 link/ether 08:00:27:7f:fd:54 brd ff:ff:ff:ff:ff:ff

在高层次上,ip命令允许用户打印、修改和添加网络配置。在上面的示例中,我们告诉ip命令通过使用show links参数来“显示”所有可用的“链接”。显示的链接实际上是为该服务器定义的网络接口。

什么是网络接口?

在谈论物理服务器时,网络接口通常是物理以太网端口的表示。如果我们假设前面示例中使用的机器是一台物理机器,我们可以假设enp0s3enp0s8链接是物理设备。然而,实际上,上述机器是一台虚拟机。这意味着这些设备逻辑上连接到这台虚拟机;但是,这台机器的内核并不知道,甚至不需要知道这种区别。

例如,在这本书中,大多数接口(除了“lo”或回环接口)都直接与物理(或虚拟物理)网络设备相关。然而,也有可能创建虚拟接口,这允许您创建多个接口,这些接口链接回单个物理接口。一般来说,这些接口以“:”或“.”作为原始设备名称的分隔符。如果我们要为enp0s8创建一个虚拟接口,它看起来会像enp0s8:1

查看设备配置

ip命令的输出中,我们可以看到有三个定义的网络接口。在了解哪个接口用于我们的数据库连接之前,我们首先需要更好地了解这些接口。

1: lo:  mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT

lo或回环接口是列表中的第一个。在 Linux 或 Unix 上工作了足够长时间的人都会对回环接口非常熟悉。回环接口旨在为系统的用户提供一个本地网络地址,只能用于连接回本地系统。

这个特殊的接口允许位于同一台服务器上的应用程序通过 TCP/IP 进行交互,而无需将其连接外部网络。它还允许这些应用程序在没有网络数据包离开本地服务器的情况下进行交互,从而使其成为非常快速的网络连接。

传统上,回环接口 IP 的已知地址是127.0.0.1。然而,就像本书中的其他内容一样,我们将在假设其为真之前先验证这些信息。我们可以使用ip命令来显示回环接口的定义地址来做到这一点。

[blog]# ip addr show lo
1: lo:  mtu 65536 qdisc noqueue state UNKNOWN
 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
 inet 127.0.0.1/8 scope host lo
 valid_lft forever preferred_lft forever
 inet6 ::1/128 scope host
 valid_lft forever preferred_lft forever

在显示可用接口的前面示例中,使用了“link show”选项;为了显示 IP 地址,可以使用“addr show”选项。ip命令打印项目的语法在整个过程中都遵循这个相同的方案。

前面的例子还指定了我们感兴趣的设备的名称;这限制了输出到指定的设备。如果我们在前面的命令中省略设备名称,它将简单地打印出所有设备的 IP 地址。

那么,上面的内容告诉我们关于 lo 接口的什么呢?其中一件事是,lo接口正在监听 IPv4 地址127.0.0.1;我们可以在下一行看到这一点。

 inet 127.0.0.1/8 scope host lo

这意味着,如果我们想通过环回接口连接到这个主机,我们可以通过定位127.0.0.1来实现。然而,ip命令还显示了在这个接口上定义的第二个 IP。

 inet6 ::1/128 scope host

这告诉我们::1的 IPv6 地址也绑定到了 lo 接口。这个地址用于相同的目的作为127.0.0.1,但它是为IPv6通信设计的。

通过ip命令提供的上述信息,我们可以看到lo或环回接口被按预期定义。

在这台服务器上定义的第二个接口是enp0s3;这个设备,不像 lo,要么是一个物理设备,要么是一个虚拟化的物理接口。之前执行的ip link show 命令已经告诉我们关于这个接口的很多信息。

2: enp0s3:  mtu 1500 qdisc pfifo_fast state UP mode DEFAULT qlen 1000
 link/ether 08:00:27:20:5d:4b brd ff:ff:ff:ff:ff:ff

The device is in an **up** state: `state UP`The MTU size is **1500**: `mtu 1500`The MAC address is **08:00:27:20:5d:4b**: `link/ether 08:00:27:20:5d:4b`

从这些信息中,我们知道接口已经启动并且可以被利用。我们还知道 MTU 大小设置为默认的 1500,并且可以轻松地识别 MAC 地址。虽然 MTU 大小和 MAC 地址可能与这个问题无关,但在其他情况下它们可能非常有用。

然而,对于我们当前的任务,即确定用于数据库连接的接口,我们需要确定绑定到这个接口的 IP 是哪些。

[blog]# ip addr show enp0s3
2: enp0s3:  mtu 1500 qdisc pfifo_fast state UP qlen 1000
 link/ether 08:00:27:20:5d:4b brd ff:ff:ff:ff:ff:ff
 inet 10.0.2.15/24 brd 10.0.2.255 scope global dynamic enp0s3
 valid_lft 49655sec preferred_lft 49655sec
 inet6 fe80::a00:27ff:fe20:5d4b/64 scope link
 valid_lft forever preferred_lft forever

从前面的输出中,我们可以看到enp0s3接口正在监听 IPv4 IP 10.0.2.15inet 10.0.2.15/24)以及 IPv6 IP f380::a00:27ff:fe20:5d4binet6 fe80::a00:27ff:fe20:5d4b/64)。这是否告诉我们连接到192.168.33.12是通过这个接口?不,但也不意味着不是。

这告诉我们enp0s3接口被用于连接到10.0.2.15/24网络。这个网络可能能够路由到192.168.33.12的地址;在做出这个决定之前,我们应该首先审查下一个接口的配置。

这个系统上的第三个接口是enp0s8;它也是一个物理或虚拟网络设备,从ip link show 命令提供的信息中,我们可以看到它与enp0s3有类似的配置。

3: enp0s8:  mtu 1500 qdisc pfifo_fast state UP mode DEFAULT qlen 1000
 link/ether 08:00:27:7f:fd:54 brd ff:ff:ff:ff:ff:ff

从这个输出中,我们可以看到enp0s8接口也处于UP状态,并且具有默认的 MTU 大小为 1500。我们还可以确定这个接口的 MAC 地址,这在这个时候并不是特别需要;然而,以后可能会变得有用。

然而,如果我们看一下在这台服务器上定义的 IP,与enp0s3设备相比,有一个显著的不同。

[blog]# ip addr show enp0s8
3: enp0s8:  mtu 1500 qdisc pfifo_fast state UP qlen 1000
 link/ether 08:00:27:7f:fd:54 brd ff:ff:ff:ff:ff:ff
 inet 192.168.33.11/24 brd 192.168.33.255 scope global enp0s8
 valid_lft forever preferred_lft forever
 inet6 fe80::a00:27ff:fe7f:fd54/64 scope link
 valid_lft forever preferred_lft forever

我们可以看到enp0s8接口正在监听 IPv4 地址192.168.33.11inet 192.168.33.11/24)和 IPv6 地址fe80::a00:27ff:fe7f:fd54inet6 fe80::a00:27ff:fe7f:fd54/64)。

这是否意味着enp0s8接口被用于连接到192.168.33.12?实际上,可能是的。

enp0s8定义的子网是192.168.33.11/24,这意味着这个接口连接到一个跨越192.168.33.0192.168.33.255的 IP 范围的设备网络。由于数据库服务器的IP 192.168.33.12在这个范围内,很可能是通过enp0s8接口进行与这个地址的通信。

在这一点上,我们可以“怀疑”enp0s8接口被用于与数据库服务器进行通信。虽然这个接口可能被配置为与包含192.168.33.12的子网进行通信,但完全有可能通过使用定义的路由强制通过另一个接口进行通信。

为了检查是否定义了路由并强制通过另一个接口进行通信,我们将再次使用ip命令。然而,对于这个任务,我们将使用ip命令的“route get”选项。

[blog]# ip route get 192.168.33.12
192.168.33.12 dev enp0s8  src 192.168.33.11
 cache

当使用“route get”参数执行时,ip命令将特别输出用于路由到指定 IP 的接口。

从前面的输出中,我们可以看到blog.example.com服务器实际上是使用enp0s8接口路由到 192.168.33.12 地址,即db.example.com的 IP。

到目前为止,我们不仅使用ip命令确定了这台服务器上存在哪些网络接口,还使用它确定了网络数据包到达目标主机所需的接口。

ip命令是一个非常有用的工具,最近被计划用来替代诸如ifconfigroute之类的旧命令。如果你通常熟悉使用ifconfig等命令,但对ip命令不太熟悉,那么建议你回顾一下上面介绍的用法,因为最终ifconfig命令将被弃用。

指定 tcpdump 的接口

现在我们已经确定了与db.example.com通信所使用的接口,我们可以通过使用tcpdump开始我们的网络跟踪。如前所述,我们将使用-nvvv标志将tcpdump置于非常“非常”详细的模式,而不进行主机名解析。然而,这一次,我们将指定tcpdumpenp0s8接口捕获网络流量;我们可以使用-i(接口)标志来实现这一点。我们还将使用-w(写入)标志将捕获的数据写入文件。

[blog]# tcpdump -nvvv -i enp0s8 -w /var/tmp/chapter5.pcap
tcpdump: listening on enp0s8, link-type EN10MB (Ethernet), capture size 65535 bytes
48 packets captured

当我们首次执行tcpdump命令时,屏幕上输出了相当多的内容。当要求将其输出保存到文件时,tcpdump不会将捕获的数据输出到屏幕上,而是会持续显示捕获的数据包的计数器。

一旦我们让tcpdump将捕获的数据保存到文件中,我们需要复制问题以尝试生成数据库流量。我们将通过与netstat命令相同的方法来实现这一点:简单地在 Web 浏览器中导航到blog.example.com

当我们导航到 WordPress 网站时,我们应该看到捕获的数据包计数器在增加;这表明tcpdump已经看到了流量并进行了捕获。一旦计数器达到一个合理的数字,我们就可以停止tcpdump的捕获。要做到这一点,只需在命令行上按下Ctrl + C;一旦停止,我们应该看到类似以下的消息:

^C48 packets captured
48 packets received by filter
0 packets dropped by kernel

读取捕获的数据

现在我们已经将捕获的网络跟踪保存到文件中,我们可以使用这个文件来调查数据库流量。将这些数据保存在文件中的好处是我们可以多次读取这些数据,并通过过滤器来减少输出。此外,当对实时网络流进行tcpdump时,我们可能只能捕获一次流量,再也捕获不到了。

为了读取保存的数据,我们可以使用-r(读取)标志后跟要读取的文件名来运行tcpdump

我们可以通过使用以下命令打印我们捕获的所有48个数据包的数据包头信息来开始。

[blog]# tcpdump -nvvv -r /var/tmp/chapter5.pcap

然而,这个命令的输出可能会让人感到不知所措;为了找到问题的核心,我们需要缩小tcpdump的输出范围。为此,我们将使用 tcpdump 的过滤器功能来对捕获的数据进行过滤。特别是,我们将使用host过滤器将输出过滤到特定的 IP 地址。

[blog]# tcpdump -nvvv -r /var/tmp/chapter5.pcap host 192.168.33.12
reading from file /var/tmp/chapter5.pcap, link-type EN10MB (Ethernet)
03:33:05.569739 IP (tos 0x0, ttl 64, id 26591, offset 0, flags [DF], proto TCP (6), length 60)
 192.168.33.11.37785 > 192.168.33.12.mysql: Flags [S], cksum 0xc396 (incorrect -> 0x3543), seq 3937874058, win 14600, options [mss 1460,sackOK,TS val 53696341 ecr 0,nop,wscale 6], length 0
03:33:06.573145 IP (tos 0x0, ttl 64, id 26592, offset 0, flags [DF], proto TCP (6), length 60)
 192.168.33.11.37785 > 192.168.33.12.mysql: Flags [S], cksum 0xc396 (incorrect -> 0x3157), seq 3937874058, win 14600, options [mss 1460,sackOK,TS val 53697345 ecr 0,nop,wscale 6], length 0
03:33:08.580122 IP (tos 0x0, ttl 64, id 26593, offset 0, flags [DF], proto TCP (6), length 60)
 192.168.33.11.37785 > 192.168.33.12.mysql: Flags [S], cksum 0xc396 (incorrect -> 0x2980), seq 3937874058, win 14600, options [mss 1460,sackOK,TS val 53699352 ecr 0,nop,wscale 6], length 0

通过在tcpdump命令的末尾添加host 192.168.33.12,输出被过滤为只与主机 192.168.33.12 相关的流量。这是通过host过滤器实现的。tcpdump命令有许多可用的过滤器;然而,在本章中,我们主要将利用主机过滤器。我强烈建议经常解决网络问题的人熟悉tcpdump过滤器。

在运行tcpdump(与上面类似)时,重要的是要知道每一行都是通过指定接口发送或接收的一个数据包。下面的例子是一个完整的tcpdump行,本质上是通过enp0s8接口传递的一个数据包。

03:33:05.569739 IP (tos 0x0, ttl 64, id 26591, offset 0, flags [DF], proto TCP (6), length 60)
 192.168.33.11.37785 > 192.168.33.12.mysql: Flags [S], cksum 0xc396 (incorrect -> 0x3543), seq 3937874058, win 14600, options [mss 1460,sackOK,TS val 53696341 ecr 0,nop,wscale 6], length 0

如果我们看一下前面的行,我们可以看到这个数据包是从192.168.33.11发送到192.168.33.12的。我们可以从以下部分看到这一点:

192.168.33.11.37785 > 192.168.33.12.mysql: Flags [S]

192.168.33.11 to 192.168.33.12. We can identify this by the first and the second IPs in this snippet. Since 192.168.33.11 is the first IP, it is the source of the packet, and the second IP (192.168.33.12) is then the destination.
192.168.33.11.37785 > 192.168.33.12.mysql

192.168.33.11 was from the local port 37785 to a remote port of 3306. We can infer this as the fifth dot in the source address is 37785 and "mysql" is in the target address. The reason that tcpdump has printed "mysql" is that by default it will map common service ports to their common name. In this case, it mapped port 3306 to mysql and simply printed mysql. This can be turned off on the command line by using two –n flags (i.e. -nn) to the tcpdump command.
tcpdump output will have a section for flags. When the flags set on a packet are only S, this means that the packet is the initial SYN packet.

这个数据包是一个SYN数据包实际上告诉了我们关于这个数据包的很多信息。

关于 TCP 的快速入门

传输控制协议TCP)是互联网通信中最常用的协议之一。它是我们每天依赖的许多服务的选择协议。从用于加载网页的 HTTP 协议到所有 Linux 系统管理员最喜欢的SSH,这些协议都是在 TCP 协议之上实现的。

虽然 TCP 被广泛使用,但它也是一个相当高级的话题,每个系统管理员都应该至少有基本的了解。在本节中,我们将快速介绍一些 TCP 基础知识;这绝不是一个详尽的指南,但足以理解我们问题的根源。

要理解我们的问题,我们必须首先了解 TCP 连接是如何建立的。在 TCP 通信中,通常有两个重要的参与方,即客户端和服务器。客户端是连接的发起者,并将发送一个SYN数据包作为建立 TCP 连接的第一步。

当服务器接收到一个SYN数据包并愿意接受连接时,它会向客户端发送一个同步确认SYN-ACK)数据包。这是为了让服务器确认它已经收到了原始的SYN数据包。

当客户端接收到这个SYN-ACK数据包时,它会回复服务器一个ACK,有时也称为SYN-ACK-ACK。这个数据包的想法是让客户端确认它已经收到了服务器的确认。

这个过程被称为三次握手,是 TCP 的基础。这种方法的好处是,每个系统都确认它接收到的数据包,因此不会有关于客户端和服务器是否能够来回通信的问题。一旦进行了三次握手,连接就会转移到已建立的状态。在这种状态下可以使用其他类型的数据包,比如推送PSH)数据包,用于在客户端和服务器之间传输信息。

TCP 数据包的类型

说到其他类型的数据包,重要的是要知道确定一个数据包是SYN数据包还是ACK数据包的组件只是在数据包头中设置一个标志。

在我们捕获的数据的第一个数据包上,只有SYN标志被设置;这就是为什么我们会看到输出如Flags [S]的原因。这是第一个数据包被发送并且该数据包只有SYN标志被设置的一个例子。

一个SYN-ACK数据包是一个SYNACK标志被设置的数据包。这通常在tcpdump中看到的是[S.]

以下是在使用tcpdump进行故障排除活动中常见的数据包标志的表格。这绝不是一个完整的列表,但它确实给出了常见数据包类型的一个大致概念。

  • SYN- [S]:这是一个同步数据包,从客户端发送到服务器的第一个数据包。

  • SYN-ACK- [S.]:这是一个同步确认数据包;这些数据包标志用于指示服务器接收到客户端的SYN请求。

  • ACK- [.]:确认数据包被服务器和客户端用来确认接收到的数据包。在初始的SYN数据包发送后,所有后续的数据包都应该设置确认标志。

  • PSH- [P]: 这是一个推送数据包。它旨在将缓冲的网络数据推送到接收方。这是实际传输数据的数据包类型。

  • PSH-ACK- [P.]: 推送确认数据包用于确认先前的数据包并向接收方发送数据。

  • FIN- [F]: FIN或完成数据包用于告诉服务器没有更多数据,可以关闭已建立的连接。

  • FIN-ACK- [F.]: 完成确认数据包用于确认先前的完成数据包已被接收。

  • RST- [R]: 重置数据包用于源系统希望重置连接时使用。一般来说,这是由于错误或目标端口实际上不处于监听状态。

  • RST-ACK -[R.]: 重置确认数据包用于确认先前的重置数据包已被接收。

现在我们已经探讨了不同类型的数据包,让我们把它们联系起来,快速回顾一下之前捕获的数据。

[blog]# tcpdump -nvvv -r /var/tmp/chapter5.pcap host 192.168.33.12
reading from file /var/tmp/chapter5.pcap, link-type EN10MB (Ethernet)
03:33:05.569739 IP (tos 0x0, ttl 64, id 26591, offset 0, flags [DF], proto TCP (6), length 60)
 192.168.33.11.37785 > 192.168.33.12.mysql: Flags [S], cksum 0xc396 (incorrect -> 0x3543), seq 3937874058, win 14600, options [mss 1460,sackOK,TS val 53696341 ecr 0,nop,wscale 6], length 0
03:33:06.573145 IP (tos 0x0, ttl 64, id 26592, offset 0, flags [DF], proto TCP (6), length 60)
 192.168.33.11.37785 > 192.168.33.12.mysql: Flags [S], cksum 0xc396 (incorrect -> 0x3157), seq 3937874058, win 14600, options [mss 1460,sackOK,TS val 53697345 ecr 0,nop,wscale 6], length 0
03:33:08.580122 IP (tos 0x0, ttl 64, id 26593, offset 0, flags [DF], proto TCP (6), length 60)
 192.168.33.11.37785 > 192.168.33.12.mysql: Flags [S], cksum 0xc396 (incorrect -> 0x2980), seq 3937874058, win 14600, options [mss 1460,sackOK,TS val 53699352 ecr 0,nop,wscale 6], length 0
If we look at just the IP addresses and the flags from the captured data, from each line, it becomes very clear what the issue is.
192.168.33.11.37785 > 192.168.33.12.mysql: Flags [S],
192.168.33.11.37785 > 192.168.33.12.mysql: Flags [S],
192.168.33.11.37785 > 192.168.33.12.mysql: Flags [S],

如果我们分解这三个数据包,我们可以看到它们都来自源端口37785,目标端口为 3306。我们还可以看到这些数据包是SYN数据包。这意味着我们的系统发送了 3 个SYN数据包,但从目标端口,即192.168.33.12,没有收到SYN-ACK

这告诉我们关于与主机192.168.33.12的网络连接的什么?它告诉我们要么远程服务器192.168.33.12从未收到我们的数据包,要么它收到了并且我们从未能收到SYN-ACK回复。如果问题是由于数据库服务器不接受我们的数据包,我们将期望看到一个RST重置数据包。

审查收集的数据

此时,现在是时候盘点我们收集的信息和我们目前所知的信息了。

我们已经确定的第一条关键信息是博客服务器(blog.example.com)无法连接到数据库服务器(db.example.com)。我们已经确定的第二条关键信息是 DNS 名称db.example.com解析为10.0.0.50。但是,在blog.example.com服务器上还有一个/etc/hosts文件条目覆盖了 DNS。由于 hosts 文件,当 Web 应用程序尝试连接到db.example.com时,它实际上连接到了192.168.33.12

我们还确定了主机192.168.33.11blog.example.com)在访问 WordPress 应用程序时向192.168.33.12发送初始的SYN数据包。然而,服务器192.168.33.12要么没有接收到这些数据包,要么没有回复这些数据包。

在我们的调查过程中,我们审查了博客服务器的网络配置,并确定它似乎已正确设置。我们可以通过简单使用 ping 命令向每个网络接口的子网内的 IP 发送 ICMP 回显来对此进行额外验证。

[blog]# ip addr show enp0s3
2: enp0s3:  mtu 1500 qdisc pfifo_fast state UP qlen 1000
 link/ether 08:00:27:20:5d:4b brd ff:ff:ff:ff:ff:ff
 inet 10.0.2.16/24 brd 10.0.2.255 scope global dynamic enp0s3
 valid_lft 62208sec preferred_lft 62208sec
 inet6 fe80::a00:27ff:fe20:5d4b/64 scope link
 valid_lft forever preferred_lft forever

对于enp0s3接口,我们可以看到绑定的 IP 地址是10.0.2.16,子网掩码为/24255.255.255.0。通过这种设置,我们应该能够与该子网内的其他 IP 进行通信。以下是使用 ping 命令测试与10.0.2.2的连通性的输出。

[blog]# ping 10.0.2.2
PING 10.0.2.2 (10.0.2.2) 56(84) bytes of data.
64 bytes from 10.0.2.2: icmp_seq=1 ttl=63 time=0.250 ms
64 bytes from 10.0.2.2: icmp_seq=2 ttl=63 time=0.196 ms
64 bytes from 10.0.2.2: icmp_seq=3 ttl=63 time=0.197 ms
^C
--- 10.0.2.2 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2001ms
rtt min/avg/max/mdev = 0.196/0.214/0.250/0.027 ms

这表明enp0s3接口至少可以连接到其子网内的其他 IP。对于enp0s8,我们可以使用另一个 IP 执行相同的测试。

[blog]# ip addr show enp0s8
3: enp0s8:  mtu 1500 qdisc pfifo_fast state UP qlen 1000
 link/ether 08:00:27:7f:fd:54 brd ff:ff:ff:ff:ff:ff
 inet 192.168.33.11/24 brd 192.168.33.255 scope global enp0s8
 valid_lft forever preferred_lft forever
 inet6 fe80::a00:27ff:fe7f:fd54/64 scope link
 valid_lft forever preferred_lft forever

从上述命令中,我们可以看到enp0s8的 IP 为192.168.33.11,子网掩码为/24255.255.255.0。如果我们可以使用 ping 命令与192.168.33.11/24子网内的任何其他 IP 进行通信,那么我们可以验证该接口也已正确配置。

# ping 192.168.33.1
PING 192.168.33.1 (192.168.33.1) 56(84) bytes of data.
64 bytes from 192.168.33.1: icmp_seq=1 ttl=64 time=0.287 ms
64 bytes from 192.168.33.1: icmp_seq=2 ttl=64 time=0.249 ms
64 bytes from 192.168.33.1: icmp_seq=3 ttl=64 time=0.260 ms
64 bytes from 192.168.33.1: icmp_seq=4 ttl=64 time=0.192 ms
^C
--- 192.168.33.1 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3028ms
rtt min/avg/max/mdev = 0.192/0.247/0.287/0.034 ms

从结果中,我们可以看到对 IP192.168.33.1的连接正常工作。因此,这意味着,至少在基本方面,enp0s8接口已正确配置。

有了所有这些信息,我们可以假设blog.example.com服务器已正确配置,并且可以连接到其配置的网络。从这一点开始,如果我们想要更多关于我们问题的信息,我们需要从db.example.com192.168.33.12)服务器获取。

看看对方的情况

虽然可能并非总是可能的,但在处理网络问题时,最好从对话的两端进行故障排除。在我们之前的例子中,我们有两个构成我们网络对话的系统,即客户端和服务器。到目前为止,我们已经从客户端的角度看了一切;在本节中,我们将从服务器的角度来看这次对话的另一面。

识别网络配置

在前一节中,我们在查看博客服务器的网络配置之前经历了几个步骤。在数据库服务器的情况下,我们已经知道问题与网络有关,特别是 IP 为192.168.33.12。既然我们已经知道问题与哪个 IP 相关,我们应该做的第一件事是确定这个 IP 绑定到哪个接口。

我们将再次使用ip命令和addr show选项来执行此操作。

[db]# ip addr show
1: lo:  mtu 65536 qdisc noqueue state UNKNOWN
 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
 inet 127.0.0.1/8 scope host lo
 valid_lft forever preferred_lft forever
 inet6 ::1/128 scope host
 valid_lft forever preferred_lft forever
2: enp0s3:  mtu 1500 qdisc pfifo_fast state UP qlen 1000
 link/ether 08:00:27:20:5d:4b brd ff:ff:ff:ff:ff:ff
 inet 10.0.2.16/24 brd 10.0.2.255 scope global dynamic enp0s3
 valid_lft 86304sec preferred_lft 86304sec
 inet6 fe80::a00:27ff:fe20:5d4b/64 scope link
 valid_lft forever preferred_lft forever
3: enp0s8:  mtu 1500 qdisc pfifo_fast state UP qlen 1000
 link/ether 08:00:27:c9:d3:65 brd ff:ff:ff:ff:ff:ff
 inet 192.168.33.12/24 brd 192.168.33.255 scope global enp0s8
 valid_lft forever preferred_lft forever
 inet6 fe80::a00:27ff:fec9:d365/64 scope link
 valid_lft forever preferred_lft forever

在之前的例子中,我们使用addr show选项来显示与单个接口关联的 IP。然而,这次通过省略接口名称,ip命令显示了所有 IP 以及这些 IP 绑定到的接口。这是一种快速简单的方法,可以显示与这台服务器关联的 IP 地址和接口。

从前面的命令中,我们可以看到数据库服务器与应用服务器的配置类似,都有三个接口。在深入之前,让我们更好地了解服务器的接口,并看看我们可以从中识别出什么信息。

1: lo:  mtu 65536 qdisc noqueue state UNKNOWN
 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
 inet 127.0.0.1/8 scope host lo
 valid_lft forever preferred_lft forever
 inet6 ::1/128 scope host
 valid_lft forever preferred_lft forever

这台服务器上的第一个接口是环回接口lo。如前所述,这个接口对于每台服务器来说都是通用的,只用于本地网络流量。这个接口不太可能与我们的问题有关。

2: enp0s3:  mtu 1500 qdisc pfifo_fast state UP qlen 1000
 link/ether 08:00:27:20:5d:4b brd ff:ff:ff:ff:ff:ff
 inet 10.0.2.16/24 brd 10.0.2.255 scope global dynamic enp0s3
 valid_lft 86304sec preferred_lft 86304sec
 inet6 fe80::a00:27ff:fe20:5d4b/64 scope link
 valid_lft forever preferred_lft forever

对于第二个接口enp0s3,数据库服务器的配置与博客服务器非常相似。在 Web 应用服务器上,我们也有一个名为enp0s3的接口,这个接口也在10.0.2.0/24网络上。

由于博客和数据库服务器之间的连接似乎是针对 IP192.168.33.12,因此enp0s3不是一个需要关注的接口,因为enp0s3接口的 IP 是10.0.2.16

3: enp0s8:  mtu 1500 qdisc pfifo_fast state UP qlen 1000
 link/ether 08:00:27:c9:d3:65 brd ff:ff:ff:ff:ff:ff
 inet 192.168.33.12/24 brd 192.168.33.255 scope global enp0s8
 valid_lft forever preferred_lft forever
 inet6 fe80::a00:27ff:fec9:d365/64 scope link
 valid_lft forever preferred_lft forever

另一方面,第三个网络设备enp0s8确实绑定了 IP192.168.33.12enp0s8设备的设置也与博客服务器上的enp0s8设备类似,因为这两个设备似乎都在192.168.33.0/24网络上。

通过之前的故障排除,我们知道我们的 Web 应用程序所针对的 IP 是 IP 192.168.33.12。通过ip命令,我们已经确认 192.168.33.12 通过enp0s8接口绑定到了这台服务器上。

从 db.example.com 测试连接

现在我们知道数据库服务器有预期的网络配置,我们需要确定这台服务器是否正确连接到192.168.33.0/24网络。最简单的方法是执行一个我们之前在博客服务器上执行过的任务;使用ping连接到该子网上的另一个 IP。

[db]# ping 192.168.33.1
PING 192.168.33.1 (192.168.33.1) 56(84) bytes of data.
64 bytes from 192.168.33.1: icmp_seq=1 ttl=64 time=0.438 ms
64 bytes from 192.168.33.1: icmp_seq=2 ttl=64 time=0.208 ms
64 bytes from 192.168.33.1: icmp_seq=3 ttl=64 time=0.209 ms
^C
--- 192.168.33.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2001ms
rtt min/avg/max/mdev = 0.208/0.285/0.438/0.108 ms

通过上面的输出,我们可以看到数据库服务器能够联系到192.168.33.0/24子网上的另一个 IP。在故障排除时,我们曾试图从博客服务器连接到数据库服务器,但测试失败了。一个有趣的测试是验证当数据库服务器发起连接到博客服务器时,连接是否也失败。

[db]# ping 192.168.33.11
PING 192.168.33.11 (192.168.33.11) 56(84) bytes of data.
From 10.0.2.16 icmp_seq=1 Destination Host Unreachable
From 10.0.2.16 icmp_seq=2 Destination Host Unreachable
From 10.0.2.16 icmp_seq=3 Destination Host Unreachable
From 10.0.2.16 icmp_seq=4 Destination Host Unreachable
^C
--- 192.168.33.11 ping statistics ---
6 packets transmitted, 0 received, +4 errors, 100% packet loss, time 5005ms

从数据库服务器运行ping命令到博客服务器的 IP(192.168.33.11),我们可以看到 ping 已经回复目标主机不可达。这与我们从博客服务器尝试连接时看到的错误相同。

如前所述,除了网络连接问题之外,ping 失败的原因还有很多;为了确保存在连接问题,我们还应该使用telnet测试连接。我们知道博客服务器正在接受到 Web 服务器的连接,因此简单地 telnet 到 Web 服务器的端口应该明确告诉我们从数据库服务器到 Web 服务器是否存在任何连接。

运行telnet时,我们需要指定要连接的端口。我们知道 Web 服务器正在运行,当我们导航到http://blog.example.com时,我们会得到一个网页。基于这些信息,我们可以确定使用默认的 HTTP 端口并且正在监听。有了这些信息,我们还知道我们可以简单地使用 telnet 连接到端口80,这是HTTP通信的默认端口。

[db]# telnet 192.168.33.11 80
-bash: telnet: command not found

但是,在这台服务器上,未安装telnet。这没关系,因为我们可以像在之前的示例中那样使用curl命令。

[db]# curl telnet://192.168.33.11:80 -v
* About to connect() to 192.168.33.11 port 80 (#0)
*   Trying 192.168.33.11...
* No route to host
* Failed connect to 192.168.33.11:80; No route to host
* Closing connection 0
curl: (7) Failed connect to 192.168.33.11:80; No route to host

curl命令的输出中,我们可以看到无论是博客服务器还是数据库服务器发起连接,通信问题都存在。

使用 netstat 查找连接

在之前的部分中,当从博客服务器进行故障排除时,我们使用netstat查看了到数据库服务器的开放 TCP 连接。现在我们已经登录到数据库服务器,我们可以使用相同的命令从数据库服务器的角度查看连接的状态。为此,我们将使用指定的间隔运行netstat;这会导致netstat每 5 秒打印一次网络连接统计,类似于vmstattop命令。

netstat命令运行时,我们只需刷新浏览器,使 WordPress 应用程序再次尝试数据库连接。

[db]# netstat -na 5 | grep 192.168.33.11

在我喜欢称为“连续模式”的情况下运行netstat命令,并使用grep过滤博客服务器的 IP(192.168.33.11),我们无法看到任何 TCP 连接或连接尝试。

在许多情况下,这似乎表明数据库服务器从未收到来自博客服务器的 TCP 数据包。我们可以通过使用tcpdump命令在enp0s8接口上捕获所有网络流量来确认是否是这种情况。

使用 tcpdump 跟踪网络连接

早些时候学习tcpdump时,我们了解到它默认使用编号最低的接口。这意味着,为了捕获连接尝试,我们必须使用-i(接口)标志来跟踪正确的接口enp0s8。除了告诉tcpdump监视enp0s8接口外,我们还将让tcpdump将其输出写入文件。我们这样做是为了尽可能多地捕获数据,并稍后使用tcpdump命令多次分析数据。

[db]# tcpdump -i enp0s8 -w /var/tmp/db-capture.pcap
tcpdump: listening on enp0s8, link-type EN10MB (Ethernet), capture size 65535 bytes

现在tcpdump正在运行,我们只需要再次刷新浏览器。

^C110 packets captured
110 packets received by filter
0 packets dropped by kernel

在刷新浏览器并看到捕获的数据包计数器增加后,我们可以通过在键盘上按Ctrl + C来停止tcpdump

一旦tcpdump停止,我们可以使用-r(读取)标志读取捕获的数据;但是,这将打印tcpdump捕获的所有数据包。在某些环境中,这可能是相当多的数据。因此,为了将输出修剪为仅有用的数据,我们将使用port过滤器告诉tcpdump仅输出从端口 3306 发起或针对端口 3306 的捕获流量,默认的 MySQL 端口。

我们可以通过在tcpdump命令的末尾添加port 3306来实现这一点。

[db]# tcpdump -nnvvv -r /var/tmp/db-capture.pcap port 3306
reading from file /var/tmp/db-capture.pcap, link-type EN10MB (Ethernet)
03:11:03.697543 IP (tos 0x10, ttl 64, id 43196, offset 0, flags [DF], proto TCP (6), length 64)
 192.168.33.1.59510 > 192.168.33.12.3306: Flags [S], cksum 0xc125 (correct), seq 2335155468, win 65535, options [mss 1460,nop,wscale 5,nop,nop,TS val 1314733695 ecr 0,sackOK,eol], length 0
03:11:03.697576 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 60)
 192.168.33.12.3306 > 192.168.33.1.59510: Flags [S.], cksum 0xc38c (incorrect -> 0x5d87), seq 2658328059, ack 2335155469, win 14480, options [mss 1460,sackOK,TS val 1884022 ecr 1314733695,nop,wscale 6], length 0
03:11:03.697712 IP (tos 0x10, ttl 64, id 61120, offset 0, flags [DF], proto TCP (6), length 52)
 192.168.33.1.59510 > 192.168.33.12.3306: Flags [.], cksum 0xb4cd (correct), seq 1, ack 1, win 4117, options [nop,nop,TS val 1314733695 ecr 1884022], length 0
03:11:03.712018 IP (tos 0x8, ttl 64, id 25226, offset 0, flags [DF], proto TCP (6), length 127)

然而,在使用上述过滤器的同时,似乎这个数据库服务器不仅仅被 WordPress 应用程序使用。从tcpdump输出中,我们可以看到端口3306上的流量不仅仅是博客服务器。

为了进一步清理此输出,我们可以向tcpdump命令添加主机过滤器,以仅过滤出我们感兴趣的流量:来自主机192.168.33.11的流量。

[db]# tcpdump -nnvvv -r /var/tmp/db-capture.pcap port 3306 and host 192.168.33.11
reading from file /var/tmp/db-capture.pcap, link-type EN10MB (Ethernet)
04:04:09.167121 IP (tos 0x0, ttl 64, id 60173, offset 0, flags [DF], proto TCP (6), length 60)
 192.168.33.11.51149 > 192.168.33.12.3306: Flags [S], cksum 0x4111 (correct), seq 558685560, win 14600, options [mss 1460,sackOK,TS val 9320053 ecr 0,nop,wscale 6], length 0
04:04:10.171104 IP (tos 0x0, ttl 64, id 60174, offset 0, flags [DF], proto TCP (6), length 60)
 192.168.33.11.51149 > 192.168.33.12.3306: Flags [S], cksum 0x3d26 (correct), seq 558685560, win 14600, options [mss 1460,sackOK,TS val 9321056 ecr 0,nop,wscale 6], length 0
04:04:12.175107 IP (tos 0x0, ttl 64, id 60175, offset 0, flags [DF], proto TCP (6), length 60)
 192.168.33.11.51149 > 192.168.33.12.3306: Flags [S], cksum 0x3552 (correct), seq 558685560, win 14600, options [mss 1460,sackOK,TS val 9323060 ecr 0,nop,wscale 6], length 0
04:04:16.187731 IP (tos 0x0, ttl 64, id 60176, offset 0, flags [DF], proto TCP (6), length 60)
 192.168.33.11.51149 > 192.168.33.12.3306: Flags [S], cksum 0x25a5 (correct), seq 558685560, win 14600, options [mss 1460,sackOK,TS val 9327073 ecr 0,nop,wscale 6], length 0

在这里,我们使用and运算符告诉tcpdump只打印到/从端口3306和到/从主机192.168.33.11的流量。

tcpdump命令有许多可能的过滤器和运算符;然而,在所有这些中,我建议熟悉基于端口和主机的过滤,因为这些将足够满足大多数情况。

如果我们分解前面捕获的网络跟踪,我们可以看到一些有趣的信息;为了更容易发现,让我们将输出修剪到只显示正在使用的 IP 和标志。

04:04:09.167121 IP
 192.168.33.11.51149 > 192.168.33.12.3306: Flags [S],
04:04:10.171104 IP
 192.168.33.11.51149 > 192.168.33.12.3306: Flags [S],
04:04:12.175107 IP
 192.168.33.11.51149 > 192.168.33.12.3306: Flags [S],
04:04:16.187731 IP
 192.168.33.11.51149 > 192.168.33.12.3306: Flags [S],

从这些信息中,我们可以看到从blog.example.com192.168.33.11)发送的SYN数据包,并到达db.example.com192.168.33.12)。然而,我们看不到返回的SYN-ACKS

这告诉我们,我们至少已经找到了网络问题的源头;服务器db.example.com没有正确地回复从博客服务器收到的数据包。

现在的问题是:什么会导致这种问题?导致此类问题的原因有很多;但是,一般来说,这样的问题是由网络配置设置中的错误配置引起的。根据我们收集的信息,我们可以假设数据库服务器只是配置错误。

然而,有几种方法可以通过错误配置导致这种类型的问题。为了确定可能的错误配置,我们可以使用tcpdump命令在此服务器上捕获所有网络流量。

在以前的tcpdump示例中,我们总是指定单个要监视的接口。在大多数情况下,这对于问题是适当的,因为它减少了tcpdump捕获的数据量。在非常活跃的服务器上,tcpdump数据的几分钟可能非常庞大,因此最好将数据减少到只有必需的部分。

然而,在某些情况下,例如这种问题,告诉tcpdump捕获所有接口的网络流量是有用的。为此,我们只需指定any作为要监视的接口。

[db]# tcpdump -i any -w /var/tmp/alltraffic.pcap
tcpdump: listening on any, link-type LINUX_SLL (Linux cooked), capture size 65535 bytes

现在我们有tcpdump捕获并保存所有接口上的所有流量,我们需要再次刷新浏览器,以强制 WordPress 应用程序尝试数据库连接。

^C440 packets captured
443 packets received by filter
0 packets dropped by kernel

经过几次尝试,我们可以再次按Ctrl + C停止tcpdump。将捕获的网络数据保存到文件后,我们可以开始调查这些连接尝试的情况。

由于tcpdump捕获了大量数据包,我们将再次使用“主机”过滤器将结果限制为与192.168.33.11之间的网络流量。

[db]# tcpdump -nnvvv -r /var/tmp/alltraffic.pcap host 192.168.33.11
reading from file /var/tmp/alltraffic.pcap, link-type LINUX_SLL (Linux cooked)
15:37:51.616621 IP (tos 0x0, ttl 64, id 8389, offset 0, flags [DF], proto TCP (6), length 60)
 192.168.33.11.47339 > 192.168.33.12.3306: Flags [S], cksum 0x34dd (correct), seq 4225047048, win 14600, options [mss 1460,sackOK,TS val 3357389 ecr 0,nop,wscale 6], length 0
15:37:51.616665 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 60)
 192.168.33.12.3306 > 192.168.33.11.47339: Flags [S.], cksum 0xc396 (incorrect -> 0x3609), seq 1637731271, ack 4225047049, win 14480, options [mss 1460,sackOK,TS val 3330467 ecr 3357389,nop,wscale 6], length 0
15:37:51.616891 IP (tos 0x0, ttl 255, id 2947, offset 0, flags [none], proto TCP (6), length 40)
 192.168.33.11.47339 > 192.168.33.12.3306: Flags [R], cksum 0x10c4 (correct), seq 4225047049, win 0, length 0
15:37:52.619386 IP (tos 0x0, ttl 64, id 8390, offset 0, flags [DF], proto TCP (6), length 60)
 192.168.33.11.47339 > 192.168.33.12.3306: Flags [S], cksum 0x30f2 (correct), seq 4225047048, win 14600, options [mss 1460,sackOK,TS val 3358392 ecr 0,nop,wscale 6], length 0
15:37:52.619428 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 60)
 192.168.33.12.3306 > 192.168.33.11.47339: Flags [S.], cksum 0xc396 (incorrect -> 0x1987), seq 1653399428, ack 4225047049, win 14480, options [mss 1460,sackOK,TS val 3331470 ecr 3358392,nop,wscale 6], length 0
15:37:52.619600 IP (tos 0x0, ttl 255, id 2948, offset 0, flags [none], proto TCP (6), length 40)
 192.168.33.11.47339 > 192.168.33.12.3306: Flags [R], cksum 0x10c4 (correct), seq 4225047049, win 0, length 0

通过捕获的数据,似乎我们已经找到了预期的SYN-ACK。为了更清晰地展示这一点,让我们将输出修剪到仅包括正在使用的 IP 和标志。

15:37:51.616621 IP
 192.168.33.11.47339 > 192.168.33.12.3306: Flags [S],
15:37:51.616665 IP
 192.168.33.12.3306 > 192.168.33.11.47339: Flags [S.],
15:37:51.616891 IP
 192.168.33.11.47339 > 192.168.33.12.3306: Flags [R],
15:37:52.619386 IP
 192.168.33.11.47339 > 192.168.33.12.3306: Flags [S],
15:37:52.619428 IP
 192.168.33.12.3306 > 192.168.33.11.47339: Flags [S.],
15:37:52.619600 IP
 192.168.33.11.47339 > 192.168.33.12.3306: Flags [R],

通过更清晰的图片,我们可以看到一系列有趣的网络数据包正在传输。

15:37:51.616621 IP
 192.168.33.11.47339 > 192.168.33.12.3306: Flags [S],

第一个数据包是从192.168.33.11192.168.33.12的端口3306SYN数据包。这与我们之前使用tcpdump执行捕获的数据包类型相同。

15:37:51.616665 IP
 192.168.33.12.3306 > 192.168.33.11.47339: Flags [S.],

然而,我们以前没有看到第二个数据包。在第二个数据包中,我们看到它是一个SYN-ACK(由Flags [S.]标识)。SYN-ACK是从端口3306192.168.33.12发送到端口47339192.168.33.11(发送原始SYN数据包的端口)。

乍一看,这似乎是一个正常的SYNSYN-ACK握手。

15:37:51.616891 IP
 192.168.33.11.47339 > 192.168.33.12.3306: Flags [R],

然而,第三个数据包很有趣,因为它清楚地表明了一个问题。第三个数据包是一个RESET数据包(由Flags [R]标识),从博客服务器192.168.33.11发送。关于这个有趣的事情是,当在博客服务器上执行tcpdump时,我们从未捕获到RESET数据包。如果我们在博客服务器上再次执行tcpdump,我们可以再次看到这个。

[blog]# tcpdump -i any port 3306
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on any, link-type LINUX_SLL (Linux cooked), capture size 65535 bytes
15:24:25.646731 IP blog.example.com.47336 > db.example.com.mysql: Flags [S], seq 3286710391, win 14600, options [mss 1460,sackOK,TS val 2551514 ecr 0,nop,wscale 6], length 0
15:24:26.648706 IP blog.example.com.47336 > db.example.com.mysql: Flags [S], seq 3286710391, win 14600, options [mss 1460,sackOK,TS val 2552516 ecr 0,nop,wscale 6], length 0
15:24:28.652763 IP blog.example.com.47336 > db.example.com.mysql: Flags [S], seq 3286710391, win 14600, options [mss 1460,sackOK,TS val 2554520 ecr 0,nop,wscale 6], length 0
15:24:32.660123 IP blog.example.com.47336 > db.example.com.mysql: Flags [S], seq 3286710391, win 14600, options [mss 1460,sackOK,TS val 2558528 ecr 0,nop,wscale 6], length 0
15:24:40.676112 IP blog.example.com.47336 > db.example.com.mysql: Flags [S], seq 3286710391, win 14600, options [mss 1460,sackOK,TS val 2566544 ecr 0,nop,wscale 6], length 0
15:24:56.724102 IP blog.example.com.47336 > db.example.com.mysql: Flags [S], seq 3286710391, win 14600, options [mss 1460,sackOK,TS val 2582592 ecr 0,nop,wscale 6], length 0

从前面的tcpdump输出中,我们从未看到博客服务器上的SYN-ACKRESET数据包。这意味着RESET要么是由另一个系统发送的,要么是在tcpdump捕获之前被博客服务器的内核拒绝了SYN-ACK数据包。

tcpdump命令捕获网络流量时,它是在内核处理这些网络流量之后进行的。这意味着如果由于任何原因内核拒绝了数据包,它将不会通过tcpdump命令看到。因此,博客服务器的内核在tcpdump能够捕获它们之前可能会拒绝来自数据库服务器的返回数据包。

通过在数据库上执行tcpdump,我们还发现了另一个有趣的点,即如果我们查看在enp0s8上执行的tcpdump,我们看不到SYN-ACK数据包。然而,如果我们告诉tcpdump查看我们使用的所有接口,tcpdump也会显示SYN-ACK数据包来自192.168.33.12。这表明SYN-ACK是从另一个接口发送的。

为了确认这一点,我们可以再次运行tcpdump,限制捕获经过enp0s8接口的数据包。

[db]# tcpdump -nnvvv -i enp0s8 port 3306 and host 192.168.33.11
04:04:09.167121 IP (tos 0x0, ttl 64, id 60173, offset 0, flags [DF], proto TCP (6), length 60)
 192.168.33.11.51149 > 192.168.33.12.3306: Flags [S], cksum 0x4111 (correct), seq 558685560, win 14600, options [mss 1460,sackOK,TS val 9320053 ecr 0,nop,wscale 6], length 0
04:04:10.171104 IP (tos 0x0, ttl 64, id 60174, offset 0, flags [DF], proto TCP (6), length 60)
 192.168.33.11.51149 > 192.168.33.12.3306: Flags [S], cksum 0x3d26 (correct), seq 558685560, win 14600, options [mss 1460,sackOK,TS val 9321056 ecr 0,nop,wscale 6], length 0

通过这次对tcpdump的执行,我们只能再次看到来自博客服务器的SYN数据包。然而,如果我们对所有接口运行相同的tcpdump,我们不仅应该看到SYN数据包,还应该看到SYN-ACK数据包。

[db]# tcpdump -nnvvv -i any port 3306 and host 192.168.33.11
15:37:51.616621 IP (tos 0x0, ttl 64, id 8389, offset 0, flags [DF], proto TCP (6), length 60)
 192.168.33.11.47339 > 192.168.33.12.3306: Flags [S], cksum 0x34dd (correct), seq 4225047048, win 14600, options [mss 1460,sackOK,TS val 3357389 ecr 0,nop,wscale 6], length 0
15:37:51.616665 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 60)
 192.168.33.12.3306 > 192.168.33.11.47339: Flags [S.], cksum 0xc396 (incorrect -> 0x3609), seq 1637731271, ack 4225047049, win 14480, options [mss 1460,sackOK,TS val 3330467 ecr 3357389,nop,wscale 6], length 0

返回到192.168.33.11SYN-ACK数据包源自192.168.33.12。早些时候,我们确定了这个 IP 绑定到网络设备enp0s8。然而,当我们使用tcpdump查看所有发送的数据包时,SYN-ACK并没有被捕获从enp0s8出去。这意味着SYN-ACK数据包是从另一个接口发送的。

路由

SYN数据包如何到达一个接口,而SYN-ACK却从另一个接口返回呢?一个可能的答案是这是由于数据库服务器路由定义的错误配置。

支持网络的每个操作系统都维护着一个称为路由表的东西。这个路由表是一组定义的网络路由,数据包应该经过的路由。为了给这个概念提供一些背景,让我们以enp0s3enp0s8两个接口为例。

# ip addr show enp0s8
3: enp0s8:  mtu 1500 qdisc pfifo_fast state UP qlen 1000
 link/ether 08:00:27:c9:d3:65 brd ff:ff:ff:ff:ff:ff
 inet 192.168.33.12/24 brd 192.168.33.255 scope global enp0s8
 valid_lft forever preferred_lft forever
 inet6 fe80::a00:27ff:fec9:d365/64 scope link
 valid_lft forever preferred_lft forever
# ip addr show enp0s3
2: enp0s3:  mtu 1500 qdisc pfifo_fast state UP qlen 1000
 link/ether 08:00:27:20:5d:4b brd ff:ff:ff:ff:ff:ff
 inet 10.0.2.16/24 brd 10.0.2.255 scope global dynamic enp0s3
 valid_lft 65115sec preferred_lft 65115sec
 inet6 fe80::a00:27ff:fe20:5d4b/64 scope link
 valid_lft forever preferred_lft forever

如果我们查看这两个接口,我们知道enp0s8接口连接到192.168.33.0/24inet 192.168.33.12/24)网络,而enp0s3接口连接到10.0.2.0/24inet 10.0.2.16/24)网络。

如果我们要连接到 IP 10.0.2.19,数据包不应该从enp0s8接口出去,因为这些数据包的最佳路由应该是通过enp0s3接口路由。这是最佳路由的原因是enp0s3接口已经是10.0.2.0/24网络的一部分,其中包含 IP10.0.2.19

enp0s8接口是不同网络(192.168.33.0/24)的一部分,因此是不太理想的路由。事实上,enp0s8接口甚至可能无法路由到10.0.2.0/24网络。

即使enp0s8可能是一个不太理想的路由,内核在没有路由表中对应条目的情况下是不知道这一点的。为了更深入地了解我们的问题,我们需要查看数据库服务器上的路由表。

查看路由表

在 Linux 中,有几种方法可以查看当前的路由表;在本节中,我将介绍两种方法。第一种方法将利用netstat命令。

要使用netstat命令查看路由表,只需使用-r(route)或--route标志运行它。在下面的例子中,我们还将使用-n标志防止netstat执行 DNS 查找。

[db]# netstat -rn
Kernel IP routing table
Destination     Gateway         Genmask         Flags   MSS Window irtt Iface
0.0.0.0         10.0.2.2        0.0.0.0         UG        0 0 0 enp0s3
10.0.2.0        0.0.0.0         255.255.255.0   U         0 0 0 enp0s3
169.254.0.0     0.0.0.0         255.255.0.0     U         0 0 0 enp0s8
192.168.33.0    0.0.0.0         255.255.255.0   U         0 0 0 enp0s8
192.168.33.11   10.0.2.1        255.255.255.255 UGH       0 0 0 enp0s3

虽然netstat可能不是打印路由表的最佳 Linux 命令,但在这个例子中使用它有一个非常特殊的原因。正如我在本章和本书中早些时候提到的,netstat命令是一个几乎存在于每台现代服务器、路由器或台式机上的通用工具。通过了解如何使用netstat查看路由表,您可以在安装了netstat的任何操作系统上执行基本的网络故障排除。

一般来说,可以肯定地说netstat命令是可用的,并且可以为您提供系统网络状态和配置的基本细节。

与其他实用程序(如ip命令)相比,netstat的格式可能有点晦涩。然而,前面的路由表向我们展示了相当多的信息。为了更好地理解,让我们逐条分解输出的路由。

Destination     Gateway         Genmask         Flags   MSS Window irtt Iface
0.0.0.0         10.0.2.2        0.0.0.0         UG        0 0 0 enp0s3

正如你所看到的,netstat命令的输出有多列,确切地说有八列。第一列是Destination列。这用于定义路由范围内的目标地址。在前面的例子中,目的地是0.0.0.0,这实际上是一个通配值,意味着任何东西都应该通过这个表项进行路由。

第二列是Gateway。网关地址是利用这条路由的网络数据包应该发送到的下一跳。在这个例子中,下一跳或网关地址设置为10.0.2.2;这意味着通过这个表项进行路由的任何数据包将被发送到10.0.2.2,然后应该将数据包路由到下一个系统,直到它们到达目的地。

第三列是Genmask,本质上是一种陈述路由的“一般性”的方式。另一种思考这一列的方式是作为netmask;在前面的例子中,“genmask”设置为0.0.0.0,这是一个开放范围。这意味着任何地方的数据包都应该通过这个路由表项进行路由。

第四列是Flag列,用于提供有关这条路由的具体信息。例子中的U值表示此路由使用的接口处于上行状态。G值用于显示路由使用了网关地址。在前面的例子中,我们可以看到我们的路由使用了网关地址;然而,并非这个系统的所有路由都是这样。

第五、第六和第七列在 Linux 服务器上并不经常使用。MSS列用于显示为这条路由指定的最大分段大小。值为 0 意味着此值设置为默认值且未更改。

Window列是 TCP 窗口大小,表示单个突发接受的最大数据量。同样,当值设置为 0 时,将使用默认大小。

第七列是irtt,用于指定这条路由的初始往返时间。内核将通过设置初始往返时间重新发送从未得到响应的数据包;您可以增加或减少内核认为数据包丢失之后的时间。与前两列一样,值为 0 意味着使用这条路由的数据包将使用默认值。

第八和最后一列是IFace列,是利用这条路由的数据包应该使用的网络接口。在前面的例子中,这是enp0s3接口。

默认路由

我们例子中的第一条路由实际上是我们系统的一个非常特殊的路由。

Destination     Gateway         Genmask         Flags   MSS Window irtt Iface
0.0.0.0         10.0.2.2        0.0.0.0         UG        0 0 0 enp0s3

如果我们查看这条路由的详细信息和每列的定义,我们可以确定这条路由是服务器的默认路由。默认路由是一种特殊路由,在没有其他路由取代它时“默认”使用。简而言之,如果我们有要发送到诸如172.0.0.10的地址的数据包,这些数据包将通过默认路由发送。

这是因为我们的数据库服务器路由表中没有其他指定 IP172.0.0.10的路由。因此,系统只需通过默认路由发送数据包到这个 IP,这是一个万能路由。

我们可以通过目的地址为0.0.0.0来确定第一条路由是服务器的默认路由,这基本上意味着任何东西。第二个指示是Genmask0.0.0.0,这与目的地一起意味着任何 IPv4 地址。

默认路由通常使用网关地址,因此网关为destinationgenmask设置通配符是明确表明上述路由是默认路由的迹象。

非默认路由通常看起来像这样:

10.0.2.0        0.0.0.0         255.255.255.0   U         0 0 0 enp0s3

上述路由的目的地是 10.0.2.0,genmask为 255.255.255.0;这基本上意味着 10.0.2.0/24 网络中的任何内容都会匹配这条路由。

由于这条路由的范围是10.0.2.0/24,很可能是由enp0s3接口配置添加的。我们可以根据enp0s3接口配置来确定这一点,因为它连接到10.0.2.0/24网络,这是这条路由的目标。默认情况下,Linux 会根据网络接口的配置自动添加路由。

10.0.2.0        0.0.0.0         255.255.255.0   U         0 0 0 enp0s3

这条路由是内核确保10.0.2.0/24网络的通信通过enp0s3接口进行的一种方式,因为这条路由将取代默认路由。在网络路由中,将始终使用最具体的路由。由于默认路由是通配符,而这条路由是特定于10.0.2.0/24网络的,因此这条路由将用于网络中的任何内容。

利用 IP 显示路由表

审查路由表的另一个工具是ip命令。从本章中使用的情况可以看出,ip命令是一个非常全面的实用工具,几乎可以用于现代 Linux 系统上的任何网络相关事务。

ip命令的一个用途是添加、删除或显示网络路由配置。要显示当前的路由表,只需执行带有route show选项的ip命令。

[db]# ip route show
default via 10.0.2.2 dev enp0s3  proto static  metric 1024
10.0.2.0/24 dev enp0s3  proto kernel  scope link  src 10.0.2.16
169.254.0.0/16 dev enp0s8  scope link  metric 1003
192.168.33.0/24 dev enp0s8  proto kernel  scope link  src 192.168.33.12
192.168.33.11 via 10.0.2.1 dev enp0s3  proto static  metric 1

虽然学习使用netstat命令对于非 Linux 操作系统很重要,但ip命令是任何 Linux 网络故障排除或配置的基本工具。

使用ip命令来排除故障路由时,我们甚至可能会发现它比netstat命令更容易。一个例子是查找默认路由。当ip命令显示默认路由时,它使用单词"default"作为目的地,而不是 0.0.0.0,这种方法对于新系统管理员来说更容易理解。

阅读其他路由也更容易。例如,之前在netstat中查看路由时,我们的示例路由如下:

10.0.2.0        0.0.0.0         255.255.255.0   U         0 0 0 enp0s3

使用ip命令,相同的路由以以下格式显示:

10.0.2.0/24 dev enp0s3  proto kernel  scope link  src 10.0.2.16

在我看来,ip route show 的格式比netstat -rn命令的格式简单得多。

寻找路由错误配置

现在我们知道如何查看服务器上的路由表,我们可以使用ip命令查找可能导致数据库连接问题的任何路由。

[db]# ip route show
default via 10.0.2.2 dev enp0s3  proto static  metric 1024
10.0.2.0/24 dev enp0s3  proto kernel  scope link  src 10.0.2.16
169.254.0.0/16 dev enp0s8  scope link  metric 1003
192.168.33.0/24 dev enp0s8  proto kernel  scope link  src 192.168.33.12
192.168.33.11 via 10.0.2.1 dev enp0s3  proto static  metric 1

在这里,我们可以看到系统上定义了五条路由。让我们分解这些路由,以更好地理解它们。

default via 10.0.2.2 dev enp0s3  proto static  metric 1024
10.0.2.0/24 dev enp0s3  proto kernel  scope link  src 10.0.2.16

我们已经介绍了前两条路由,不会再次复习。

169.254.0.0/16 dev enp0s8  scope link  metric 1003

第三条路由定义了所有来自169.254.0.0/16169.254.0.0169.254.255.255)的流量通过enp0s8设备发送。这是一个非常广泛的路由,但很可能不会影响我们到 IP192.168.33.11的路由。

192.168.33.0/24 dev enp0s8  proto kernel  scope link  src 192.168.33.12
192.168.33.11 via 10.0.2.1 dev enp0s3  proto static  metric 1

然而,第四和第五条路由将改变网络数据包到 192.168.33.11 的路由方式。

192.168.33.0/24 dev enp0s8  proto kernel  scope link  src 192.168.33.12

第四条路由定义了所有流量到192.168.33.0/24192.168.33.0192.168.33.255)网络都从enp0s8接口路由,并且源自192.168.33.12。这条路由似乎也是由enp0s8接口的配置自动添加的;这与enp0s3添加的早期路由类似。

由于enp0s8设备被定义为192.168.33.0/24网络的一部分,将该网络的流量路由到这个接口是合理的。

192.168.33.11 via 10.0.2.1 dev enp0s3  proto static  metric 1

然而,第五条路由定义了所有流量到特定 IP192.168.33.11(博客服务器的 IP)都通过enp0s3设备发送到10.0.2.1的网关。这很有趣,因为第五条路由和第四条路由有非常冲突的配置,因为它们都定义了对192.168.33.0/24网络中的 IP 该怎么做。

更具体的路由获胜

正如前面提到的,路由网络数据包的“黄金法则”是更具体的路由总是获胜。如果我们查看路由配置,我们有一个路由,它表示192.168.33.0/24子网中的所有流量应该从enp0s8设备出去。还有第二条路由,它明确表示192.168.33.11应该通过enp0s3设备出去。IP192.168.33.11适用于这两条规则,但系统应该通过哪条路由发送数据包呢?

答案总是更具体的路由。

由于第二条路由明确定义了所有流量到192.168.33.11都从enp0s3接口出去,内核将通过enp0s3接口路由所有返回的数据包。这种情况不受192.168.33.0/24或甚至默认路由的影响。

我们可以通过使用带有route get选项的ip命令来看到所有这些情况。

[db]# ip route get 192.168.33.11
192.168.33.11 via 10.0.2.1 dev enp0s3  src 10.0.2.16
 cache

带有route get选项的ip命令将获取提供的 IP 并输出数据包将经过的路由。

当我们使用这个命令与192.168.33.11一起使用时,我们可以看到ip明确显示数据包将通过enp0s3设备。如果我们使用相同的命令与其他 IP 一起使用,我们可以看到默认路由和192.168.33.0/24路由是如何使用的。

[db]# ip route get 192.168.33.15
192.168.33.15 dev enp0s8  src 192.168.33.12
 cache
[db]# ip route get 4.4.4.4
4.4.4.4 via 10.0.2.2 dev enp0s3  src 10.0.2.16
 cache
[db]# ip route get 192.168.33.200
192.168.33.200 dev enp0s8  src 192.168.33.12
 cache
[db]# ip route get 169.254.3.5
169.254.3.5 dev enp0s8  src 192.168.33.12
 cache

我们可以看到,当提供一个特定路由定义的子网内的 IP 地址时,将采用这个特定路由。然而,当 IP 没有被特定路由定义时,将采用默认路由。

假设

现在我们了解了到192.168.33.11的数据包是如何路由的,我们应该调整我们之前的假设,以反映192.168.33.11enp0s3的路由是不正确的,并且导致了我们的问题。

基本上,正在发生的事情(我们通过tcpdump看到了这一点)是,当数据库服务器(192.168.33.12)从博客服务器(192.168.33.11)接收到网络数据包时,它是通过enp0s8设备到达的。然而,当数据库服务器发送回复数据包(SYN-ACK)到 Web 应用服务器时,数据包是通过enp0s3接口发送出去的。

由于enp0s3设备连接到10.0.2.0/24网络,似乎数据包被10.0.2.0/24网络上的另一个系统或设备拒绝(RESET)。很可能,这是由于这是异步路由的一个典型例子。

异步路由是指数据包到达一个接口,但在另一个接口上回复。在大多数网络配置中,默认情况下是被拒绝的,但在某些情况下可以被启用;然而,这些情况并不是非常常见。

在我们的情况下,由于enp0s8接口是192.168.33.0/24子网的一部分,启用异步路由是没有意义的。我们的数据包到192.168.33.11应该简单地通过enp0s8接口路由。

反复试验

现在我们已经确定了数据收集的问题,并建立了我们的假设可能的原因,我们可以开始下一个故障排除步骤:使用试错法来纠正问题。

删除无效路由

为了纠正我们的问题,我们需要删除针对192.168.33.11的无效路由。为此,我们将再次使用ip命令,这次使用route del选项。

[db]# ip route del 192.168.33.11
[db]# ip route show
default via 10.0.2.2 dev enp0s3  proto static  metric 1024
10.0.2.0/24 dev enp0s3  proto kernel  scope link  src 10.0.2.16
169.254.0.0/16 dev enp0s8  scope link  metric 1003
192.168.33.0/24 dev enp0s8  proto kernel  scope link  src 192.168.33.12

在前面的例子中,我们使用了ip命令和route del选项来删除针对单个 IP 的路由。我们可以使用相同的命令和选项来删除针对子网定义的路由。以下示例将删除169.254.0.0/16网络的路由:

[db]# ip route del 169.254.0.0/16
[db]# ip route show
default via 10.0.2.2 dev enp0s3  proto static  metric 1024
10.0.2.0/24 dev enp0s3  proto kernel  scope link  src 10.0.2.16
192.168.33.0/24 dev enp0s8  proto kernel  scope link  src 192.168.33.12

ip route show的执行中,我们可以看到192.168.33.11不再存在冲突的路由。问题是:这个修复了我们的问题吗?唯一确定的方法是测试它,为此我们可以简单地刷新加载了博客错误页面的浏览器。

看来我们成功地纠正了问题。如果我们现在执行tcpdump,我们可以验证博客和数据库服务器能够通信。

[db]# tcpdump -nnvvv -i enp0s8 port 3306
tcpdump: listening on enp0s8, link-type EN10MB (Ethernet), capture size 65535 bytes
16:14:05.958507 IP (tos 0x0, ttl 64, id 7605, offset 0, flags [DF], proto TCP (6), length 60)
 192.168.33.11.47350 > 192.168.33.12.3306: Flags [S], cksum 0xa9a7 (correct), seq 4211276877, win 14600, options [mss 1460,sackOK,TS val 46129656 ecr 0,nop,wscale 6], length 0
16:14:05.958603 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 60)
 192.168.33.12.3306 > 192.168.33.11.47350: Flags [S.], cksum 0xc396 (incorrect -> 0x786b), seq 2378639726, ack 4211276878, win 14480, options [mss 1460,sackOK,TS val 46102446 ecr 46129656,nop,wscale 6], length 0
16:14:05.959103 IP (tos 0x0, ttl 64, id 7606, offset 0, flags [DF], proto TCP (6), length 52)
 192.168.33.11.47350 > 192.168.33.12.3306: Flags [.], cksum 0xdee0 (correct), seq 1, ack 1, win 229, options [nop,nop,TS val 46129657 ecr 46102446], length 0
16:14:05.959336 IP (tos 0x8, ttl 64, id 24256, offset 0, flags [DF], proto TCP (6), length 138)
 192.168.33.12.3306 > 192.168.33.11.47350: Flags [P.], cksum 0xc3e4 (incorrect -> 0x99c9), seq 1:87, ack 1, win 227, options [nop,nop,TS val 46102447 ecr 46129657], length 86
16:14:05.959663 IP (tos 0x0, ttl 64, id 7607, offset 0, flags [DF], proto TCP (6), length 52)

前面的输出是我们从一个健康的连接中期望看到的。

在这里,我们看到四个数据包,第一个是来自blog.example.com192.168.33.11)的SYNFlags [S]),接着是来自db.example.com192.168.33.12)的SYN-ACKFlags [S.]),以及来自blog.example.com192.168.33.12)的ACK(或SYN-ACK-ACK)(Flags [.])。这三个数据包是完成的 TCP 三次握手。第四个数据包是一个PUSHFlags [P.])数据包,这是实际的数据传输。所有这些都是良好工作的网络连接的迹象。

配置文件

现在我们已经从路由表中删除了无效的路由,我们可以看到博客正在工作;这意味着我们已经完成了,对吗?不,至少还没有。

当我们使用ip命令删除路由时,我们从活动路由表中删除了路由,但没有从整个系统中删除路由。如果我们重新启动网络,或者简单地重新启动服务器,这个无效的路由将重新出现。

[db]# service network restart
Restarting network (via systemctl):                        [  OK  ]
[db]# ip route show
default via 10.0.2.2 dev enp0s3  proto static  metric 1024
10.0.2.0/24 dev enp0s3  proto kernel  scope link  src 10.0.2.16
169.254.0.0/16 dev enp0s8  scope link  metric 1003
192.168.33.0/24 dev enp0s8  proto kernel  scope link  src 192.168.33.12
192.168.33.11 via 10.0.2.1 dev enp0s3  proto static  metric 1

这是因为当系统启动时,它会根据一组文件中的配置来配置网络。ip命令用于操作实时网络配置,而不是这些网络配置文件。因此,使用ip命令进行的任何更改都不是永久性的,而只是暂时的,直到系统下一次读取和应用网络配置为止。

为了完全从网络配置中删除这个路由,我们需要修改网络配置文件。

[db]# cd /etc/sysconfig/network-scripts/

在基于 Red Hat 企业 Linux 的系统上,网络配置文件大多存储在/etc/sysconfig/network-scripts文件夹中。首先,我们可以切换到这个文件夹并执行ls -la来识别当前的网络配置文件。

[db]# ls -la
total 228
drwxr-xr-x. 2 root root  4096 Mar 14 14:37 .
drwxr-xr-x. 6 root root  4096 Mar 14 23:42 ..
-rw-r--r--. 1 root root   195 Jul 22  2014 ifcfg-enp0s3
-rw-r--r--. 1 root root   217 Mar 14 14:37 ifcfg-enp0s8
-rw-r--r--. 1 root root   254 Apr  2  2014 ifcfg-lo
lrwxrwxrwx. 1 root root    24 Jul 22  2014 ifdown -> ../../../usr/sbin/ifdown
-rwxr-xr-x. 1 root root   627 Apr  2  2014 ifdown-bnep
-rwxr-xr-x. 1 root root  5553 Apr  2  2014 ifdown-eth
-rwxr-xr-x. 1 root root   781 Apr  2  2014 ifdown-ippp
-rwxr-xr-x. 1 root root  4141 Apr  2  2014 ifdown-ipv6
lrwxrwxrwx. 1 root root    11 Jul 22  2014 ifdown-isdn -> ifdown-ippp
-rwxr-xr-x. 1 root root  1642 Apr  2  2014 ifdown-post
-rwxr-xr-x. 1 root root  1068 Apr  2  2014 ifdown-ppp
-rwxr-xr-x. 1 root root   837 Apr  2  2014 ifdown-routes
-rwxr-xr-x. 1 root root  1444 Apr  2  2014 ifdown-sit
-rwxr-xr-x. 1 root root  1468 Jun  9  2014 ifdown-Team
-rwxr-xr-x. 1 root root  1532 Jun  9  2014 ifdown-TeamPort
-rwxr-xr-x. 1 root root  1462 Apr  2  2014 ifdown-tunnel
lrwxrwxrwx. 1 root root    22 Jul 22  2014 ifup -> ../../../usr/sbin/ifup
-rwxr-xr-x. 1 root root 12449 Apr  2  2014 ifup-aliases
-rwxr-xr-x. 1 root root   859 Apr  2  2014 ifup-bnep
-rwxr-xr-x. 1 root root 10223 Apr  2  2014 ifup-eth
-rwxr-xr-x. 1 root root 12039 Apr  2  2014 ifup-ippp
-rwxr-xr-x. 1 root root 10430 Apr  2  2014 ifup-ipv6
lrwxrwxrwx. 1 root root     9 Jul 22  2014 ifup-isdn -> ifup-ippp
-rwxr-xr-x. 1 root root   642 Apr  2  2014 ifup-plip
-rwxr-xr-x. 1 root root  1043 Apr  2  2014 ifup-plusb
-rwxr-xr-x. 1 root root  2609 Apr  2  2014 ifup-post
-rwxr-xr-x. 1 root root  4154 Apr  2  2014 ifup-ppp
-rwxr-xr-x. 1 root root  1925 Apr  2  2014 ifup-routes
-rwxr-xr-x. 1 root root  3263 Apr  2  2014 ifup-sit
-rwxr-xr-x. 1 root root  1628 Oct 31  2013 ifup-Team
-rwxr-xr-x. 1 root root  1856 Jun  9  2014 ifup-TeamPort
-rwxr-xr-x. 1 root root  2607 Apr  2  2014 ifup-tunnel
-rwxr-xr-x. 1 root root  1621 Apr  2  2014 ifup-wireless
-rwxr-xr-x. 1 root root  4623 Apr  2  2014 init.ipv6-global
-rw-r--r--. 1 root root 14238 Apr  2  2014 network-functions
-rw-r--r--. 1 root root 26134 Apr  2  2014 network-functions-ipv6
-rw-r--r--. 1 root root    30 Mar 13 02:20 route-enp0s3

从目录列表中,我们可以看到几个配置文件。然而,一般来说,我们主要只对以ifcfg-开头的文件和以route-开头的文件感兴趣。

ifcfg-开头的文件用于定义网络接口;这些文件的命名约定是“ifcfg-<设备名称>”;例如,要查看enp0s8的配置,我们可以读取ifcfg-enp0s8文件。

[db]# cat ifcfg-enp0s8
NM_CONTROLLED=no
BOOTPROTO=none
ONBOOT=yes
IPADDR=192.168.33.12
NETMASK=255.255.255.0
DEVICE=enp0s8
PEERDNS=no

我们可以看到,这个配置文件定义了用于这个接口的 IP 地址和Netmask

"route-“文件用于定义系统的路由配置。这个文件的约定与接口文件的约定相似,即"route-<设备名称>”。在文件夹列表中,只有一个路由文件route-enp0s3。这是定义不正确路由的最可能位置。

[db]# cat route-enp0s3
192.168.33.11/32 via 10.0.2.1

一般来说,除非定义了静态路由(静态定义的路由),否则"route-*"文件是不存在的。我们可以看到这里只定义了一个路由在这个文件中,这意味着路由表中定义的所有其他路由都是根据接口配置动态配置的。

在上面的例子中,route-enp0s3文件中定义的路由没有指定接口。因此,接口将根据文件名来定义;如果相同的条目出现在route-enp0s8文件中,网络服务将尝试在enp0s8接口上定义路由。

为了确保这个路由不再出现在路由表中,我们需要从这个文件中删除它;或者,在这种情况下,因为它是唯一的路由,我们应该完全删除这个文件。

[db]# rm route-enp0s3
rm: remove regular file 'route-enp0s3'? y

决定删除文件和路由取决于所支持的环境;如果您不确定这是否是正确的操作,应该询问能告诉您事先是否正确的人。在这个例子中,我们将假设可以删除这个网络配置文件。

重新启动网络服务后,我们应该看到路由消失。

[db]# service network restart
Restarting network (via systemctl):                        [  OK  ]
[db]# ip route show
default via 10.0.2.2 dev enp0s3  proto static  metric 1024
10.0.2.0/24 dev enp0s3  proto kernel  scope link  src 10.0.2.16
169.254.0.0/16 dev enp0s8  scope link  metric 1003
192.168.33.0/24 dev enp0s8  proto kernel  scope link  src 192.168.33.12

现在路由已经消失,网络配置已经重新加载,我们可以安全地说我们已经解决了问题。我们可以通过再次加载网页来验证这一点,以确保博客正常工作。

总结

如果我们回顾一下本章,我们对在 Linux 上解决网络连接问题学到了很多。我们学会了如何使用netstattcpdump工具来查看传入和传出的连接。我们了解了 TCP 的三次握手以及/etc/hosts文件如何取代 DNS 设置。

在本章中,我们涵盖了许多命令,虽然我们对每个命令及其功能都有一个相当好的概述,但有一些命令我们只是浅尝辄止。

诸如tcpdump之类的命令就是一个很好的例子。在本章中,我们使用了tcpdump相当多,但这个工具的功能远不止我们在本章中使用的那些。在本书中涵盖的所有命令中,我个人认为tcpdump是一个值得花时间学习的工具,因为它是一个非常有用和强大的工具。我用它解决了许多问题,有时这些问题不是特定于网络,而是特定于应用程序的。

在下一章中,我们将继续保持这种网络动力,解决防火墙问题。我们可能会看到一些在本章中使用的相同命令在下一章中再次出现,但这没关系;这只是显示了理解网络和故障排除工具的重要性。

本文地址:https://www.vps345.com/14796.html

搜索文章

Tags

docker 容器 运维 java-rabbitmq java PV计算 带宽计算 流量带宽 服务器带宽 上行带宽 上行速率 什么是上行带宽? CC攻击 攻击怎么办 流量攻击 DDOS攻击 服务器被攻击怎么办 源IP 开源 自动化 n8n dity make 服务器 linux rag ragflow ollama 大模型部署 人工智能 ubuntu linux环境变量 pytorch tensorflow LLM 大模型面经 大模型 职场和发展 Deepseek 大模型学习 搜索引擎 程序员 大模型应用 prompt 数据库 postgresql pgpool centos 嵌入式硬件 边缘计算 数据分析 机器学习 计算机视觉 qt linuxdeployqt 打包部署程序 appimagetool AI Dify Agent DeepSeek llama CrewAI python chatgpt gpu算力 安全 uniapp 网络 vue android studio 交互 后端 websocket android tcp/ip ai nlp 无人机 机器人 Linux DNS 硬件工程 单片机 物联网 区块链 分布式账本 web3 智能合约 信任链 共识算法 milvus flutter Google pay Apple pay deepseek 科技 大数据 云计算 debian wps vscode 安卓 Linux 维护模式 相机 知识图谱 大模型教程 AI大模型 pycharm flask 智能手机 计算机网络 web安全 网络安全 php 技能大赛 YOLO 深度学习 ide github ffmpeg 开发语言 macos MacMini Mac 迷你主机 mini Apple ESXi opencv 操作系统 nac 802.1 portal Qwen3 qwen3 32b vllm 本地部署 网络协议 gitlab 远程连接 MQTT mosquitto 消息队列 onlyoffice 在线office vim c++ 系统架构 阿里云 spring boot spring 微信开放平台 微信公众平台 微信公众号配置 华为 智能路由器 学习 笔记 WSL2 上安装 Ubuntu windows nginx oracle c语言 区块链项目 vmware 虚拟机 redis 分布式 缓存 服务器安全 网络安全策略 防御服务器攻击 安全威胁和解决方案 程序员博客保护 数据保护 安全最佳实践 网页服务器 web服务器 Nginx chrome ssh vue.js react.js 前端 HTTP 服务器控制 ESP32 DeepSeek rocketmq Docker引擎已经停止 Docker无法使用 WSL进度一直是0 镜像加速地址 远程 命令 执行 sshpass 操作 政务 分布式系统 监控运维 Prometheus Grafana top Linux top top命令详解 top命令重点 top常用参数 fiddler 爬虫 http 云原生 iventoy VmWare OpenEuler conda mysql 主从复制 Apache Beam 批流统一 性能优化 案例展示 数据分区 容错机制 kubernetes go GIS 遥感 WebGIS 多线程服务器 TCP服务器 qt项目 qt项目实战 qt教程 BMC IPMI 带外管理 tidb GLIBC mamba 服务器扩容没有扩容成功 harmonyos 编辑器 鸿蒙 鸿蒙系统 前端框架 车载系统 金融 算法 数据挖掘 kylin oceanbase rc.local 开机自启 systemd 麒麟 mcu ssl fpga开发 virtualenv arm 线程 多线程 ShapeFile GeoJSON Python ruoyi 环境迁移 Windows ai工具 存储维护 NetApp存储 EMC存储 mcp mcp协议 go-zero mcp服务器 串口服务器 万物互联 工业自动化 工厂改造 k8s 卸载 软件 列表 ROS 自动驾驶 c# 大模型推理 MCP AIGC ansible playbook 自动化运维 django 架构 语言模型 华为云 华为od udp 经验分享 kvm qemu libvirt 蓝桥杯 spring cloud Dell HPE 联想 浪潮 AI-native elasticsearch 7-zip UEFI Legacy MBR GPT U盘安装操作系统 jvm 游戏 程序人生 wsl2 wsl vsxsrv Python教程 pycharm安装 目标检测 YOLOv12 git maven 学习方法 音视频 servlet muduo 网络库 关系型 考研 数据结构 ddos 具身智能 强化学习 网络结构图 yaml Ultralytics 可视化 ssh漏洞 ssh9.9p2 CVE-2025-23419 ragflow 源码启动 游戏引擎 jenkins 1024程序员节 agi 部署 网工 运维开发 Deepseek-R1 私有化部署 推理模型 p2p hadoop big data CH340 串口驱动 CH341 uart 485 程序 编程 内存 性能分析 eureka IO pip Kali 渗透 自然语言处理 神经网络 H3C javascript edge cursor kafka tomcat 其他 Cursor webpack appium 软件测试 自动化测试 功能测试 课程设计 pyautogui rpa llm 群晖 低代码 ACL 流量控制 基本ACL 网络管理 规则配置 云电竞 云电脑 todesk ipython visualstudio 驱动开发 word图片自动上传 word一键转存 复制word图片 复制word图文 复制word公式 粘贴word图文 粘贴word公式 https 微服务 源码 毕业设计 RAGFlow Ollama excel CosyVoice 企业微信 嵌入式 linux驱动开发 arm开发 小程序 hdc 鸿蒙NEXT 图文教程 VMware虚拟机 macOS系统安装教程 macOS最新版 虚拟机安装macOS Sequoia Vmamba eclipse prometheus grafana intellij-idea 卷积神经网络 网络工程师 软考 2024 2024年上半年 下午真题 答案 AI编程 visual studio code 镜像源 transformer AI员工 Playwright pythonai PlaywrightMCP mysql离线安装 ubuntu22.04 mysql8.0 harmonyOS面试题 中兴光猫 换光猫 网络桥接 自己换光猫 SenseVoice bash 负载均衡 fstab rabbitmq ruby 计算机外设 bug 智慧农业 开源鸿蒙 团队开发 gcc g++ g++13 vmamba grep 智能驾驶 BEVFusion Ubuntu 面试 unity Claude Desktop Claude MCP Windows Cli MCP devops LSTM 信息与通信 FTP服务器 大模型入门 springsecurity6 oauth2 授权服务器 自定义客户端 SAS LLM Web APP Streamlit r语言 数据可视化 gaussdb jupyter uni-app 上传视频文件到服务器 uniApp本地上传视频并预览 uniapp移动端h5网页 uniapp微信小程序上传视频 uniapp app端视频上传 uniapp uview组件库 PyQt PySide6 gpt sdkman typescript 重启 排查 系统重启 日志 原因 mysql安装报错 windows拒绝安装 burpsuite 安全工具 mac安全工具 burp安装教程 渗透工具 adb Ubuntu20.04 2.35 cnn GoogLeNet pdf 办公自动化 自动化生成 pdf教程 电脑 镜像 docker-compose 前后端分离 图形化界面 vite vue3 yolov5 powerpoint Docker Docker Compose Kubernetes 压力测试 大模型压力测试 EvalScope 网络攻击模型 系统安全 v10 DeepSeek-R1 API接口 armbian u-boot oneapi 在线预览 xlsx xls文件 在浏览器直接打开解析xls表格 前端实现vue3打开excel 文件地址url或接口文档流二进 svn jmeter matlab C++软件实战问题排查经验分享 0xfeeefeee 0xcdcdcdcd 动态库加载失败 程序启动失败 程序运行权限 标准用户权限与管理员权限 webrtc IM即时通讯 QQ 微信 剪切板对通 HTML FORMAT 鲲鹏 昇腾 npu word yolov8 notepad gnu zabbix 远程工作 mac 截图 录屏 gif 工具 docker run 数据卷挂载 环境变量 端口映射 交互模式 知识库 本地知识库部署 DeepSeek R1 模型 5G 3GPP 卫星通信 QT 5.12.12 QT开发环境 Ubuntu18.04 gitee node.js 集成学习 集成测试 抓包工具 框架搭建 cron crontab日志 腾讯云 tcpdump 测试工具 LVM 磁盘分区 lvresize 磁盘扩容 pvcreate android-studio Apache Flume 数据采集 安装部署 配置优化 高级功能 大数据工具集成 SSE stm32 远程桌面 服务器配置 数据库系统 ESP32 模拟器 教程 键盘 卡死 list 安卓模拟器 隐藏文件 kotlin iphone openssl unix ArkUI ArkTS 移动端开发 iTerm2 VMware 软件需求 Ubuntu DeepSeek DeepSeek Ubuntu DeepSeek 本地部署 DeepSeek 知识库 DeepSeek 私有化知识库 本地部署 DeepSeek DeepSeek 私有化部署 selenium iftop 网络流量监控 视频编解码 vnc ui 前端面试题 持续部署 axure 富文本编辑器 网络爬虫 Alexnet 工具分享 nvm 网络药理学 生信 分子对接 autodock mgltools PDB PubChem VMware Tools vmware tools安装 vmwaretools安装步骤 vmwaretools安装失败 vmware tool安装步骤 vm tools安装步骤 vm tools安装后不能拖 vmware tools安装步骤 防火墙 端口号 开放端口 访问列表 智能体开发 lvs sql golang audio vue音乐播放器 vue播放音频文件 Audio音频播放器自定义样式 播放暂停进度条音量调节快进快退 自定义audio覆盖默认样式 服务器无法访问 ip地址无法访问 无法访问宝塔面板 宝塔面板打不开 YOLOv8 NPU Atlas800 A300I pro asi_bench 模型联网 API CherryStudio sqlserver 我的世界服务器搭建 iot gitea IIS服务器 IIS性能 日志监控 模拟退火算法 游戏服务器 Minecraft 数据库架构 数据管理 数据治理 数据编织 数据虚拟化 安全漏洞 信息安全 shell NVML nvidia-smi 微信小程序 notepad++ chrome devtools chromedriver GCC crosstool-ng shell脚本免交互 expect linux免交互 maxkb ARG open webui 设备树 毕设 arkUI arkTs Linux find grep nohup 异步执行 python3.11 openjdk Ubuntu 24 常用命令 Ubuntu 24 Ubuntu vi 异常处理 CUPS 打印机 Qt5 WSL2 IP 地址 arcgis 人工智能生成内容 7z Doris搭建 docker搭建Doris Doris搭建过程 linux搭建Doris Doris搭建详细步骤 Doris部署 opengl DevOps 软件交付 数据驱动 应用场景 数据安全 jdk RTSP xop RTP RTSPServer 推流 视频 Trae IDE AI 原生集成开发环境 Trae AI fastapi 华为认证 交换机 dell服务器 Flask FastAPI Waitress Gunicorn uWSGI Uvicorn 银河麒麟 rsyslog kind json html5 firefox triton 模型分析 micropython esp32 mqtt GPU 大文件分片上传断点续传及进度条 如何批量上传超大文件并显示进度 axios大文件切片上传详细教 node服务器合并切片 vue3大文件上传报错提示错误 vu大文件秒传跨域报错cors 语音识别 蓝桥杯C++组 C++ deepseek-r1 大模型本地部署 apache microsoft chatbox gitee go Java进程管理 DevOps自动化 脚本执行 跨平台开发 远程运维 Apache Exec JSch pygame 小游戏 五子棋 ip kali 共享文件夹 虚拟显示器 远程控制 Mac内存不够用怎么办 ubuntu20.04 开机黑屏 程序员创富 diskgenius rime 图像处理 MobaXterm 文件传输 策略模式 zip unzip comfyui comfyui教程 AnythingLLM AnythingLLM安装 实时音视频 实时互动 生物信息学 gromacs 分子动力学模拟 MD 动力学模拟 ip协议 单例模式 计算生物学 生物信息 基因组 ros ros1 Noetic 20.04 apt 安装 html qps 高并发 k8s部署 MySQL8.0 高可用集群(1主2从) 显卡驱动持久化 GPU持久化 SRS 流媒体 直播 硬件 设备 PCI-Express 升级 CVE-2024-7347 漏洞 回显服务器 UDP的API使用 大模型微调 雨云 NPS IIS .net core Hosting Bundle .NET Framework vs2022 AutoDL live555 rtsp rtp VM搭建win2012 win2012应急响应靶机搭建 攻击者获取服务器权限 上传wakaung病毒 应急响应并溯源 挖矿病毒处置 应急响应综合性靶场 xrdp 安全威胁分析 宝塔面板 同步 备份 建站 npm 反向代理 RAID RAID技术 磁盘 存储 监控 我的世界 我的世界联机 数码 rust腐蚀 file server http server web server 大语言模型 langchain 状态管理的 UDP 服务器 Arduino RTOS .net db jar Windsurf 宝塔 oracle fusion oracle中间件 NFC 近场通讯 智能门锁 虚拟化 EtherCAT转Modbus EtherCAT转485网关 ECT转485串口服务器 ECT转Modbus485协议 ECT转Modbus串口网关 ECT转Modbus串口服务器 es6 qt6.3 g726 华为OD 华为OD机试真题 可以组成网络的服务器 css css3 电路仿真 multisim 硬件工程师 硬件工程师学习 电路图 电路分析 仪器仪表 web 飞腾处理器 硬件架构 国产化 Python 视频爬取教程 Python 视频爬取 Python 视频教程 aws localhost 进程 进程控制 C 进程地址空间 chrome 浏览器下载 chrome 下载安装 谷歌浏览器下载 邮件APP 免费软件 嵌入式Linux IPC xshell termius iterm2 客户端 MySql java-ee MacOS 向日葵 xcode python2 ubuntu24.04 桌面环境 Isaac Sim 虚拟仿真 export env 变量 chromium dpi 打不开xxx软件 无法检查其是否包含恶意软件 IP配置 netplan SecureCRT lsb_release /etc/issue /proc/version uname -r 查看ubuntu版本 cuda Bug解决 Qt platform OpenCV Maxkb RAG技术 本地知识库 OpenCore 权限 鸿蒙面试 面试题 IMX317 MIPI H265 VCU 热榜 rpc apt 国内源 Python基础 Python技巧 rnn 多进程 camera Arduino 电子信息 yum换源 WebVM centos 7 dify 本地化部署 计算机 cudnn nvidia dash 正则表达式 进程信号 post.io 企业邮箱 搭建邮箱 单元测试 可用性测试 小智 京东云 DrissionPage Cline spark HistoryServer Spark YARN jobhistory FTP 服务器 RustDesk自建服务器 rustdesk服务器 docker rustdesk 个人博客 mount挂载磁盘 wrong fs type LVM挂载磁盘 Centos7.9 ping++ 宝塔面板访问不了 宝塔面板网站访问不了 宝塔面板怎么配置网站能访问 宝塔面板配置ip访问 宝塔面板配置域名访问教程 宝塔面板配置教程 孤岛惊魂4 filezilla 无法连接服务器 连接被服务器拒绝 vsftpd 331/530 adobe 国产操作系统 open Euler dde deepin 统信UOS 源码剖析 rtsp实现步骤 流媒体开发 windwos防火墙 defender防火墙 win防火墙白名单 防火墙白名单效果 防火墙只允许指定应用上网 防火墙允许指定上网其它禁止 媒体 EtherNet/IP串口网关 EIP转RS485 EIP转Modbus EtherNet/IP网关协议 EIP转RS485网关 EIP串口服务器 googlecloud 银河麒麟操作系统 express 测试用例 游戏程序 三级等保 服务器审计日志备份 NFS redhat 服务器繁忙 备选 网站 api 调用 示例 大数据平台 CDN rustdesk 镜像下载 freebsd lighttpd安装 Ubuntu配置 Windows安装 性能测试 服务器优化 Chatbox postman Qualcomm WoS QNN AppBuilder 虚幻 信号处理 ftp服务 文件上传 框架 .netcore 匿名FTP 邮件传输代理 SSL支持 chroot监狱技术 mongodb yum RAGFLOW RAG 检索增强生成 文档解析 大模型垂直应用 ios USB转串口 PX4 cocoapods Qwen2.5-coder 离线部署 版本 安装 高德地图 鸿蒙接入高德地图 HarmonyOS5.0 WSL resolv.conf 快捷键 旋转屏幕 自动操作 Ubuntu 22.04 算家云 算力租赁 安装教程 termux 环境搭建 mac设置host 虚拟现实 向量数据库 milvus安装 vm Xinference 数据集 影刀 #影刀RPA# 腾讯云大模型知识引擎 Docker Desktop 产测工具框架 IMX6ULL 管理框架 elk Logstash 日志采集 拓扑图 网络用户购物行为分析可视化平台 大数据毕业设计 软链接 硬链接 wireshark 自动化任务管理 虚拟局域网 seleium 3d 双系统 GRUB引导 Linux技巧 飞牛nas fnos ip命令 新增网卡 新增IP 启动网卡 flash-attention 报错 国产数据库 瀚高数据库 数据迁移 下载安装 kernel CPU 使用率 系统监控工具 linux 命令 tar remote-ssh 安装MySQL 命名管道 客户端与服务端通信 rtc xml 计算机学习路线 编程语言选择 easyTier 内网穿透 组网 论文笔记 deepseek-v3 ktransformers C语言 Kylin-Server 服务器安装 ftp 个人开发 微信小程序域名配置 微信小程序服务器域名 微信小程序合法域名 小程序配置业务域名 微信小程序需要域名吗 微信小程序添加域名 HTML audio 控件组件 vue3 audio音乐播放器 Audio标签自定义样式默认 vue3播放音频文件音效音乐 自定义audio播放器样式 播放暂停调整声音大小下载文件 react next.js 部署next.js opcua opcda KEPServer安装 做raid 装系统 ssrf 失效的访问控制 IMM web3.py Headless Linux string模拟实现 深拷贝 浅拷贝 经典的string类问题 三个swap kamailio sip VoIP WebRTC opensearch helm 项目部署到linux服务器 项目部署过程 火绒安全 弹性计算 云服务器 裸金属服务器 弹性裸金属服务器 创意 社区 DOIT 四博智联 zookeeper asp.net大文件上传 asp.net大文件上传下载 asp.net大文件上传源码 ASP.NET断点续传 asp.net上传文件夹 asp.net上传大文件 .net core断点续传 云服务 midjourney AI写作 asm ABAP 毕昇JDK llama.cpp 机柜 1U 2U Pyppeteer numpy 跨域请求 大模型训练/推理 推理问题 mindie c/s 密码学 哈希算法 minio 文件存储服务器组件 云计算面试题 宠物 免费学习 宠物领养 宠物平台 私有化 make命令 makefile文件 bcompare Beyond Compare GaN HEMT 氮化镓 单粒子烧毁 辐射损伤 辐照效应 软件工程 稳定性 看门狗 设计模式 软件卸载 系统清理 并查集 leetcode 通信工程 毕业 英语 本地环回 bind nohup后台启动 EVE-NG 终端工具 远程工具 ollama api ollama外网访问 进程间通信 GPU训练 裸机装机 linux磁盘分区 裸机安装linux 裸机安装ubuntu 裸机安装kali 裸机 华为鸿蒙系统 ArkTS语言 Component 生命周期 条件渲染 Image图片组件 Obsidian Dataview csrf MAC Kali Linux 蓝耘科技 元生代平台工作流 ComfyUI 工作流 agent workflow 实习 Claude 王者荣耀 okhttp searxng AI Agent 字节智能运维 目标跟踪 OpenVINO 推理应用 性能调优 安全代理 docker desktop image nftables scapy Linux Vim IPv4/IPv6双栈 双栈技术 网路规划设计 ensp综合实验 IPv4过渡IPv6 IPv4与IPv6 隐藏目录 文件系统 管理器 通配符 rust gru MAVROS 四旋翼无人机 Apache OpenNLP 句子检测 分词 词性标注 核心指代解析 mcp-proxy mcp-inspector fastapi-mcp sse Linux网络编程 MCP server C/S EMQX 通信协议 Invalid Host allowedHosts pillow vscode1.86 1.86版本 ssh远程连接 阻塞队列 生产者消费者模型 服务器崩坏原因 常用命令 文本命令 目录命令 NAT转发 NAT Server UDP EasyConnect LDAP cpu 实时 使用 单一职责原则 统信 虚拟机安装 echarts 信息可视化 网页设计 NAS Termux Samba Hyper-V WinRM TrustedHosts frp 内网服务器 内网代理 内网通信 LLMs 黑客 渗透测试 信息收集 直播推流 大大通 第三代半导体 碳化硅 高效日志打印 串口通信日志 服务器日志 系统状态监控日志 异常记录日志 FunASR ASR 算力 rtsp服务器 rtsp server android rtsp服务 安卓rtsp服务器 移动端rtsp服务 大牛直播SDK uni-file-picker 拍摄从相册选择 uni.uploadFile H5上传图片 微信小程序上传图片 阿里云ECS ECT转Modbus协议 EtherCAT转485协议 ECT转Modbus网关 C# MQTTS 双向认证 emqx 浏览器自动化 像素流送api 像素流送UE4 像素流送卡顿 像素流送并发支持 outlook 错误代码2603 无网络连接 2603 gunicorn 锁屏不生效 mariadb #STC8 #STM32 ecmascript 商用密码产品体系 rsync openEuler GPU状态 服务网格 istio 换源 Debian 混合开发 环境安装 JDK 灵办AI HarmonyOS Next DevEco Studio rclone AList webdav fnOS 历史版本 下载 本地部署AI大模型 产品经理 protobuf 序列化和反序列化 Linux无人智慧超市 LInux多线程服务器 QT项目 LInux项目 单片机项目 匿名管道 沙盒 openvpn server openvpn配置教程 centos安装openvpn 开发环境 matplotlib fonts-noto-cjk jellyfin nas lvm 磁盘挂载 iNode Macos gpt-3 Xshell 冯诺依曼体系 pthread 系统 node mybase 代码 对比 meld DiffMerge 创业创新 react native PostgreSQL15数据库 eNSP 企业网络规划 华为eNSP 网络规划 linux上传下载 can 线程池 perf openwrt USB网络共享 运维监控 minicom 串口调试工具 glibc Reactor GPU环境配置 Ubuntu22 CUDA PyTorch Anaconda安装 路径解析 KVM uv efficientVIT YOLOv8替换主干网络 TOLOv8 ue4 着色器 ue5 ros2 moveit 机器人运动 telnet 远程登录 ai小智 语音助手 ai小智配网 ai小智教程 智能硬件 esp32语音助手 diy语音助手 etcd RBAC springcloud Portainer搭建 Portainer使用 Portainer使用详解 Portainer详解 Portainer portainer perl 设置代理 实用教程 读写锁 指令 Typore rancher dubbo ubuntu安装 linux入门小白 compose Java LInux ci/cd RDP 新盘添加 partedUtil autoware 智能体 autogen openai coze 源代码 linux子系统 忘记密码 python高级编程 Ansible elk stack 传统数据库升级 银行 嵌入式实习 华为机试 AD域 MS Materials zotero WebDAV 同步失败 代理模式 Kylin OS intellij idea springboot unity3d HCIE 数通 豆瓣 追剧助手 迅雷 需求分析 规格说明书 查询数据库服务IP地址 SQL Server PVE 磁盘监控 VPS 外网访问 X11 Xming 网络编程 聊天服务器 套接字 TCP Socket 大文件秒传跨域报错cors Unity Dedicated Server Host Client 无头主机 文件分享 MI300x 显示管理器 lightdm gdm 雨云服务器 IPMITOOL 硬件管理 hugo grub 版本升级 扩容 skynet aarch64 编译安装 HPC 田俊楠 搭建个人相关服务器 CPU 主板 电源 网卡 视频平台 录像 视频转发 视频流 录音麦克风权限判断检测 录音功能 录音文件mp3播放 小程序实现录音及播放功能 RecorderManager 解决录音报错播放没声音问题 HP Anyware 视频监控 内网渗透 靶机渗透 智能电视 行情服务器 股票交易 速度慢 切换 股票量化接口 股票API接口 程序化交易 量化交易 高频交易 券商 股票交易接口api 类型 特点 element-ui 上传视频并预览视频 vue上传本地视频及进度条功能 vue2选择视频上传到服务器 upload上传视频组件插件 批量上传视频 限制单个上传视频 设计规范 华为昇腾910b3 qwen2vl finebi pyscenic 生信教程 电子信息工程 deekseek linux安装配置 捆绑 链接 谷歌浏览器 youtube google gmail alias unalias 别名 黑苹果 MacOS录屏软件 ArcTS 登录 ArcUI GridItem 微信分享 Image wxopensdk Ubuntu Server Ubuntu 22.04.5 Linux24.04 hive Hive环境搭建 hive3环境 Hive远程模式 多层架构 解耦 Redis Desktop Linux awk awk函数 awk结构 awk内置变量 awk参数 awk脚本 awk详解 Linux的基础指令 VMware安装mocOS macOS系统安装 neo4j safari trea idea ArkTs 进程优先级 调度队列 进程切换 高级IO epoll 磁盘清理 curl wget linux内核 华为证书 HarmonyOS认证 华为证书考试 UOS 开机自启动 桌面快捷方式 电脑桌面出现linux图标 电脑桌面linux图标删除不了 电脑桌面Liunx图标删不掉 linux图标删不掉 Echarts图表 折线图 柱状图 异步动态数据 鸿蒙开发 可视化效果 brew 材料工程 js fd 文件描述符 深度求索 私域 docker compose 开发 环境配置 ShenTong 中间件 可信计算技术 安全架构 DocFlow k8s集群资源管理 云原生开发 序列化反序列化 anaconda docker搭建nacos详解 docker部署nacos docker安装nacos 腾讯云搭建nacos centos7搭建nacos Docker Hub docker pull daemon.json 显卡驱动 增强现实 沉浸式体验 技术实现 案例分析 AR Cookie log4j docker搭建pg docker搭建pgsql pg授权 postgresql使用 postgresql搭建 聚类 mq 嵌入式系统开发 输入法 miniapp 真机调试 调试 debug 断点 网络API请求调试方法 监控k8s 监控kubernetes WLAN 文心一言 DBeaver 大模型技术 本地部署大模型 K8S k8s管理系统 burp suite 抓包 centos-root /dev/mapper yum clean all df -h / du -sh 免密 公钥 私钥 网易邮箱大师 宝塔面板无法访问 健康医疗 trae 全文检索 图搜索算法 命令模式 软考设计师 中级设计师 SQL 软件设计师 AI提示词优化 RagFlow 企业风控系统 互联网反欺诈 DDoS攻击 SQL注入攻击 恶意软件和病毒攻击 lstm LSTM-SVM 时间序列预测 stm32项目 c 网卡的名称修改 eth0 ens33 SSH Xterminal VSCode 物联网开发 webstorm ocr odoo 服务器动作 Server action tcp DeepSeek行业应用 Heroku 网站部署 nextjs reactjs 僵尸世界大战 游戏服务器搭建 HAProxy 银河麒麟桌面操作系统 多个客户端访问 IO多路复用 TCP相关API hibernate vSphere vCenter 软件定义数据中心 sddc 银河麒麟服务器操作系统 系统激活 XFS xfs文件系统损坏 I_O error dns是什么 如何设置电脑dns dns应该如何设置 ldap 服务器数据恢复 数据恢复 存储数据恢复 raid5数据恢复 磁盘阵列数据恢复 nuxt3 服务器部署ai模型 iis Netty 即时通信 NIO DigitalOcean GPU服务器购买 GPU服务器哪里有 GPU服务器 Dell R750XS java-rocketmq 远程看看 远程协助 银河麒麟高级服务器 外接硬盘 Kylin 计算虚拟化 弹性裸金属 崖山数据库 YashanDB swoole Java Applet URL操作 服务器建立 Socket编程 网络文件读取 能力提升 面试宝典 技术 IT信息化 致远OA OA服务器 服务器磁盘扩容 网络穿透 netty 加解密 Yakit yaklang JAVA mybatis 剧本 温湿度数据上传到服务器 Arduino HTTP 半虚拟化 硬件虚拟化 Hypervisor 网站搭建 serv00 博客 VMware创建虚拟机 .net mvc断点续传 KylinV10 麒麟操作系统 Vmware 信创 信创终端 中科方德 迁移指南 gradle TrueLicense Jellyfin 安防软件 飞牛 dba 互信 话题通信 服务通信 Web服务器 多线程下载工具 PYTHON 硅基流动 ChatBox 笔灵AI AI工具 海康 samba vCenter服务器 ESXi主机 监控与管理 故障排除 日志记录 RK3568 百度云 矩池云 数据下载 数据传输 web开发 教育电商 socket 机器人仿真 模拟仿真 HarmonyOS MateBook 数学建模 webgl 医疗APP开发 app开发 数据仓库 数据库开发 database 图形渲染 VMware安装Ubuntu Ubuntu安装k8s 命令行 基础入门 链表 飞牛NAS 飞牛OS MacBook Pro regedit 开机启动 embedding 分析解读 prometheus数据采集 prometheus数据模型 prometheus特点 cmos conda配置 conda镜像源 CentOS Stream CentOS 模拟实现 Linux PID Ubuntu共享文件夹 共享目录 Linux共享文件夹 c/c++ 串口 系统开发 binder framework 源码环境 Ubuntu22.04 开发人员主页 强制清理 强制删除 mac废纸篓 基础环境 iperf3 带宽测试 Linux权限 权限命令 特殊权限 Reactor反应堆 cmake MVS 海康威视相机 deep learning 动静态库 推荐算法 视觉检测 实时内核 Ardupilot 一切皆文件 可执行程序 photoshop Github加速 Mac上Github加速 Chrome浏览器插件 切换root stable diffusion jdk11安装 jdk安装 openjdk11 openjdk11安装 ceph 离线部署dify ollama下载加速 jina 跨域 k8s资源监控 annotations自动化 自动化监控 监控service 监控jvm AI代码编辑器 金仓数据库 2025 征文 数据库平替用金仓 Open WebUI 怎么卸载MySQL MySQL怎么卸载干净 MySQL卸载重新安装教程 MySQL5.7卸载 Linux卸载MySQL8.0 如何卸载MySQL教程 MySQL卸载与安装 HiCar CarLife+ CarPlay QT RK3588 DenseNet linux 命令 sed 命令 Linux的权限 springboot远程调试 java项目远程debug docker远程debug java项目远程调试 springboot远程 vr bat x64 SIGSEGV xmm0 软负载 监控k8s集群 集群内prometheus Node-Red 编程工具 流编程 WebUI DeepSeek V3 proxy模式 easyui DeepSeek r1 selete 链路聚合 端口聚合 win11 windows11 NLP模型 NLP ubuntu 18.04 思科 lb 协议 scikit-learn qt5 客户端开发 ROS2 蓝牙 大屏端 进程程序替换 execl函数 execv函数 execvp函数 execvpe函数 putenv函数 音乐服务器 Navidrome 音流 rdp 远程服务 论文阅读 物理地址 页表 虚拟地址 postgres Dify重启后重新初始化 solr ranger MySQL8.0 桥接模式 windows虚拟机 虚拟机联网 hosts hosts文件管理工具 开源软件 动态库 GCC编译器 -fPIC -shared 超级终端 多任务操作 提高工作效率 dns 电子器件 二极管 三极管 三维重建 实验 远程过程调用 Windows环境 无法解析服务器的名称或地址 SVN Server tortoise svn vasp安装 autodl kylin v10 麒麟 v10 Ubuntu 24.04.1 轻量级服务器 RTMP 应用层 高效远程协作 TrustViewer体验 跨设备操作便利 智能远程控制 流式接口 junit ecm bpm 压测 ECS 游戏机 TrinityCore 魔兽世界 恒源云 Wi-Fi bootstrap 统信操作系统 edge浏览器 深度优先 图论 并集查找 换根法 树上倍增 SSL 域名 Spring Security 移动云 windows日志 AI agent ISO镜像作为本地源 MNN Qwen vscode 1.86 ukui 麒麟kylinos openeuler 社交电子 直流充电桩 充电桩 内网环境 token sas Nuxt.js ux llama3 Chatglm 开源大模型 thingsboard cpp-httplib 联想开天P90Z装win10 bonding 思科模拟器 Cisco gateway Clion Nova ResharperC++引擎 Centos7 远程开发 SWAT 配置文件 服务管理 网络共享 执法记录仪 智能安全帽 smarteye SysBench 基准测试 代码调试 ipdb sqlite3 技术共享 矩阵 工业4.0 域名服务 DHCP 符号链接 配置 欧标 OCPP 状态模式 iBMC UltraISO 服务器管理 配置教程 网站管理 上传视频至服务器代码 vue3批量上传多个视频并预览 如何实现将本地视频上传到网页 element plu视频上传 ant design vue vue3本地上传视频及预览移除 AP配网 AK配网 小程序AP配网和AK配网教程 WIFI设备配网小程序UDP开 visual studio UOS1070e Qwen2.5-VL 云桌面 微软 AD域控 证书服务器 WebServer powerbi windows 服务器安装 clickhouse 服务器部署 本地拉取打包 联机 僵尸毁灭工程 游戏联机 开服 弹性服务器 CNNs 图像分类 动态规划 软件开发 pyside6 界面 Webserver 异步 MinIO Qt QModbus 机架式服务器 1U工控机 国产工控机 计算机系统 shard SoC lua anonymous HTTP状态码 客户端错误 服务器端错误 API设计 多端开发 智慧分发 应用生态 鸿蒙OS 僵尸进程 信号 小艺 Pura X sequoiaDB navicat sonoma 自动更新 deepseek r1 OpenHarmony EMUI 回退 降级 keepalived Attention threejs 3D mm-wiki搭建 linux搭建mm-wiki mm-wiki搭建与使用 mm-wiki使用 mm-wiki详解 代理服务器 交叉编译 Ark-TS语言 HarmonyOS NEXT 原生鸿蒙 支付 微信支付 开放平台 etl minecraft vpn 软件构建 DIFY sublime text Mac软件 AI作画 Tabs组件 TabContent TabBar TabsController 导航页签栏 滚动导航栏 initramfs Linux内核 Grub 空间 查错 sublime text3 macbook systemctl composer firewalld 管道 harmonyosnext SPI nano nacos isaacgym IO模型 TRAE VLAN 企业网络 互联网医院 多路转接 MDK 嵌入式开发工具 kerberos SSH 密钥生成 SSH 公钥 私钥 生成 ssh远程登录 IPv4 子网掩码 公网IP 私有IP 代理 OpenSSH P2P HDLC firewall PPI String Cytoscape CytoHubba 虚幻引擎 流水线 脚本式流水线 lio-sam SLAM 实战案例 CLion 免费域名 域名解析 OpenManus cfssl 接口优化 k8s二次开发 virtualbox copilot import save load 迁移镜像 ebpf uprobe GameFramework HybridCLR Unity编辑器扩展 自动化工具 底层实现 ICMP 内存管理 考试 脚本 GRE Alist mount 挂载 网盘 UFW 线程同步 线程互斥 条件变量 Crawlee finalsheel risc-v 多媒体 BitTorrent 搜索 pppoe radius SSL证书 code-server CORS pyqt 飞书 无桌面 黑客技术 SEO iDRAC R720xd jetty undertow 相差8小时 UTC 时间 sysctl.conf vm.nr_hugepages mock mock server 模拟服务器 mock服务器 Postman内置变量 Postman随机数据 KingBase URL nfs 备份SQL Server数据库 数据库备份 傲梅企业备份网络版 Erlang OTP gen_server 热代码交换 事务语义 业界资讯 xss Unity插件 Anolis nginx安装 linux插件下载 sentinel 磁盘镜像 服务器镜像 服务器实时复制 实时文件备份 分布式训练 vue-i18n 国际化多语言 vue2中英文切换详细教程 如何动态加载i18n语言包 把语言json放到服务器调用 前端调用api获取语言配置文件 wsgiref Web 服务器网关接口 W5500 OLED u8g2 LORA XCC Lenovo idm OD机试真题 服务器能耗统计 繁忙 解决办法 替代网站 汇总推荐 AI推理 智能音箱 智能家居 小智AI服务端 xiaozhi TTS 音乐库 西门子PLC 通讯 win服务器架设 windows server less 小番茄C盘清理 便捷易用C盘清理工具 小番茄C盘清理的优势尽显何处? 教你深度体验小番茄C盘清理 C盘变红?!不知所措? C盘瘦身后电脑会发生什么变化? 代码托管服务 云耀服务器 React Next.js 开源框架 Web应用服务器 浪潮信息 AI服务器 代码规范 zerotier 联网 easyconnect 树莓派 显示器 金仓数据库概述 金仓数据库的产品优化提案 日志分析 系统取证 AimRT 支持向量机 源代码管理 热键 slave 粘包问题 语法 av1 电视盒子 机顶盒ROM 魔百盒刷机 VR手套 数据手套 动捕手套 动捕数据手套 元服务 应用上架 玩机技巧 软件分享 软件图标 合成模型 扩散模型 图像生成 Linux环境 cd 目录切换 用户缓冲区 查看显卡进程 fuser su sudo 内核 ELF加载 网络文件系统 多产物 csrutil mac恢复模式进入方法 SIP 恢复模式 Maven 用户管理 beautifulsoup 玩游戏 大版本升 升级Ubuntu系统 Office llamafactory 微调 Charles STL cpolar 接口返回 devmem 容器技术 wifi驱动 RockyLinux Bluetooth 配对 openssh figma SSH 服务 SSH Server OpenSSH Server ubuntu24.04.1 fast TCP协议 xpath定位元素 bot 浏览器开发 AI浏览器 openstack Xen seatunnel ubuntu24 vivado24 yum源切换 更换国内yum源 自动化编程 烟花代码 烟花 元旦 端口 查看 ss 项目部署 网络建设与运维 网络搭建 神州数码 神州数码云平台 云平台 显示过滤器 Wireshark安装 问题解决 钉钉 自学笔记 小米 澎湃OS Android 解决方案 System V共享内存 进程通信 Sealos 电视剧收视率分析与可视化平台 图片增强 增强数据 Mermaid 可视化图表 Zoertier 内网组网 Docker快速入门 netlink libnl3 GeneCards OMIM TTD VGG网络 卷积层 池化层 WireGuard 异地组网 ufw SystemV 计算机科学与技术 ajax sudo原理 su切换 IPv6 IPv6测试 IPv6测速 IPv6检测 IPv6查询 kubeless client-go Linux的基础开发工具 Trae叒更新了? VM虚拟机 anythingllm open-webui docker国内镜像 MLLMs VLM gpt-4v 文件共享 openresty 工作流自动化工具 bushujiaocheng 部署教程 AI算力 租算力 到算家云 laravel 根服务器 wpf 服务器时间 es h.264 聊天室 sqlite tailscale derp derper 中转 游戏开发 架构与原理 chfs ubuntu 16.04 服务器主板 AI芯片 IDEA 北亚数据恢复 oracle数据恢复 VNC VS Code AD 域管理 wordpress 无法访问wordpess后台 打开网站页面错乱 linux宝塔面板 wordpress更换服务器 超融合 端口测试 saltstack MQTT协议 消息服务器 nosql 数字证书 签署证书 banner client close 授时服务 北斗授时 服务器ssl异常解决 glm4 deepseak 豆包 KIMI 腾讯元宝 高效I/O VPN wireguard 网络原理 umeditor粘贴word ueditor粘贴word ueditor复制word ueditor上传word图片 ueditor导入word ueditor导入pdf ueditor导入ppt logstash AWS 火山引擎 fpga ECS服务器 OpenGL sse_starlette Starlette Server-Sent Eve 服务器推送事件 移动魔百盒 移动开发 ArtTS whistle 搜狗输入法 中文输入法 fork wait waitpid exit 跨平台 终端 archlinux kde plasma retry 重试机制 子系统 NVM Node Yarn PM2 提示词 流程图 mermaid 互联网实用编程指南 影视app mac安装软件 mac卸载软件 mac book clipboard 剪贴板 剪贴板增强 mac cocoapods macos cocoapods Mac部署 Ollama模型 Openwebui 配置教程 AI模型 进程等待 内存泄漏 开发工具 容器清理 大文件清理 空间清理 RoboVLM 通用机器人策略 VLA设计哲学 vlm fot robot 视觉语言动作模型 docker命令大全 docker部署Python 李心怡 docker部署翻译组件 docker部署deepl docker搭建deepl java对接deepl 翻译组件使用 抗锯齿 dock 加速 deployment daemonset statefulset cronjob 集群管理 Helm k8s集群 生活 静态IP Masshunter 质谱采集分析软件 使用教程 科研软件 fabric HBase分布式集群 HBase环境搭建 HBase安装 HBase完全分布式环境 Radius 线性代数 电商平台 国标28181 监控接入 语音广播 流程 SDP 佛山戴尔服务器维修 佛山三水服务器维修 宕机切换 服务器宕机 AISphereButler flink 干货分享 黑客工具 密码爆破 流量运营 ardunio BLE hexo 服务器正确解析请求体 Carla 配置原理 负载测试 yashandb 充电桩平台 充电桩开源平台 solidworks安装 免费 massa sui aptos sei java-zookeeper paddle lrzsz Putty 花生壳 cocos2d 3dcoat pxe 概率论 accept h.265 腾讯云服务器 轻量应用服务器 linux系统入门 linux命令 issue linq 站群服务器 性能监控 风扇控制软件 极限编程 xfce pyicu 星河版 NVIDIA win向maOS迁移数据 mvc miniconda Bandizip Mac解压 Mac压缩 压缩菜单 顽固图标 启动台 IP地址 计算机基础 苹果电脑装windows系统 mac安装windows系统 mac装双系统 macbook安装win10双 mac安装win10双系统 苹果电脑上安装双系统 mac air安装win 系统内核 Linux版本 树莓派项目 RAGflow 红黑树 uni-app x 文件清理 tftp 达梦 DM8 AzureDataStudio 机械臂 webview docker search d3d12 知行EDI 电子数据交换 知行之桥 EDI homeassistant 轮播图 代理配置 企业级DevOps podman registries rxjava 三次握手 caddy bigdata PTrade QMT 量化股票 静态NAT 零日漏洞 CVE macOS 欧拉系统 qtcreator java毕业设计 微信小程序医院预约挂号 医院预约 医院预约挂号 小程序挂号 ubantu GKI KMI AppLinking 应用间跳转 function address 函数 地址 进程池实现 direct12 C/C++ Linux指令 时间轮 泰山派 根文件系统 编译 烧录 嵌入式实时数据库 NVIDIA显卡安装 Ubuntu开机黑屏 零售 输入系统 watchtower 红黑树封装map和set 医院门诊管理系统 BCLinux LLaMA-Factory GPUGEEK 集成 scrapy 5分钟快速学 docker入门 dockerfile libreoffice 转换 恒玄BES 共享 设置 nacos容器环境变量 docker启动nacos参数 nacos镜像下载 Linux系统编程 根目录 WINCC OpenManage 自定义登录信息展示 motd 美化登录 实时云渲染 云渲染 3D推流 LVS rtcp CPU架构 服务器cpu vue在线预览excel和编辑 vue2打开解析xls电子表格 浏览器新开页签或弹框内加载预览 文件url地址或接口二进制文档 解决网页打不开白屏报错问题 大厂程序员 硅基计算 碳基计算 认知计算 生物计算 AGI 系统架构设计 软件哲学 程序员实现财富自由 5090 显卡 AI性能 A2A Svelte OpenAI 全栈 机架式 IDC aiohttp asyncio 独立服务器 模块测试 bug定位 缺陷管理 数据库管理 Metastore Catalog 导航栏 青少年编程 自定义shell当中管道的实现 匿名和命名管道 broadcom MySQL 基础指令 本地不受DeepSeek word转pdf charles chrome历史版本下载 chrominum下载 路径规划 环境部署 文档 coffeescript Autoware 辅助驾驶 CAD瓦片化 栅格瓦片 矢量瓦片 Web可视化 DWG解析 金字塔模型 web环境 4 - 分布式通信、分布式张量 代码复审 烟雾检测 yolo检测 消防检测 springboot容器部署 springboot容器化部署 微服务容器化负载均衡配置 微服务容器多节点部署 微服务多节点部署配置负载均衡 通用环境搭建 仓库 影刀证书 分享 服务注册与发现 电脑操作 CodeBuddy首席试玩官 dify部署 生成对抗网络 STP 生成树协议 PVST RSTP MSTP 防环路 网络基础 proxy_pass Playwright MCP 电子学会 Nginx报错413 Request Entity Too Large 的客户端请求体限制 authing aac openvino 能源 动态域名 惠普服务器 惠普ML310e Gen8 惠普ML310e Gen8V2 站群 多IP 风扇散热策略 曙光 海光 宁畅 中科可控 RNG 状态 可复现性 随机数生成 光电器件 LED 分布式数据库 集中式数据库 业务需求 选型误 客户端-服务器架构 点对点网络 服务协议 网络虚拟化 网络安全防御 科勘海洋 数据采集浮标 浮标数据采集模块 Cache Aside Read/Write Write Behind 分类 视频服务器 proto actor actor model Actor 模型 雾锁王国 bert swift 实时日志 logs NTP服务器 能效分析 nginx默认共享目录 端口开放 tar.gz tar.xz linux压缩 pipe函数 管道的大小 匿名管道的四种情况 ps命令 手动分区 cuda驱动 Navigation 路由跳转 鸿蒙官方推荐方式 鸿蒙原生开发 Python学习 Python编程 FS bootfs rootfs linux目录 Linux的进程控制 亲测 linux/cmake linux常用命令 homebrew windows转mac ssh密匙 Mac配brew环境变量 gstreamer Eigen 云盘 安全组 zephyr lvgl8.3 lvgl9.2 lvgl lvgl安装 智能问答 Spring AI Milvus mapreduce 定义 核心特点 优缺点 适用场景 Multi-Agent 蜂窝网络 频率复用 射频单元 无线协议接口RAN 主同步信号PSS Modbus TCP 基本指令 模板 高可用 containerd 软路由 线程安全 微信自动化工具 微信消息定时发送 文件权限 CAN 多总线 docker安装mysql win下载mysql镜像 mysql基本操作 docker登陆私仓 docker容器 deepseek与mysql 多系统 触觉传感器 GelSight GelSightMini GelSight触觉传感器 Quixel Fab UE5 游戏商城 electron pnet 百度 pnetlab 工厂方法模式 环境 非root access blocked 破解 vmware tools 制造 流量 证书 签名 排序算法 选择排序 rtmp gitlab服务器 物理服务器 服务器租用 云服务器租用 物理机租用 网络接口 时间间隔 所有接口 多网口 事件分析 边缘服务器 利旧 AI识别 DELL R730XD维修 全国服务器故障维修 raid AI导航站 eventfd 高性能 Modbustcp服务器 SFTP SFTP服务端 GRANT REVOKE OS 软件商店 livecd systemtools Ubuntu 24.04 搜狗输入法闪屏 Ubuntu中文输入法 orbslam2 ANDROID_HOME zshrc latex 更换镜像源 IT 护眼模式 Linux的进程概念 重置密码 codereview code-review 系统完整性 越狱设备 VUE Mysql PATH 命令行参数 main的三个参数 激光雷达 镭眸 GenAI LLM 推理优化 LLM serving 机床 仿真 课件 教学 课程 九天画芯 铁电液晶 显示技术 液晶产业 技术超越 数码相机 全景相机 设备选择 实用技巧 数字空间 二级页表 tvm安装 深度学习编译器 杂质 webgis cesium HP打印机 RHCE 冯诺依曼体系结构 漏洞报告生成 手机 容器化 Serverless 学习路线 Web3 Telegram nmcli 网络配置 访问公司内网 安全性测试 写时拷贝 Linux的进程调度队列 活动队列 sql注入 回归 原子操作 AXI MinerU 权限掩码 粘滞位 量子计算 muduo库 gerrit 仙盟大衍灵机 东方仙盟 仙盟创梦IDE usb typec 自动化测试框架 dnf 编译器 计算机八股 huggingface CKA docker 失效 docker pull失效 docker search超时 集合 List vb SonarQube 泛型编程 Async注解 事件驱动 janus MCP 服务器 JADX-AI 插件 jQuery EF Core 客户端与服务器评估 查询优化 数据传输对象 查询对象模式 实时传输 服务器托管 云托管 数据中心 idc机房 linux cpu负载异常 网络IO 队列 数据库占用空间 LangGraph 模型上下文协议 MultiServerMCPC load_mcp_tools load_mcp_prompt 若依 内存不足 outofmemory Key exchange 主包过大 Windows 11 重装电脑系统 鼠标 teamspeak 磁盘IO iostat FreeRTOS 进程管理 麒麟OS Lenovo System X GNOME Scoket js逆向 labview 观察者模式 SQI iOS Trust Authentication Challenge mujoco 迁移 Unlocker C++11 lambda Windows应急响应 应急响应 webshell 网络攻击防御 网络攻击 命令键 Makefile Windows Hello 摄像头 指纹 生物识别 bpf bpfjit pcap vr看房 在线看房系统 房产营销 房产经济 三维空间 汽车 软硬链接 文件 药品管理 HarmonyOS SDK Map Kit 地图 elementui 若依框架 English 服务 视频号 鸿蒙项目 程序地址空间 数据库数据恢复 #Linux #shell #脚本 vsode arkts arkui dnn Make 单用户模式 智能手表 Pura80 WATCH 5 nvcc A100 数字比特流 模拟信号 将二进制数据映射到模拟波形上 频谱资源 振幅频率相位 载波高频正弦波 MobileNetV3 深度强化学习 深度Q网络 Q_Learning 经验回收 Jenkins流水线 声明式流水线 笔记本电脑 dsp开发 pow 指数函数 优化 AOD-PONO-Net 图像去雾技术 NAT 软件安装 Agentic Web NLWeb 自然语言网络 微软build大会 编程与数学 Typescript ROS1/ROS2 Wayland 开启关闭防火墙 N8N 机器人操作系统 AI控制浏览器 Browser user 开发效率 Windmill 责任链模式 authorized_keys 密钥 改行学it openlayers bmap tile server TraeAgent Spring Boot http状态码 请求协议 asp.net 低成本 网络带宽 问题排查 RHEL 去中心化 过期连接 Featurize Mobilenet 分割 信创国产化 达梦数据库 pikachu靶场 XSS漏洞 XSS DOM型XSS CLI JavaScript langgraph.json messages dmesg 报警主机 豪恩 VISTA120 乐可利 霍尼韦尔 枫叶 时刻 矩阵乘法 3D深度学习 udp回显服务器 CSDN开发云 zipkin funasr asr 语音转文字 Web测试 gpu flinkcdc 大模型应用开发 AI 应用商业化 参数服务器 分布式计算 数据并行 加密 哥sika CTE AGE debezium 数据变更 android-ndk illustrator octomap_server react Native 学习笔记 包装类 CKEditor5 桶装水小程序 在线下单送水小程序源码 桶装水送货上门小程序 送水小程序 订水线上商城 源码软件 调试方法 Valgrind 内存分析工具 实战项目 入门 精通 CTF 云解析 云CDN SLS日志服务 云监控 HarmonyOS 5开发环境 CUDA Toolkit 恢复 libtorch Arduino下载开发板 esp32开发板 esp32-s3 记账软件 容器部署 ICMPv6 network NetworkManager unionFS OverlayFS OCI docker架构 写时复制 VAD 视频异常检测 VAR 视频异常推理 推理数据集 强化微调 GRPO 简单工厂模式 客户端和服务器端 BMS 储能 pandas Cilium 最新微服务 思科实验 高级网络互联 中文分词 麒麟kos 网络检测 ping guava 实时语音识别 流式语音识别 BiSheng 进程状态 地平线5 OSB Oracle中间件 SOA 相机标定 服务发现 nvidia驱动 Tesla显卡 高考 MQTT Broker GMQT 客户端/服务器架构 分布式应用 三层架构 Web应用 跨平台兼容性 dfs blender three.js 数字孪生 决策树 漫展 滑动验证码 反爬虫 英语六级 cs144 接口隔离原则 大学大模型可视化教学 全球气象可视化 大学气象可视化 算法协商 故障排查 scala cangjie struts 物联网嵌入式开发实训室 物联网实训室 嵌入式开发实训室 物联网应用技术专业实训室 物理机 Java 日志框架 Log4j2 Logback SLF4J 结构化日志 企业级应用 材质 贴图 alphafold3 gemini gemini国内访问 gemini api gemini中转搭建 Cloudflare ESP8266简单API服务器 Arduino JSON webserver 支付宝小程序 云开发 DICOM pve TCP回显服务器 jvm调优 LRU策略 内存增长 垃圾回收 分布式总线 时序数据库 iotdb 美食 路由器 对话框showDialog showActionMenu 操作列表ActionSheet CustomDialog 文本滑动选择器弹窗 消息提示框 警告弹窗 rk3588 rknn-toolkit2 更新apt 安装hadoop前的准备工作 FCN WebFuture OpenTiny 打包工具 WinCC OT与IT SCADA 智能制造 MES 信奥 dataworks maxcompute 省份 年份 分数线 数据 几何绘图 三角函数 UDS Bootloader 嵌入式软件 SSM 项目实战 页面放行 物理层 重构 迭代器模式 数字化转型 局域网 数据链路层 驱动器映射 批量映射 win32wnet模块 网络驱动器映射工具 shell编程 开启黑屏 tty2 抽象工厂模式 BIO Java socket Java BIO Java NIO Java 网络编程 医药 协作 快速入门 进度条 FS100P cn2 带宽 mcp client mcp server FreeLearning EulerOS 版本对应 Linux 发行版 企业级操作系统 开源社区 mysql 8 mysql 8 忘记密码 RustDesk 搭建服务器 AudioLM 弹性 华为OD机考 机考真题 需要广播的服务器数量 pavucontrol 蓝牙耳机 cordova 跨域开发 mobaxterm dockercompose安装 compose.yml文件详解 dockercompose使用