<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="zh_CN">
  <title>Shirone</title>
  <subtitle>A Material 3 anime blog</subtitle>
  <link href="https://blog-l7wd3.pages.dev/" rel="alternate" type="text/html"/>
  <link href="https://blog-l7wd3.pages.dev/atom.xml" rel="self" type="application/atom+xml"/>
  <id>https://blog-l7wd3.pages.dev/</id>
  <updated>2026-09-11T00:00:00.000Z</updated>
  <entry>
    <title>一文讲通 LVM 与传统扩容的异同</title>
    <link href="https://blog-l7wd3.pages.dev/posts/lvm/" rel="alternate" type="text/html"/>
    <id>https://blog-l7wd3.pages.dev/posts/lvm/</id>
    <published>2026-09-11T00:00:00.000Z</published>
    <updated>2026-09-11T00:00:00.000Z</updated>
    <summary>从分区与文件系统的职责划分讲清 LVM 与传统分区的差异，并给出新增磁盘、在线扩容的完整流程、命令与常见坑。</summary>
    <content type="html"><![CDATA[<blockquote>
<p>本文思维链：理解分区职能 → 拆开传统分区的两个角色 → 看清 LVM 的三层抽象 → 落到扩容流程与常见坑</p>
</blockquote>
<h2>基本概念</h2>
<p>Linux 的存储从上到下是一条链：<strong>物理磁盘 → 分区表 → 分区 / PV → （VG）→ 文件系统 → 挂载点</strong>。
日常最容易混淆的是"分区"和"文件系统"——把它们分开看，后面所有扩容操作才讲得通。</p>
<h3>分区概念</h3>
<p><strong>分区不是一块独立的设备</strong>，它只是分区表里的一条记录：「从第 X 个扇区开始，共 Y 个扇区」。内核读到这条记录后，生成一个块设备 <code>/dev/sdb1</code>，上层就能像读写整盘一样读写这段地址范围。</p>
<p><strong>分区表</strong>写在磁盘最前面，常见两种：</p>
<table>
<thead>
<tr>
<th>类型</th>
<th>位置</th>
<th>分区数上限</th>
<th>容量上限</th>
<th>特点</th>
</tr>
</thead>
<tbody>
<tr>
<td>MBR</td>
<td>第 0 扇区（512 B）</td>
<td>4 个主分区（或 3 主 + 1 扩展）</td>
<td>2 TiB</td>
<td>老、兼容性好，改完需重读</td>
</tr>
<tr>
<td>GPT</td>
<td>头部 + 尾部各一份，带 CRC 校验</td>
<td>默认 128 个</td>
<td>远超 2 TiB</td>
<td>自带备份表，主表损坏可恢复</td>
</tr>
</tbody>
</table>
<p><strong>分区与文件系统的分工</strong>：<code>mkfs.xfs /dev/sdb1</code> 是把文件系统的超级块、空闲块位图、inode 表<strong>写进分区覆盖的地址范围</strong>。也就是说：</p>
<ul>
<li><strong>分区</strong>决定「文件系统能用多大」——地址范围写在分区表里，建好即固定；</li>
<li><strong>文件系统</strong>决定「这些空间怎么组织数据」——元数据写在分区内部。</li>
</ul>
<p>两边互不知情，所以扩容时要<strong>拆成两次扩</strong>：先改分区表/LV 映射让块设备变大，再通知文件系统吃满新空间。这是全文最关键的一句话。</p>
<pre><code>flowchart TD
    A[物理磁盘 /dev/sdb] --&gt; B[分区表 MBR / GPT]
    B --&gt; C[分区 /dev/sdb1]
    C --&gt; D[文件系统 XFS / EXT4]
    D --&gt; E[挂载点 /data]
</code></pre>
<p><strong>查看命令</strong></p>
<pre><code>lsblk -f                              # 树形展示：磁盘→分区→文件系统→挂载点
lsblk -o NAME,SIZE,TYPE,MOUNTPOINT
blkid                                 # 查看 UUID / 文件系统类型
fdisk -l /dev/sdb                     # 查看分区表
</code></pre>
<h3>LVM概念</h3>
<p><strong>LVM（Logical Volume Manager）</strong> 是架在 <code>device-mapper</code>（dm）之上的卷管理层：它把「物理空间」和「逻辑空间」解耦，用一张可动态修改的<strong>映射表</strong>取代固定地址。</p>
<p>层次结构：物理磁盘/分区 → <strong>PV</strong> → <strong>VG</strong> → <strong>LV</strong> → 文件系统</p>
<table>
<thead>
<tr>
<th>层</th>
<th>全称</th>
<th>作用</th>
<th>关键命令</th>
<th>类比</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>PV</strong></td>
<td>Physical Volume</td>
<td>被 LVM 纳管的块设备；空间切成固定大小的 PE（默认 4 MiB），头部写入 LVM 元数据</td>
<td><code>pvcreate</code> / <code>pvs</code> / <code>pvdisplay</code></td>
<td>砖头</td>
</tr>
<tr>
<td><strong>VG</strong></td>
<td>Volume Group</td>
<td>把一个或多个 PV 汇总成存储池，池内空间以 PE 为单位统一编号</td>
<td><code>vgcreate</code> / <code>vgextend</code> / <code>vgs</code></td>
<td>砖堆</td>
</tr>
<tr>
<td><strong>LV</strong></td>
<td>Logical Volume</td>
<td>从 VG 里切出一段空间，由若干 LE 组成，与 PE 一一映射</td>
<td><code>lvcreate</code> / <code>lvextend</code> / <code>lvs</code></td>
<td>砌好的墙</td>
</tr>
<tr>
<td><strong>PE / LE</strong></td>
<td>Physical / Logical Extent</td>
<td>分配的最小粒度：PE 在 PV 上，LE 在 LV 上，默认 4 MiB</td>
<td><code>pvcreate -s</code> / <code>lvcreate -l</code></td>
<td>砖的单位</td>
</tr>
</tbody>
</table>
<p><strong>映射是怎么实现的</strong>：LV 并不是"把某块盘的一部分划出来"，而是 dm 里的一张 <code>linear</code> 映射表——「LV 的第 0 ~ 10239 个 LE → /dev/sdb 上第 0 ~ 10239 个 PE」。由此得到三个直接结论：</p>
<ul>
<li>一个 LV 可以<strong>横跨多块 PV</strong>；</li>
<li>扩容只是<strong>往映射表尾部追加条目</strong>，已有条目的地址不动，所以不需要搬数据；</li>
<li>设备节点其实是 <code>/dev/mapper/vg_data-lv_data</code>，<code>/dev/vg_data/lv_data</code> 只是 udev 生成的软链接，两者是同一个设备。</li>
</ul>
<p><strong>元数据存在哪</strong>：LVM 自己的元数据（PV/VG/LV 结构、PE 分配表）有两份——磁盘上每个 PV 头部的 label + metadata area，以及 <code>/etc/lvm/backup/</code>、<code>/etc/lvm/archive/</code> 里的文本备份，必要时可用 <code>vgcfgrestore</code> 回滚。这也是 LVM 比裸分区"更好救"的原因之一。</p>
<pre><code>pvs                 # PV 清单
vgs                 # VG 清单（重点看 VFree：还剩多少可分配）
lvs -o +devices     # LV 清单，+devices 可看到它落在哪些 PV 上
pvdisplay -m        # 展开 PE → LE 的映射关系
</code></pre>
<h3>两者关系</h3>
<p>传统分区 <code>/dev/sdb1</code> 同时干了两件事：</p>
<ol>
<li><strong>划分物理边界</strong>：把一块物理磁盘切成一段一段的存储区域</li>
<li><strong>作为文件系统的载体</strong>：<code>mkfs /dev/sdb1</code>，文件系统直接建在分区上</li>
</ol>
<p>而 LVM 把这两个角色<strong>拆开</strong>了：</p>
<table>
<thead>
<tr>
<th>角色</th>
<th>传统</th>
<th>LVM</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>划分物理边界</strong></td>
<td>分区（fdisk）</td>
<td><strong>PV</strong>（pvcreate，切成 PE）</td>
</tr>
<tr>
<td><strong>池化/合并空间</strong></td>
<td>不存在</td>
<td><strong>VG</strong>（vgextend 加入新 PV）</td>
</tr>
<tr>
<td><strong>作为文件系统载体</strong></td>
<td>分区（sdb1）</td>
<td><strong>LV</strong>（lvcreate）</td>
</tr>
<tr>
<td><strong>容量调整粒度</strong></td>
<td>整块磁盘的空闲区域</td>
<td>VG 中任意可用 PE</td>
</tr>
</tbody>
</table>
<blockquote>
<p>注：<strong>VG</strong> ≈ 传统里没有的中间池化层（把多个物理范围合并成一个可分配空间）。</p>
</blockquote>
<h3>两者区别</h3>
<h4>分区大小</h4>
<p><strong>LV 的物理边界不是固定的</strong>，而传统分区大小固定：</p>
<ul>
<li>传统 <code>/dev/sdb1</code> 建好后，物理位置和大小就钉死了。</li>
<li>LVM 的 <code>/dev/vg_data/lv_data</code> 逻辑上 50G，但底层可能横跨 <code>/dev/sda3</code> 和 <code>/dev/sdb</code> 两块盘，扩容后可以变成 150G。</li>
</ul>
<p>也就是说，<strong>LV 只保留"文件系统载体"这个角色，丢掉了"固定物理边界"这个角色</strong>——物理边界被 PV/VG 接管了。这正是 LVM 灵活性的来源。</p>
<h4>在线扩容</h4>
<p>传统分区必须挂载新目录或调整分区，LVM 可通过 LV 实现逻辑上的扩展：</p>
<ul>
<li><strong>PV 层面</strong>：<code>pvcreate /dev/sdb</code> 只是初始化磁盘元数据，不涉及已挂载的磁盘。</li>
<li><strong>VG 层面</strong>：<code>vgextend vg_data /dev/sdb</code> 只是在卷组里新增一个物理存储池成员，不影响现有 LV 的数据分布。</li>
<li><strong>LV 层面</strong>：<code>lvextend -l +100%FREE /dev/vg_data/lv_data</code> 是逻辑上的扩展，内核支持动态调整映射表。</li>
<li><strong>文件系统层面</strong>：<code>xfs_growfs /data</code> 或 <code>resize2fs</code> 专门设计为<strong>在线扩展</strong>，底层文件系统会动态分配新空间到已有目录中。</li>
</ul>
<p>整个过程业务进程无需重启，正在读写的文件不受影响（因为扩展的是空闲区域，不移动已有数据）。</p>
<h4>扩容能力的边界</h4>
<table>
<thead>
<tr>
<th>场景</th>
<th>传统分区</th>
<th>LVM</th>
</tr>
</thead>
<tbody>
<tr>
<td>同一块盘有<strong>紧邻</strong>空闲空间时扩容</td>
<td>可以（改分区表 + 重读 + resize2fs）</td>
<td>可以</td>
</tr>
<tr>
<td>跨磁盘扩大同一个目录</td>
<td><strong>不可能</strong>，分区不能跨盘</td>
<td>可以（vgextend + lvextend）</td>
</tr>
<tr>
<td>在线扩容（不 umount、不停业务）</td>
<td>受盘内空间与内核重读分区表限制</td>
<td>可以</td>
</tr>
<tr>
<td>缩容</td>
<td>ext4 需 umount + e2fsck；XFS 完全不支持</td>
<td>同样受限，<code>lvreduce</code> 必须离线且风险极高</td>
</tr>
<tr>
<td>秒级快照 / 克隆</td>
<td>不支持</td>
<td><code>lvcreate -s</code> 支持</td>
</tr>
</tbody>
</table>
<blockquote>
<p>结论：LVM 换来灵活性，代价是多一层抽象（多一份要维护的元数据、多一处可能敲错的地方）。缩容不是它的强项，<strong>扩容才是它的主场</strong>。</p>
</blockquote>
<h2>扩容流程</h2>
<p>扩容要先分清诉求，两种诉求的代价完全不同：</p>
<ul>
<li><strong>A. 新增挂载点</strong>：磁盘满了，加一块盘，把新盘挂到新目录（如 <code>/data2</code>）——不需要停机。</li>
<li><strong>B. 扩大已有目录</strong>：磁盘满了，但业务路径不能改，必须让 <code>/data</code> 本身变大——这才是考验底层方案的地方。</li>
</ul>
<h3>新增磁盘（传统 &amp; LVM）</h3>
<p><strong>场景1</strong>：为新磁盘新建分区（或 LV）并挂载到<strong>新目录</strong>，即上面的诉求 A。</p>
<p><strong>传统流程</strong>：OS 层执行 rescan → 确认 lsblk → fdisk 分区 → mkfs 格式化 → mount 挂载 → df 验证→ fstab 永久挂载 → mount -a 验证</p>
<pre><code># OS层发现新设备
echo "- - -" &gt; /sys/class/scsi_host/host0/scan
lsblk                        # 确认新容量已识别
fdisk /dev/sdb               # 分区
mkfs.xfs /dev/sdb1           # 格式化
mount /dev/sdb1 /mnt/data    # 挂载
df -h                        # 验证
# 永久挂载：获取UUID → 写入 /etc/fstab
blkid /dev/sdb1				 # 获取UUID
blkid /dev/sdb1 | grep -o ' UUID="[^"]*' | cut -d'"' -f2	# 获取纯UUID字段
vim /etc/fstab				 # vim 手动加一行或用 echo 追加
echo 'UUID=&lt;UUID&gt; /mnt/data xfs defaults 0 0' &gt;&gt; /etc/fstab
mount -a -v					 # 用 mount -a 测试 fstab 是否正确
</code></pre>
<p><strong>LVM 流程</strong>：OS 层执行 rescan → 确认 lsblk → PV/VG/LV 操作 → mkfs → mount → df 验证 → fstab 永久挂载 → mount -a 验证</p>
<pre><code>echo "- - -" &gt; /sys/class/scsi_host/host0/scan
lsblk
pvcreate /dev/sdb                    # 相当于分区中的初始化物理边界
vgcreate vg_data /dev/sdb            # 建存储池（传统没有这层）
lvcreate -L 50G -n lv_data vg_data   # 相当于"切出分区"
mkfs.xfs /dev/vg_data/lv_data        # 格式化
mount /dev/vg_data/lv_data /mnt/data # 挂载
df -h
blkid /dev/sdb1
vim /etc/fstab
mount -a -v
</code></pre>
<p><strong>场景2</strong>：为新磁盘新建分区（或 LV）并挂载到<strong>已有目录</strong>，即上面的诉求 B。</p>
<p><strong>流程</strong>：停业务 → 搬运现有业务数据 → 识别新容量 → umount → 其余流程</p>
<p><strong>为什么该流程要停止业务/不支持在线操作？</strong></p>
<p><strong>"新盘 + 新挂载点"这套流程本身不需要停机</strong>。<code>mkfs</code> 和 <code>mount</code> 只作用于新的块设备与新的空目录，完全不碰正在服务的文件系统。真正要停机的，是「<strong>想把新盘并进已有目录</strong>」（诉求 B）——传统分区方案做不到，只能停机搬数据。</p>
<p>拆开看，原因有三层：</p>
<ol>
<li><strong>分区是固定地址，且不能跨盘</strong>：<code>/dev/sda1</code> 的边界写死在分区表里，<code>/dev/sdb1</code> 是另一段独立地址。"把 sdb 的空间追加给 sda1 上的文件系统"在传统分区里根本不存在对应操作。</li>
<li><strong>同一块盘内扩容也有限制</strong>：只有当盘上存在<strong>紧邻分区尾部</strong>的空闲空间时才能扩（<code>growpart</code> / <code>parted resizepart</code> → <code>resize2fs</code>）；改完分区表还要让内核重读（<code>partprobe</code> / <code>partx -u</code>），而分区正被挂载占用时，旧内核会直接拒绝重读，只能 umount。</li>
<li><strong>做不到就只能搬数据</strong>：换新盘意味着 <code>rsync</code> 全量搬运 → umount → 重新分区、<code>mkfs</code> → 改 <code>/etc/fstab</code> → 重新 mount。停机时间和数据量成正比，且搬运期间的新写入无法保证一致，业务必须停。</li>
</ol>
<p>另外，<code>mkfs</code> 这个动作<strong>永远不可能在线做</strong>：它是"在块设备上从零构建文件系统元数据"的<strong>破坏性</strong>操作，等于把原有超级块、inode 表、位图全部重建。这和"扩容文件系统（在原有结构上变大、保留数据）"是两件完全不同的事。</p>
<h3>LVM 在线扩容</h3>
<p><strong>生产环境中的最佳实践</strong>。不动现有磁盘/分区，而是把新磁盘加入卷组，再扩展 LV，让文件系统直接使用新空间。</p>
<p><strong>流程</strong>：OS 层执行 rescan → 确认 lsblk → PV/VG/LV 操作 → xfs_growfs/resize2fs → df 验证</p>
<pre><code>echo "- - -" &gt; /sys/class/scsi_host/host0/scan
lsblk
pvcreate /dev/sdb                             # 新盘纳入 LVM
vgextend vg_data /dev/sdb                     # 加入卷组，VG 变大
lvextend -l +100%FREE /dev/vg_data/lv_data    # LV 变大（也可以 -L +50G 指定增量）
# 扩容文件系统，通知文件系统用满新空间
xfs_growfs /data                 # XFS：接挂载点
resize2fs /dev/vg_data/lv_data   # EXT4：接设备
# 检查
df -h
</code></pre>
<p>:::tip
想少敲一条命令，可以在扩 LV 时直接加 <code>-r</code>（等价于 <code>--resizefs</code>）：<code>lvextend -r -l +100%FREE /dev/vg_data/lv_data</code>，LVM 会顺带调用文件系统的扩容工具。
:::</p>
<p><strong>为什么扩容流程中没有格式化/挂载操作？</strong></p>
<ul>
<li><strong>格式化（mkfs）<strong>是对整个块设备"从无到有"<strong>创建文件系统</strong>的操作，只能做一次；而扩容是</strong>扩展文件系统</strong>（在原有基础上变大、保留数据），走的是另一条代码路径。</li>
<li><strong>挂载（mount）<strong>只在</strong>全新磁盘 / 全新 LV 第一次投入使用</strong>时做一次，作用是把文件系统接入目录树；扩容是对<strong>已挂载</strong>的对象操作，属于两个不同阶段。</li>
<li>扩容其实是"拆成两次扩"：<code>lvextend</code> 让<strong>块设备</strong>变大（改 dm 映射表），<code>xfs_growfs</code> / <code>resize2fs</code> 让<strong>文件系统</strong>用满新空间。少做后一步，<code>lsblk</code> 已经变大而 <code>df -h</code> 毫无变化。</li>
</ul>
<h3>常见坑</h3>
<ol>
<li><strong>只扩了 LV，忘了扩文件系统</strong>：补一条 <code>xfs_growfs /data</code>（XFS）或 <code>resize2fs /dev/vg_data/lv_data</code>（EXT4）即可，对已挂载的文件系统是安全的（XFS 只支持扩、不支持缩）。</li>
<li><strong>参数搞反</strong>：<code>xfs_growfs</code> 接<strong>挂载点</strong>，<code>resize2fs</code> 接<strong>设备</strong>；<code>lvextend</code> 接 LV 路径，<code>vgextend</code> 接 <strong>VG 名 + 新 PV</strong>。</li>
<li><strong>对已有数据的盘执行 <code>pvcreate</code></strong>：会覆盖磁盘头部（含分区表），旧数据事实不可见。新盘整盘使用没问题；复用旧盘前务必确认盘上没有要保留的数据。</li>
<li><strong>整盘做 PV 还是分区做 PV</strong>：<code>pvcreate /dev/sdb</code> 最省事；若这块盘还要分给别的用途、或需要保留分区表结构，就先建一个类型为 <code>8e</code>（Linux LVM）的分区，再 <code>pvcreate /dev/sdb1</code>。</li>
<li><strong>虚拟机扩完盘系统没识别</strong>：SCSI 用 <code>echo "- - -" &gt; /sys/class/scsi_host/host0/scan</code>；NVMe 用 <code>echo 1 &gt; /sys/class/nvme/nvme0/rescan_controller</code>；只改了分区表则用 <code>partprobe /dev/sdb</code> 或 <code>partx -u /dev/sdb</code>。</li>
<li><strong>缩容是另一回事</strong>：ext4 缩容要先 <code>umount</code> + <code>e2fsck -f</code> + <code>resize2fs</code>，顺序错一步就丢数据；XFS 根本不支持缩容。生产上想"腾出空间"，通常是新建 LV、迁移数据、再删旧 LV。</li>
<li><strong>变更前留后路</strong>：动 LV 之前可以先 <code>lvcreate -s</code> 打个快照，或确认有可用备份，扩容失败时可回滚。</li>
</ol>
<h2>小结</h2>
<table>
<thead>
<tr>
<th>问题</th>
<th>一句话答案</th>
</tr>
</thead>
<tbody>
<tr>
<td>分区和文件系统有什么区别？</td>
<td>分区是「地址范围」（写在分区表里），文件系统是「数据的组织方式」（写在分区内部），扩容要分别扩两次。</td>
</tr>
<tr>
<td>LVM 比传统分区多了什么？</td>
<td>多了 <strong>VG 池化层</strong>和一张可动态修改的<strong>映射表</strong>，所以空间能跨盘拼、容量能在线追加。</td>
</tr>
<tr>
<td>为什么还要执行 <code>xfs_growfs</code> / <code>resize2fs</code>？</td>
<td><code>lvextend</code> 只改块设备大小，文件系统必须被显式通知才能用满新空间。</td>
</tr>
<tr>
<td>为什么扩容不需要 mkfs / mount？</td>
<td>格式化是"从无到有创建"，扩容是"在原有基础上变大"；挂载只在对象第一次投入使用时做一次。</td>
</tr>
<tr>
<td>传统方案什么时候必须停机？</td>
<td>当诉求是"扩大已有目录"，且可用空间不在同一块盘的紧邻位置时——只能停机搬数据，这正是 LVM 存在的理由。</td>
</tr>
</tbody>
</table>
]]></content>
    <author><name>L7WD3-Xiao</name></author>
    <category term="Linux"/>
  </entry>
  <entry>
    <title>Kubernetes 八股</title>
    <link href="https://blog-l7wd3.pages.dev/posts/interview/k8s/" rel="alternate" type="text/html"/>
    <id>https://blog-l7wd3.pages.dev/posts/interview/k8s/</id>
    <published>2026-08-21T00:00:00.000Z</published>
    <updated>2026-08-21T00:00:00.000Z</updated>
    <summary>速记版 Kubernetes 八股</summary>
    <content type="html"><![CDATA[<h2>架构与组件</h2>
<p><strong>Kubernetes 核心组件有哪些？</strong></p>
<ul>
<li><strong>Master（控制平面）</strong>：kube-apiserver（API 入口）、etcd（配置存储）、kube-scheduler（调度）、kube-controller-manager（控制器）、cloud-controller-manager（云厂商控制器，可选）。</li>
<li><strong>Node（工作节点）</strong>：kubelet（Pod 生命周期）、kube-proxy（网络代理/负载均衡）、containerd（容器运行时）。</li>
</ul>
<p><strong>kube-apiserver 的作用？</strong>
集群统一 API 入口，所有组件通过它通信。它是唯一直接读写 etcd 的组件，负责认证（Authentication）、鉴权（Authorization）、准入控制（Admission）、校验与持久化，并提供 Watch 机制供其他组件监听资源变化。</p>
<p><strong>etcd 的作用？</strong>
分布式 KV 存储，保存集群所有配置和状态数据。基于 Raft 协议保证强一致性，因此生产环境推荐 3/5 个奇数节点做高可用；它是集群的<strong>唯一真实数据源</strong>，备份 etcd 就等于备份整个集群。</p>
<p><strong>kube-scheduler 如何调度 Pod？</strong>
过滤（Filtering）→ 打分（Scoring）→ 绑定（Binding）。过滤阶段剔除不满足条件的节点（资源不足、污点不容忍、端口冲突、亲和性不符）；打分阶段按优先级函数（资源均衡、亲和性权重等）排序选出最优节点；最后把结果写入 Pod 的 <code>nodeName</code>（Binding）。调度考虑资源、亲和性、污点容忍、拓扑分布等。</p>
<p><strong>kube-controller-manager 的作用？</strong>
运行一系列控制循环（Reconcile Loop），让实际状态不断逼近期望状态。常见控制器：Deployment、ReplicaSet、StatefulSet、DaemonSet、Node、Job、EndpointSlice、ServiceAccount 等。多个控制器共享同一进程以减少复杂度。</p>
<p><strong>kubelet 的作用？</strong>
每个节点上的代理，负责 Pod 生命周期管理（创建、监控、销毁）。它监听 API Server 上属于自己的 Pod，通过 CRI 调用容器运行时拉起容器，并上报节点与 Pod 状态；<strong>它不管理非本节点创建的静态 Pod 之外的容器</strong>。</p>
<p><strong>kube-proxy 的 iptables 和 IPVS 模式区别？</strong></p>
<ul>
<li>iptables：基于规则链线性匹配，规则数量随 Service 数量增长而膨胀，适合小规模集群；随机负载均衡。</li>
<li>IPVS：基于哈希表，性能高、支持 rr/wrr/lc/sh 等多种负载均衡算法与会话保持，适合大规模集群。</li>
<li>两者都只做<strong>四层转发</strong>，都从 EndpointSlice 获取后端地址。</li>
</ul>
<p><strong>一个 Pod 从创建到运行的完整流程？</strong></p>
<ol>
<li>kubectl 提交 YAML → apiserver 认证、鉴权、准入（Mutating/Validating）→ 写入 etcd。</li>
<li>controller-manager 中的 Deployment/ReplicaSet 控制器发现后创建对应的 Pod 对象。</li>
<li>scheduler 监听到未绑定的 Pod，筛选节点后写入 <code>nodeName</code>。</li>
<li>目标节点的 kubelet watch 到该 Pod，调用 CRI 拉镜像、先跑 Init 容器、再按顺序启动业务容器；调用 CNI 分配 IP、挂载 Volume。</li>
<li>kubelet 上报状态，EndpointSlice 控制器把就绪 Pod 写入 Service 后端，CoreDNS 提供域名解析。</li>
</ol>
<h2>Pod</h2>
<p><strong>Pod 是什么？</strong>
Kubernetes 最小调度单元，一组容器的集合，共享网络（同一 Network Namespace 与 IP）和存储（Volume），可共享 UTS/IPC。每个 Pod 都有一个 <code>pause</code> 容器（Infra 容器）持有网络命名空间，业务容器加入其中。</p>
<p><strong>为什么 Pod 是 K8s 调度的最小单元？</strong>
调度、网络、存储和资源配额都以 Pod 为粒度：</p>
<ol>
<li><strong>网络、存储和资源配额</strong>：容器只是进程的封装，如果容器需要<strong>共享 Network/UTS/IPC 命名空间与 Volume</strong>，必须被调度到同一节点，单独调度无法保证共置。</li>
<li><strong>调度</strong>：资源 requests/limits、QoS 等级、亲和性与污点容忍都定义在 Pod 级别（容器级别只是求和）。</li>
<li><strong>生命周期</strong>：Pod 是原子生命周期单位，副本数、滚动更新、扩缩容的计数对象都是 Pod。</li>
</ol>
<p><strong>Pod 的生命周期状态有哪些？</strong></p>
<ul>
<li>Pending（等待调度/拉镜像）</li>
<li>Running（至少一个容器运行中）</li>
<li>Succeeded（全部容器成功退出且不会重启）</li>
<li>Failed（至少一个容器异常退出且不会重启）</li>
<li>Unknown（与节点失联）</li>
<li>容器自身的状态则为 Waiting、Running、Terminated。</li>
</ul>
<p><strong>Init 容器的作用？</strong>
在主容器启动前串行执行、必须成功退出，常用于等待依赖服务就绪、初始化配置、修改数据卷权限；与普通容器不同的还有：不支持探针、不计入 QoS 计算。</p>
<p><strong>Pod 与 Deployment 的区别？</strong>
Pod 是单个调度单元，生命周期短暂，重启/迁移后 IP 变化；Deployment 通过 ReplicaSet 管理 Pod 副本，提供声明式副本数、滚动更新和回滚。</p>
<p><strong>Pod 健康检查的探针有哪些？</strong></p>
<p>三种探针：</p>
<ul>
<li>Liveness Probe（存活探针，失败则按 restartPolicy 重启容器）</li>
<li>Readiness Probe（就绪探针，失败则从 Service 后端摘除、不转发流量）</li>
<li>Startup Probe（启动探针，成功前抑制其他探针，适合慢启动应用）</li>
</ul>
<p>实现方式：<code>exec</code>（执行命令）、<code>httpGet</code>、<code>tcpSocket</code>、<code>grpc</code>。参数有 initialDelaySeconds、periodSeconds、timeoutSeconds、failureThreshold。</p>
<p><strong>讲一下Pod重启，restartPolicy 有哪些？</strong></p>
<ul>
<li>重启是重启容器而不是重建 Pod，所以 Pod IP 不变，但重启间隔会指数退避（10s→20s→40s…上限 5min），这就是 CrashLoopBackOff。</li>
<li>重启策略：Always（默认，Deployment 用）、OnFailure、Never（Job 常用）。</li>
</ul>
<p><strong>Pod 处于 CrashLoopBackOff / ImagePullBackOff 怎么办？</strong></p>
<ul>
<li>CrashLoopBackOff：<code>kubectl logs --previous</code> 看上次崩溃日志，常见原因有配置错误、依赖服务不可用、探针配置过严、OOMKilled（<code>kubectl describe pod</code> 看 Last State）。</li>
<li>ImagePullBackOff：镜像名/标签错误、私有仓库缺少 <code>imagePullSecrets</code>、节点网络不通。</li>
<li>Pending：资源不足、没有可调度节点、PVC 未绑定。</li>
<li>Terminating 卡住：finalizer 未清理或节点失联。</li>
</ul>
<p><strong>Pod 的 QoS 等级？</strong></p>
<ul>
<li>Guaranteed（所有容器都设了 requests == limits）</li>
<li>Burstable（设了部分 requests，不满足 Guaranteed）</li>
<li>BestEffort（完全没设）</li>
<li>节点内存不足时按 BestEffort → Burstable → Guaranteed 的顺序驱逐</li>
</ul>
<h2>Service 与网络</h2>
<p><strong>Service 的类型有哪些？</strong></p>
<ul>
<li>ClusterIP（默认，仅集群内访问，虚拟 IP）；</li>
<li>NodePort（每个节点开放 30000-32767 端口映射）；</li>
<li>LoadBalancer（调用云厂商 LB，通常是 NodePort 的超集）；</li>
<li>ExternalName（CNAME/DNS 别名，不转发流量）；</li>
<li>另外还有无头服务 Headless Service（<code>clusterIP: None</code>，直接返回 Pod IP，供 StatefulSet 使用）。</li>
</ul>
<p><strong>Service 的实现原理？</strong>
Service 只是一组 iptables/IPVS 规则的抽象：apiserver 分配 ClusterIP 后，kube-proxy 监听 Service 与 EndpointSlice 变化并下发规则，把访问 ClusterIP 的流量 DNAT 到某个就绪 Pod 的 IP:Port；CoreDNS 把 <code>svc.namespace.svc.cluster.local</code> 解析为 ClusterIP。三层结构 = DNS 名称 → ClusterIP → Pod IP。</p>
<p><strong>Ingress 是什么？与 Service 的关系？</strong>
Ingress 提供集群外部到 Service 的 HTTP/HTTPS 路由规则集合（基于 Host/Path 转发、TLS 终止），本身只是一份配置，必须由 Ingress Controller（Nginx Ingress、Traefik、Higress 等）解析并生效。流量路径为：客户端 → Ingress Controller → Service → Pod。Ingress 是七层，Service 是四层。</p>
<p><strong>CNI 是什么？常见实现？</strong>
Container Network Interface，容器网络插件标准（负责给 Pod 分配 IP、配置路由与网卡）。常见：Flannel（Overlay 网络，VXLAN/host-gw，简单但无 NetworkPolicy）、Calico（BGP 三层网络或 IPIP，性能好且支持 NetworkPolicy）、Cilium（eBPF，性能和可观测性更强）。</p>
<p><strong>K8s 如何实现负载均衡？</strong>
分层次实现：</p>
<ol>
<li><strong>四层（Service）</strong>：kube-proxy 通过 iptables/IPVS 把 ClusterIP 的流量随机/轮询转发到后端 Pod，可用 <code>sessionAffinity: ClientIP</code> 做会话保持。</li>
<li><strong>七层（Ingress / Gateway API）</strong>：按域名、路径、Header、权重做路由。</li>
<li><strong>外部入口</strong>：LoadBalancer 类型 Service 或云厂商 SLB/Nginx 做最外层负载均衡。</li>
<li><strong>客户端侧</strong>：无头 Service + 客户端自行负载均衡（gRPC 常用）。</li>
</ol>
<p><strong>Pod 之间如何通信？跨节点呢？</strong></p>
<ul>
<li>同一 Pod 内通过 localhost；</li>
<li>同节点不同 Pod 通过 CNI 创建的 veth/bridge 通信；</li>
<li>跨节点由 CNI 路由（Flannel VXLAN 封装、Calico BGP 路由）完成，要求满足 K8s 网络模型：每个 Pod 一个 IP，Pod 间可直接通信无需 NAT。</li>
</ul>
<p><strong>Service 访问不通怎么排查？</strong></p>
<ol>
<li><code>kubectl get endpointslices</code> 确认后端是否有就绪 Pod（没有则检查探针/标签选择器）。</li>
<li><code>kubectl exec</code> 进 Pod 测 <code>nslookup</code> 与 <code>curl ClusterIP</code>，定位是 DNS 还是转发问题。</li>
<li>检查 <code>targetPort</code> 与容器实际监听端口是否一致。</li>
<li>检查 NetworkPolicy 是否拦截流量。</li>
</ol>
<h2>存储</h2>
<p><strong>PV、PVC、StorageClass 的关系？</strong></p>
<ul>
<li>PV（PersistentVolume）：集群级存储资源，由管理员静态创建或由 provisioner 动态创建。</li>
<li>PVC（PersistentVolumeClaim）：用户存储申请（容量、accessMode、storageClassName），绑定到某个 PV。</li>
<li>StorageClass：动态存储供给配置，声明 provisioner 与参数（如 <code>reclaimPolicy</code>、磁盘类型），PVC 引用它即可自动创建 PV。</li>
</ul>
<p><strong>访问模式 accessMode 有哪些？</strong></p>
<ul>
<li>ReadWriteOnce（RWO，单节点读写，最常见）</li>
<li>ReadOnlyMany（ROX）</li>
<li>ReadWriteMany（RWX，需 NFS/CephFS 等支持）</li>
<li>ReadWriteOncePod</li>
<li>回收策略有 Retain / Delete / Recycle</li>
</ul>
<p><strong>PVC 处于 Pending 状态的可能原因？</strong>
没有匹配的 PV（容量 / accessMode / storageClassName / 标签选择器不匹配）；StorageClass 不存在或 provisioner 故障；WaitForFirstConsumer 模式下还没有 Pod 使用它。</p>
<p><strong>如何配置持久化存储？</strong>
三步：创建 StorageClass（或管理员静态建 PV）→ 创建 PVC 申请 → Pod 通过 <code>volumes</code> 引用 PVC。</p>
<pre><code>apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast
provisioner: kubernetes.io/aws-ebs   # 或 csi 驱动
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: data
spec:
  storageClassName: fast
  accessModes: ["ReadWriteOnce"]
  resources:
    requests:
      storage: 10Gi
---
apiVersion: v1
kind: Pod
metadata:
  name: app
spec:
  containers:
    - name: app
      image: nginx
      volumeMounts:
        - name: data
          mountPath: /data
  volumes:
    - name: data
      persistentVolumeClaim:
        claimName: data
</code></pre>
<p><strong>emptyDir、hostPath 与 PVC 的区别？</strong>
emptyDir 生命周期与 Pod 相同（Pod 删除即清空），适合缓存与容器间共享；hostPath 挂载宿主机目录，Pod 漂移后数据仍在但强绑定节点，仅适合特殊场景（日志采集）；PVC 才是真正的持久化、与 Pod 解耦的存储。</p>
<h2>配置与安全</h2>
<p><strong>ConfigMap 和 Secret 的区别？</strong>
ConfigMap 存明文非敏感配置；Secret 存敏感信息，值以 <strong>base64 编码</strong>存储（并非加密），可限制为 <code>type: Opaque</code> 或 dockerconfigjson 等。使用时都可以通过环境变量或 Volume 挂载注入。
<strong>注意：base64 是可逆编码不是加密</strong>，要真正安全需开启 etcd 静态加密、RBAC 限制读取、或使用外部密钥管理（Vault/KMS）。</p>
<p><strong>更新 ConfigMap/Secret 后 Pod 会立即生效吗？</strong>
环境变量方式注入的<strong>不会</strong>生效，必须重建 Pod（Deployment 可用 <code>kubectl rollout restart</code>）；以 Volume 挂载方式的最终会更新（kubelet 周期同步，约 1 分钟），但应用需自行 reload。</p>
<p><strong>RBAC 中 Role 和 ClusterRole 的区别？</strong>
Role 作用于单个 Namespace；ClusterRole 作用于整个集群（或用于跨 Namespace 复用与集群级资源如 Node、PV）。通过 RoleBinding 绑定 Role/ClusterRole（限定在某 Namespace），通过 ClusterRoleBinding 绑定 ClusterRole（全集群生效）。RBAC 是<strong>纯允许模型</strong>，没有拒绝规则。</p>
<p><strong>ServiceAccount 的作用？</strong>
Pod 访问 API Server 时的身份标识，未指定则使用 default。Token 会自动挂载到 <code>/var/run/secrets/kubernetes.io/serviceaccount</code>，配合 RoleBinding 即可实现 Pod 级别最小权限。</p>
<p><strong>Pod 安全限制有哪些手段？</strong>
SecurityContext（runAsUser、readOnlyRootFilesystem、capabilities、privileged）、Pod Security Admission（baseline/restricted）、NetworkPolicy（网络隔离）、ResourceQuota/LimitRange（资源约束）。</p>
<h2>高级资源</h2>
<p><strong>StatefulSet 和 Deployment 的区别？</strong></p>
<ul>
<li>Deployment 用于无状态应用，Pod 完全等价、可随意替换；</li>
<li>StatefulSet 用于有状态应用，常用于数据库、消息队列、ZooKeeper 等，提供：
<ul>
<li>稳定的网络标识（<code>pod-name-0.svc</code> 依赖 Headless Service）</li>
<li>稳定的独立存储（volumeClaimTemplates）</li>
<li>有序的创建/删除/滚动（0→N创建，逆序删除）</li>
</ul>
</li>
</ul>
<p><strong>DaemonSet 的典型应用场景？</strong>
每个（或指定的）节点运行一个 Pod，如日志收集（Fluentd/Filebeat）、节点监控（Node Exporter）、CNI 组件、kube-proxy。新节点加入时会自动部署。</p>
<p><strong>HPA 是什么？</strong>
Horizontal Pod Autoscaler，基于 CPU/内存或自定义指标自动扩缩容 Pod 副本数。默认每 15s 采集一次指标（依赖 Metrics Server），按 <code>期望副本数 = 当前副本数 × 当前指标 / 目标指标</code> 计算并受 tolerance 与扩缩容冷却时间限制。配套还有 VPA（垂直调资源）与 Cluster Autoscaler（扩节点）。</p>
<p><strong>Job 和 CronJob 的区别？</strong>
Job 运行一次性任务直到成功（可设 completions/parallelism、backoffLimit）；CronJob 按 cron 表达式周期性创建 Job，支持 concurrencyPolicy 与历史保留数。</p>
<p><strong>如何限制 Pod 资源使用？</strong>
在 Pod 的 <code>resources</code> 字段定义 <code>requests</code>（调度依据，保证下限）和 <code>limits</code>（运行时上限，超出内存会被 OOMKilled、超出 CPU 会被限流）。命名空间级别可用 LimitRange 设默认值与上下限，用 ResourceQuota 限制总量。</p>
<p><strong>节点亲和性与污点容忍的区别？</strong></p>
<ul>
<li>亲和性是 Pod 主动选择节点（吸引，支持 requiredDuringScheduling 硬性 与 preferredDuringScheduling 软性、nodeAffinity/podAffinity/podAntiAffinity）；</li>
<li>污点容忍是 Pod 被动适应节点（排斥），节点打污点排斥 Pod，Pod 加容忍才能调度上去。污点效果有 NoSchedule、PreferNoSchedule、NoExecute（NoExecute 还会驱逐不容忍的存量 Pod）。</li>
</ul>
<h2>包管理</h2>
<h3>Helm</h3>
<p><strong>Helm 是什么？</strong>
Kubernetes 的包管理器，把一组 YAML 抽象成可参数化、可版本化、可复用的 Chart。三个核心概念：Chart（模板包）、Release（Chart 的一次部署实例）、Repository（Chart 仓库）。</p>
<p><strong>常用命令？</strong></p>
<pre><code>helm repo add bitnami https://charts.bitnami.com/bitnami
helm search repo nginx
helm install my-nginx bitnami/nginx -n web --create-namespace -f values.yaml
helm upgrade my-nginx bitnami/nginx --set replicaCount=3
helm rollback my-nginx 1        # 回滚到指定 revision
helm history my-nginx
helm uninstall my-nginx -n web
helm template ./chart           # 本地渲染，不部署
</code></pre>
<p><strong>Helm 的目录结构？</strong>
<code>Chart.yaml</code>（元数据）、<code>values.yaml</code>（默认值）、<code>templates/</code>（Go Template 模板 YAML）、<code>charts/</code>（子 Chart 依赖）。升级/回滚依赖 release 的 revision 记录（存在 Secret 中）。</p>
<h3>kustomize</h3>
<p><strong>kustomize 是什么？</strong>
K8s 原生命令（<code>kubectl apply -k</code>）的配置定制工具。<strong>不用模板</strong>，而是通过 base + overlay 的叠加（patch）方式复用同一份基础 YAML，适配不同环境。</p>
<p><strong>典型目录结构？</strong></p>
<pre><code>base/
  kustomization.yaml     # resources: [deployment.yaml, service.yaml]
  deployment.yaml
overlays/
  dev/kustomization.yaml # resources: [../../base]; replicas/patch 覆盖
  prod/kustomization.yaml
</code></pre>
<p><strong>kustomize 常用能力？</strong>
<code>resources</code> 引入基础资源、<code>patchesStrategicMerge</code>/<code>patches</code> 覆盖字段、<code>replicas</code> 改副本数、<code>images</code> 改镜像 tag、<code>namePrefix</code>/<code>commonLabels</code> 统一前缀与标签、<code>configMapGenerator</code> 自动生成配置并触发滚动更新。</p>
<p><strong>Helm 与 kustomize 怎么选？</strong>
Helm 适合对外分发、需要参数化与版本管理的场景；kustomize 适合内部多环境、以 patch 为主、不想维护模板语法的场景。两者常组合使用（Helm 渲染 base + kustomize 做 overlay patch）。</p>
<h2>更新与发布</h2>
<p><strong>什么是滚动更新？如何实现滚动更新与回滚？（命令）</strong>
滚动更新（RollingUpdate）是 Deployment 的默认策略：新建一个 ReplicaSet，逐步增加新 Pod 副本、同时减少旧 Pod 副本，过程中始终有可用实例，实现零中断发布。</p>
<pre><code>kubectl set image deploy/app app=nginx:1.25      # 触发更新
kubectl rollout status deploy/app                # 观察进度
kubectl rollout history deploy/app               # 查看历史版本
kubectl rollout undo deploy/app --to-revision=2  # 回滚
kubectl rollout pause/resume deploy/app          # 暂停/继续
</code></pre>
<p><strong>滚动更新涉及哪些组件？如何控制？</strong>
链路是 Deployment → ReplicaSet → Pod：Deployment 控制器发现 Pod 模板变更后创建新的 ReplicaSet 并扩缩两个 RS 的副本数，kubelet 负责实际起停容器。控制参数在 <code>strategy.rollingUpdate</code>：</p>
<ul>
<li><code>maxSurge</code>：更新过程中最多超出期望副本数的 Pod 数（默认 25%）；</li>
<li><code>maxUnavailable</code>：最多不可用的 Pod 数（默认 25%，设为 0 即"先扩后缩"的零中断更新）；</li>
<li><code>minReadySeconds</code>：Pod 就绪后多久才认为可用；</li>
<li><code>progressDeadlineSeconds</code>：超时未完成则标记为失败。
滚动更新不丢请求的前提是配置了 <strong>Readiness 探针</strong>与优雅退出（preStop + terminationGracePeriodSeconds）。</li>
</ul>
<p><strong>什么是 Recreate 策略？</strong>
<code>Recreate</code> 是一种用于更新应用的 Deployment 策略。它通过<strong>先终止所有旧的 Pod，再创建新的 Pod</strong>来完成更新。存在停机时间。适合不能同时运行多个版本的应用（如数据库迁移）。</p>
<p><strong>什么是灰度发布 / 金丝雀发布？如何实现？</strong>
灰度（金丝雀）发布指先让一小部分流量进入新版本，观察指标正常后再逐步放量到 100%。实现方式：</p>
<ol>
<li><strong>副本比例</strong>：新旧两个 Deployment 共用同一个 Service 的标签，靠调节副本数比例分流（最粗糙，比例受 Pod 数限制）。</li>
<li><strong>Ingress 注解</strong>：Nginx Ingress 的 <code>nginx.ingress.kubernetes.io/canary: "true"</code> + <code>canary-weight: 10</code> / canary-by-header 精确切流。</li>
<li><strong>服务网格</strong>：Istio VirtualService 按权重/Header/用户维度切流，粒度最细。</li>
<li><strong>渐进式交付工具</strong>：Argo Rollouts、Flagger，支持自动分析指标并自动回滚。</li>
</ol>
<p><strong>滚动更新、蓝绿、金丝雀的区别？</strong></p>
<ul>
<li>滚动更新：逐个替换实例，资源占用小，回滚快，但新旧版本会短暂共存。</li>
<li>蓝绿发布：同时部署完整的新旧两套环境，切流量即完成发布/回滚，需要双倍资源，回滚最快。</li>
<li>金丝雀发布：按比例放量，风险最小，需要完善的监控与流量控制能力。</li>
</ul>
<h2>其他</h2>
<p><strong>K8s 为何要关闭 Swap？</strong></p>
<ol>
<li><strong>资源确定性被破坏</strong>：K8s 依赖 <strong>cgroups</strong> 严格限制 Pod 的内存上限（limits）。如果开启 Swap，Pod 写数据可能被换出到磁盘，导致 <code>cgroups</code> 统计的内存使用量不准确，容器无法精准判断自己是否达到内存限制。</li>
<li><strong>性能不可控</strong>：Swap 依赖磁盘 I/O，速度比内存慢几个数量级。一旦发生大量换页，Pod 响应延迟会急剧飙升，且这种抖动毫无预兆，破坏了 Kubernetes 承诺的 <strong>服务质量（QoS）</strong>。</li>
<li><strong>调度误判</strong>：Kubelet 根据节点上的内存容量进行 Pod 调度，如果 Swap 被占用，实际可用内存被稀释，容易产生节点资源枯竭但调度器不知情的情况。</li>
</ol>
<p><strong>K8s 节点上只有 containerd 而没有 Docker，如何构建镜像？</strong>
Docker 和 containerd 都是运行时，构建镜像需要单独的构建器：</p>
<ul>
<li><strong>nerdctl + BuildKit</strong>：<code>nerdctl</code> 是兼容 Docker CLI 的 containerd 客户端，<code>nerdctl build</code> 依赖 <code>buildkitd</code>（需单独部署，<code>buildkitd --oci-worker-no-process-sandbox</code>），也支持 <code>nerdctl compose</code>。</li>
<li><strong>buildctl + buildkitd</strong>：直接用 BuildKit 原生客户端，支持多平台构建与缓存导入导出。</li>
<li><strong>在集群内构建</strong>：Kaniko（无需特权，在 Pod 内构建）、Buildpacks、或 Tekton/Argo Workflows 流水线。</li>
<li>生产实践通常是 CI 里用 Docker/BuildKit 构建并推送镜像，节点只负责拉取运行。</li>
</ul>
<p><strong>K8s 与 Docker 的关系？</strong>
Docker 是容器技术的早期实现（CLI + daemon + 运行时 + 镜像格式）；K8s 是容器编排系统。早期 K8s 通过 dockershim 调用 Docker，1.24 起移除 dockershim，直接使用 CRI 对接 containerd/CRI-O，因此“K8s 弃用 Docker”弃用的是 dockershim 这层适配，镜像（OCI 标准）依然通用。</p>
<p><strong>常用排错命令？</strong></p>
<pre><code>kubectl get pod -A -o wide                 # 全量 Pod 与所在节点
kubectl describe pod &lt;pod&gt;                 # 事件、探针、调度失败原因
kubectl logs &lt;pod&gt; -c &lt;container&gt; -f       # 日志（--previous 看上次崩溃）
kubectl exec -it &lt;pod&gt; -- sh
kubectl top node / pod                     # 资源使用（依赖 metrics-server）
kubectl get events --sort-by=.lastTimestamp
kubectl get pod &lt;pod&gt; -o yaml              # 看最终生效的 spec
kubectl auth can-i get pods --as=system:serviceaccount:default:default
</code></pre>
]]></content>
    <author><name>L7WD3-Xiao</name></author>
    <category term="Interview"/>
  </entry>
  <entry>
    <title>Docker 八股</title>
    <link href="https://blog-l7wd3.pages.dev/posts/interview/docker/" rel="alternate" type="text/html"/>
    <id>https://blog-l7wd3.pages.dev/posts/interview/docker/</id>
    <published>2026-08-20T00:00:00.000Z</published>
    <updated>2026-08-20T00:00:00.000Z</updated>
    <summary>速记版 Docker 八股</summary>
    <content type="html"><![CDATA[<h2>基础概念</h2>
<p><strong>Docker 是什么？</strong>
容器化平台，将应用及其依赖打包成轻量级、可移植的容器，实现操作系统级虚拟化。</p>
<p><strong>为什么容器被称为"一次构建，到处运行"？</strong>
镜像把应用和它的所有依赖（库、运行时、配置）一起打包，容器运行时只依赖宿主机内核，因此只要内核兼容，任何装了 Docker 的机器行为都一致，消除了"环境不一致"问题。</p>
<p><strong>Docker 镜像与容器的关系？</strong>
镜像是只读模板（代码+依赖+环境），容器是镜像的运行实例（可读写层）。</p>
<p><strong>Docker 的三大核心是什么？</strong>
镜像（Image）、容器（Container）、仓库（Repository）。</p>
<p><strong>Docker 工作原理是什么？</strong>
Docker 使用客户端-服务器架构。Docker 客户端与 Docker 守护进程通信，后者负责构建、运行和分发 Docker 容器的繁重工作。</p>
<p><strong>Docker 组成部分？</strong></p>
<ol>
<li>docker client / CLI，客户端/命令行工具，为用户提供一系列可执行命令，用户用这些命令实现跟 docker daemon 交互；</li>
<li>docker daemon，守护进程，在宿主主机后台运行，等待接收来自客户端的请求消息；</li>
<li>docker build，用于构建镜像，使用 <strong>BuildKit</strong> 作为构建器；</li>
<li>docker container，容器，一个系统级别的服务，拥有自己的 ip 和系统目录结构。</li>
</ol>
<p><strong>Docker 容器与虚拟机的本质区别？</strong>
虚拟机通过 Hypervisor 虚拟化硬件，每个 VM 运行独立 Guest OS（GB 级、分钟级启动）；Docker 共享宿主机内核，进程级隔离（MB 级、秒级启动）。</p>
<p><strong>容器是一种轻量级虚拟机吗？</strong>
不是。容器本质就是<strong>宿主机上被 namespace 隔离、被 cgroup 限流的普通进程</strong>，与宿主共享同一个内核，所以：</p>
<ul>
<li>优势是启动快、开销小、密度高；</li>
<li>代价是隔离性弱于虚拟机，内核漏洞会影响所有容器，且无法运行不同内核版本（如 Linux 上跑 Windows 容器）。</li>
</ul>
<h2>Dockerfile / 镜像构建</h2>
<p><strong>Dockerfile 中常用指令？</strong></p>
<ul>
<li>FROM（基础镜像）</li>
<li>COPY/ADD（复制文件）</li>
<li>RUN（构建时执行）</li>
<li>CMD（默认命令）</li>
<li>ENTRYPOINT（入口点）</li>
<li>EXPOSE（声明端口，仅文档作用）</li>
<li>WORKDIR（工作目录）</li>
<li>ENV（环境变量）</li>
<li>ARG（构建参数，构建后可丢弃）</li>
<li>USER（运行用户）</li>
<li>VOLUME（声明匿名卷）</li>
<li>LABEL（元数据）</li>
<li>HEALTHCHECK（健康检查）</li>
<li>ONBUILD（被继承时触发）</li>
</ul>
<p><strong>CMD 和 ENTRYPOINT 的区别？</strong></p>
<ul>
<li>CMD 可被 <code>docker run</code> 命令覆盖；</li>
<li>ENTRYPOINT 不可覆盖，适合固定执行程序。</li>
<li>两者可组合使用：ENTRYPOINT 定可执行文件，CMD 提供默认参数。</li>
</ul>
<p><strong>ADD 和 COPY 的区别？</strong></p>
<ul>
<li>COPY 只复制本地文件；</li>
<li>ADD 支持自动解压 tar 和远程 URL 下载。</li>
<li>官方推荐用 COPY（行为更可预期）。</li>
</ul>
<p><strong>RUN、CMD、ENTRYPOINT 的区别？</strong></p>
<ul>
<li>RUN 在<strong>构建时</strong>执行并把结果固化进镜像层；</li>
<li>CMD/ENTRYPOINT 在<strong>容器启动时</strong>执行，写进镜像元数据，只有最后一条生效。</li>
</ul>
<p><strong>RUN 命令 shell 形式和 exec 形式的区别？</strong></p>
<ul>
<li><code>RUN apt-get update</code>（shell）等价于 <code>/bin/sh -c "..."</code>，能使用变量和管道，但会多一层 shell 进程；</li>
<li><code>RUN ["apt-get","update"]</code>（exec）直接执行，<strong>PID 1 就是目标进程</strong>，能正确接收信号。</li>
<li>CMD/ENTRYPOINT <strong>推荐用 exec 形式</strong>，否则 <code>docker stop</code> 的 SIGTERM 会被 sh 吞掉，导致容器等 10 秒后被杀（退出码 137）。</li>
</ul>
<p><strong>ARG 和 ENV 的区别？</strong></p>
<ul>
<li>ARG 只在构建期有效，容器运行时拿不到（可用 <code>--build-arg</code> 传入，不应放敏感信息，<code>docker history</code> 能看到）；</li>
<li>ENV 会写入镜像元数据，构建期和运行期都可见。</li>
</ul>
<p><strong>如何减小 Docker 镜像体积 / 加速镜像构建？</strong></p>
<ul>
<li>.dockerignore 排除非必要文件（<code>.git</code>、<code>node_modules</code>）；</li>
<li>使用轻量级基础镜像（如 alpine/slim、distroless）；</li>
<li>编译型语言采用多阶段构建；</li>
<li>合并 RUN 命令减少层数；</li>
<li>清理 apt/pip/npm 缓存（且必须与安装写在同一层，否则删除只是标记，体积不减）；</li>
<li>把变化频率低的指令放前面，充分利用层缓存；</li>
<li>BuildKit 的 <code>--mount=type=cache</code> 缓存包管理器目录。</li>
</ul>
<p><strong>多阶段构建的好处？</strong>
最终镜像只保留运行时所需文件，不包含编译工具和中间产物，大幅减小镜像体积，同时降低攻击面（工具链不进生产镜像）。</p>
<p><strong>镜像分层存储的好处？</strong>
共享和缓存——多个镜像共享同一基础层只存一份；构建时缓存不变层，加快构建速度。写时容器只在其上叠加一个可写层（Copy-on-Write），多个容器可共享同一个只读镜像层。</p>
<p><strong>镜像构建缓存机制？</strong>
Docker 逐条指令比对缓存，某条指令的<strong>命令文本、父层、以及 COPY/ADD 的文件内容校验和</strong>任一变化，该层及其后续所有层缓存全部失效，需要重新构建。所以：</p>
<ul>
<li>先把 <code>package.json</code> 复制进去装依赖，再复制源码，改动代码时不会重装依赖；</li>
<li><code>--no-cache</code> 强制不用缓存，<code>--cache-from</code> 复用 CI 中拉取的镜像缓存。</li>
</ul>
<p><strong>HEALTHCHECK 的作用？</strong>
在容器内定时执行命令检查应用是否健康（<code>docker ps</code> 显示 healthy/unhealthy），仅状态标记，<strong>Docker 本身不会自动重启 unhealthy 容器</strong>（这正是 K8s 探针要解决的问题）。</p>
<h2>网络与存储</h2>
<p><strong>如何让容器随 Docker 服务自动重启？</strong>
<code>--restart=always</code> 或 <code>--restart=unless-stopped</code>，还有 <code>on-failure[:max-retries]</code>。注意 <code>docker stop</code> 手动停止的容器在 always 下仍会随 daemon 启动。</p>
<p><strong>Docker 数据持久化方式？</strong>
Volume（<code>docker volume create</code>）、Bind Mount（挂载宿主机目录）、tmpfs（内存中）。</p>
<p><strong>Volume 和 Bind Mount 的区别？</strong></p>
<ul>
<li>Volume：由 Docker 管理，存放在 <code>/var/lib/docker/volumes/</code>，跨平台、可备份/迁移、可用 volume driver 接 NFS；适合生产数据。</li>
<li>Bind Mount：直接映射宿主机任意路径，路径依赖宿主机结构，权限与 SELinux 容易出问题；适合开发时挂代码、挂配置文件。</li>
<li>两者用 <code>docker inspect</code> 的 Mounts 字段区分（Type 为 volume 或 bind）。</li>
<li>容器内数据不持久化的场景：不挂载时数据留在容器可写层，<strong>容器删除即丢失</strong>。</li>
</ul>
<p><strong>容器网络隔离方式？</strong>
存在以下网络模式：</p>
<ul>
<li>Bridge 模式：默认模式，容器通过虚拟网桥 docker0 通信，拥有独立的网络命名空间。</li>
<li>Host 模式：容器与宿主机共享网络命名空间，直接使用宿主机的 IP 和端口，性能较高但缺乏隔离。</li>
<li>None 模式：容器没有网络配置，适用于无需网络的场景。</li>
<li>Container 模式：多个容器共享一个网络命名空间，适合需要紧密协作的容器。</li>
<li>Overlay / Macvlan：跨主机通信（Swarm）或让容器直接获得物理网段 IP。</li>
</ul>
<p><strong>容器之间如何互相通信？</strong>
默认 bridge 网络下只能用 IP 互访，且<strong>没有 DNS 解析</strong>。推荐自定义 bridge 网络：<code>docker network create app-net</code>，加入同一网络的容器可直接用<strong>容器名 / <code>--network-alias</code></strong> 作为主机名互访（Docker 内置 DNS 127.0.0.11）。</p>
<p><strong>端口映射的原理？</strong>
<code>-p 8080:80</code> 由 docker-proxy 与 iptables 的 DNAT 规则实现，把宿主机 8080 的流量转发到容器 IP 的 80 端口。<code>-P</code> 随机映射所有 EXPOSE 端口，<code>-p 127.0.0.1:8080:80</code> 只监听本机。</p>
<h2>容器</h2>
<p><strong>如何进入容器？</strong></p>
<pre><code>docker exec -it &lt;容器&gt; /bin/sh        # 推荐：新开一个进程进入
docker attach &lt;容器&gt;                  # 进入主进程的标准输入输出（Ctrl+C 会杀掉容器）
docker exec -it -u root &lt;容器&gt; bash   # 指定用户
</code></pre>
<p><code>exec</code> 会在容器内新起一个进程（需镜像里有 shell，distroless 镜像没有）；调试特权容器还可用 <code>nsenter -t &lt;PID&gt; -a</code> 直接进入其命名空间（宿主机上无需容器内有工具）。</p>
<p><strong>exec 和 attach 的区别？</strong></p>
<ul>
<li>exec 启动一个新的进程，退出不影响主进程；</li>
<li>attach 接管容器 PID 1 的标准流，退出（Ctrl+C）可能终止容器，且多个 attach 会共享输出。</li>
</ul>
<p><strong>如何查看容器日志？</strong></p>
<pre><code>docker logs -f --tail 100 &lt;容器&gt;      # 实时跟踪最近 100 行（-f 实时跟踪）
docker logs --since 10m &lt;容器&gt;        # 最近 10 分钟
docker logs --timestamps &lt;容器&gt;
</code></pre>
<p>日志来自容器主进程的 stdout/stderr，由 json-file 日志驱动落盘到 <code>/var/lib/docker/containers/&lt;id&gt;/*-json.log</code>。<strong>默认无大小限制</strong>，容易把磁盘写满，需配置 <code>--log-opt max-size=10m --log-opt max-file=3</code>（可在 <code>/etc/docker/daemon.json</code> 全局设置）。应用应把日志打到前台而不是文件里，这是容器的最佳实践。</p>
<p><strong>如何查看容器信息与资源占用？</strong></p>
<ul>
<li><code>docker ps -a</code>（状态与端口）</li>
<li><code>docker inspect</code>（完整 JSON，含 IP、挂载、退出码）</li>
<li><code>docker stats</code>（实时 CPU/内存/网络）</li>
<li><code>docker top</code>（容器内进程）</li>
<li><code>docker diff</code>（可写层的文件变更）</li>
</ul>
<p><strong>容器退出码代表什么？</strong>
0 正常退出；1 应用错误；126/127 命令不可执行/不存在；137 = 128+9 被 SIGKILL（OOM 或 <code>docker kill</code>）；143 = 128+15 被 SIGTERM（<code>docker stop</code> 优雅停止）。</p>
<h2>镜像仓库</h2>
<p><strong>如何推送镜像到仓库？</strong>
<code>docker login</code> → <code>docker tag app:1.0 registry.example.com/ns/app:1.0</code>（必须带仓库地址前缀）→ <code>docker push</code>。拉取时同理，私有仓库需先登录或配置凭证。</p>
<p><strong>镜像的命名规则？</strong>
<code>[registry/][namespace/]repository[:tag][@digest]</code>，tag 默认 <code>latest</code>。生产<strong>禁止依赖 latest</strong>，因为它是可变的，不同节点可能拉到不同版本；用不可变 tag（版本号或 commit id）或 digest 保证一致性。</p>
<p><strong>如何减小镜像拉取时间？</strong>
使用更小的基础镜像、减少层数、把 registry 部署在内网、开启镜像加速器、使用 P2P 分发（Dragonfly）或按需拉取（懒加载）。</p>
<h2>Docker Compose</h2>
<p><strong>Docker Compose 是什么？</strong>
单机多容器编排工具，用一个 <code>docker-compose.yml</code> 声明多个服务、网络、卷和依赖关系，一条 <code>docker compose up -d</code> 全部拉起。适合本地开发与小型部署；跨主机的生产编排应使用 Kubernetes/Swarm。</p>
<p><strong>Compose 常用命令？</strong>
<code>up -d</code>（后台启动）、<code>down</code>（停止并删除）、<code>ps</code>、<code>logs -f</code>、<code>exec</code>、<code>build</code>、<code>restart</code>、<code>config</code>（校验并渲染最终配置）。</p>
<p><strong>Compose 中 <code>depends_on</code> 能保证依赖服务就绪吗？</strong>
不能，它只保证<strong>启动顺序</strong>。要等待服务真正可用需配合 healthcheck + <code>condition: service_healthy</code>，或在应用内做重试。</p>
<h2>常用命令</h2>
<pre><code># 镜像
docker build -t app:1.0 .            # 构建
docker images / docker image prune   # 列出 / 清理悬空镜像
docker tag / push / pull             # 打标签与仓库交互

# 容器
docker run -d --name web -p 8080:80 -v data:/usr/share/nginx/html nginx
docker ps -a / start / stop / restart / rm
docker cp file.txt web:/tmp/         # 与容器互拷文件
docker commit web web:snapshot       # 把容器存成镜像（不推荐，应用 Dockerfile）

# 清理
docker system df                     # 查看磁盘占用
docker system prune -a --volumes     # 清理未使用镜像/网络/卷
</code></pre>
<p><strong>docker run 常用参数？</strong>
<code>-d</code> 后台运行、<code>-it</code> 交互式 TTY、<code>--name</code> 命名、<code>-p</code> 端口映射、<code>-v</code> 挂载、<code>-e</code> 环境变量、<code>--network</code> 网络、<code>--restart</code> 重启策略、<code>--rm</code> 退出即删除、<code>--memory/--cpus</code> 资源限制、<code>--privileged</code> 特权、<code>-w</code> 工作目录、<code>--entrypoint</code> 覆盖入口。</p>
<h2>底层原理</h2>
<p><strong>Docker 使用的底层技术？</strong>
Namespaces（隔离：PID、NET、IPC、UTS、MNT、USER）、Cgroups（资源限制：CPU、内存）、UnionFS（分层存储）。</p>
<p><strong>各个 Namespace 隔离了什么？</strong></p>
<ul>
<li>PID：进程号，容器内 PID 1 是应用进程；</li>
<li>NET：网卡、IP、端口、路由表；</li>
<li>IPC：信号量、共享内存；</li>
<li>UTS：主机名与域名；</li>
<li>MNT：挂载点与文件系统视图；</li>
<li>USER：用户与 UID 映射；</li>
<li>Cgroup：cgroup 视图（较新）。</li>
</ul>
<p><strong>Cgroups 的作用？</strong>
限制、统计和隔离进程组的资源使用（CPU、内存、IO、PID 数量）。CPU 用 quota/period（<code>--cpus</code>）或 shares（<code>--cpu-shares</code> 相对权重）；内存超限触发 OOM Killer，容器退出码 137。</p>
<p><strong>Docker 的分层架构（与 containerd / runc / OCI 的关系）？</strong>
<code>dockerd</code>（接收 CLI 请求、构建、镜像管理）→ <code>containerd</code>（容器生命周期管理，K8s 也直接用它）→ <code>runc</code>（真正调用 Linux namespace/cgroup 创建进程）。镜像格式遵循 OCI Image Spec，运行时遵循 OCI Runtime Spec，因此 Docker 构建的镜像可以被 containerd、CRI-O、Podman 通用。<strong>Docker 1.24 之后 K8s 抛弃的是 dockershim 适配层，不是容器本身。</strong></p>
<p><strong>UnionFS 是如何工作的？</strong>
采用<strong>写时复制（CoW）</strong>：镜像由多个只读层叠加而成，容器启动时在最上面追加一个可写层；读文件时从上层往下找，修改文件时先把文件复制到可写层再改，因此<strong>多个容器共享一份只读镜像层</strong>，启动快、占用小。Docker 默认存储驱动为 overlay2。</p>
<p><strong>容器的 1 号进程与信号处理？</strong>
容器 PID 1 就是应用进程（exec 形式时），它需要负责回收僵尸进程、处理 SIGTERM 优雅退出。若应用不处理信号或使用 shell 形式启动，<code>Ctrl+C</code>/<code>docker stop</code> 会失效或超时后被 SIGKILL（10 秒 grace period 可用 <code>--stop-timeout</code> 调整）。</p>
<h2>安全与排错</h2>
<p><strong>容器为什么不安全？如何加固？</strong></p>
<ul>
<li>共享内核：内核漏洞可逃逸；应保持内核与 Docker 版本更新。</li>
<li>默认 root 运行：用 <code>USER</code> 指定非 root、<code>--user</code> 覆盖、开启 user namespace。</li>
<li>权限：避免 <code>--privileged</code>，按需 <code>--cap-drop=ALL --cap-add=NET_BIND_SERVICE</code>，使用 <code>--security-opt no-new-privileges</code>、只读根文件系统 <code>--read-only</code>。</li>
<li>敏感信息：不要把密钥写进镜像层（<code>docker history</code> 可见），用环境变量、secret 文件或外部密钥管理。</li>
<li>其他：AppArmor/SELinux 配置、限制资源防 DoS、镜像扫描（Trivy）与最小化基础镜像。</li>
</ul>
<p><strong>容器启动失败怎么排查？</strong></p>
<ol>
<li><code>docker ps -a</code> 看状态与退出码，<code>docker logs</code> 看应用日志；</li>
<li><code>docker inspect</code> 看 State.Error、OOMKilled、挂载与端口；</li>
<li><code>docker run -it --entrypoint /bin/sh image</code> 进镜像手工验证命令与文件是否存在；</li>
<li>端口冲突（<code>netstat -tulnp</code>）、磁盘满（<code>docker system df</code>）、权限问题（挂载目录 SELinux 需 <code>:z</code>）。</li>
</ol>
<p><strong>常见问题：磁盘被容器占满怎么办？</strong>
先 <code>docker system df -v</code> 定位，再清理：<code>docker container prune</code>、<code>docker image prune -a</code>、<code>docker volume prune</code>、<code>docker builder prune</code>；同时配置日志 max-size、给容器日志与数据盘单独分区。</p>
<p><strong>为什么容器里不能用 systemd / systemctl？</strong>
容器只运行一个前台进程，没有 init 系统；应把每个服务拆成独立容器，或用 supervisor 管理多进程（不推荐），确需时用 <code>--privileged</code> + systemd 镜像（脏且不安全）。</p>
<p><strong>Docker 与 Kubernetes 的关系？</strong>
Docker 是单机容器引擎，K8s 是集群编排系统；K8s 早期通过 dockershim 调用 Docker，1.24 起改为直接用 CRI 对接 containerd/CRI-O，镜像标准（OCI）保持通用。</p>
]]></content>
    <author><name>L7WD3-Xiao</name></author>
    <category term="Interview"/>
  </entry>
  <entry>
    <title>Shell 三剑客八股</title>
    <link href="https://blog-l7wd3.pages.dev/posts/interview/shell-three/" rel="alternate" type="text/html"/>
    <id>https://blog-l7wd3.pages.dev/posts/interview/shell-three/</id>
    <published>2026-08-12T00:00:00.000Z</published>
    <updated>2026-08-12T00:00:00.000Z</updated>
    <summary>详细版 grep/sed/awk 八股</summary>
    <content type="html"><![CDATA[<h2>一、三剑客总览与选型</h2>
<p>面试官常问“三者的区别是什么”或“什么场景用谁”。记住一个口诀：<strong>grep 负责“查”，sed 负责“改”，awk 负责“算”和“格式化”</strong>。</p>
<table>
<thead>
<tr>
<th>工具</th>
<th>核心职责</th>
<th>一句话选型</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>grep</strong></td>
<td>文本<strong>过滤</strong>与<strong>搜索</strong></td>
<td>仅需<strong>查找</strong>、<strong>计数</strong>或<strong>筛选</strong>行，就用 grep。</td>
</tr>
<tr>
<td><strong>sed</strong></td>
<td>文本<strong>编辑</strong>与<strong>替换</strong></td>
<td>需要<strong>替换</strong>、<strong>删除</strong>、<strong>插入</strong>文本行，就用 sed。</td>
</tr>
<tr>
<td><strong>awk</strong></td>
<td>文本<strong>格式化</strong>与<strong>分析</strong></td>
<td>需要<strong>按列处理</strong>、<strong>统计计算</strong>、<strong>生成报告</strong>，就用 awk。</td>
</tr>
</tbody>
</table>
<hr />
<h2>二、grep（文本搜索器）</h2>
<h3>1. 核心功能</h3>
<p>在文件或输入流中，根据<strong>正则表达式</strong>搜索文本，并打印匹配的行。</p>
<h3>2. 高频选项</h3>
<table>
<thead>
<tr>
<th>选项</th>
<th>含义</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>-i</code></td>
<td><strong>忽略大小写</strong></td>
</tr>
<tr>
<td><code>-v</code></td>
<td><strong>反向匹配</strong>，显示不包含模式的行</td>
</tr>
<tr>
<td><code>-n</code></td>
<td>显示匹配行的<strong>行号</strong></td>
</tr>
<tr>
<td><code>-c</code></td>
<td>只输出<strong>匹配行的总数</strong></td>
</tr>
<tr>
<td><code>-l</code></td>
<td>只输出<strong>包含匹配内容的文件名</strong></td>
</tr>
<tr>
<td><code>-r</code></td>
<td><strong>递归搜索</strong>目录下的所有文件</td>
</tr>
<tr>
<td><code>-E</code></td>
<td>使用<strong>扩展正则表达式</strong>（支持<code>+</code>、<code>?</code>、`</td>
</tr>
<tr>
<td><code>-o</code></td>
<td>只输出<strong>匹配到的部分</strong>，而不是整行</td>
</tr>
<tr>
<td><code>-A n</code></td>
<td>显示匹配行及其<strong>后 n 行</strong>（After）</td>
</tr>
<tr>
<td><code>-B n</code></td>
<td>显示匹配行及其<strong>前 n 行</strong>（Before）</td>
</tr>
<tr>
<td><code>-C n</code></td>
<td>显示匹配行及其<strong>前后各 n 行</strong>（Context）</td>
</tr>
<tr>
<td><code>--include</code></td>
<td>仅在<strong>指定类型</strong>的文件中搜索</td>
</tr>
<tr>
<td><code>-m n</code></td>
<td>匹配 <strong>n 次后停止</strong>读取</td>
</tr>
</tbody>
</table>
<h3>3. 典型面试题</h3>
<table>
<thead>
<tr>
<th>题目</th>
<th>命令示例</th>
</tr>
</thead>
<tbody>
<tr>
<td>显示匹配行及其后2行</td>
<td><code>grep -A 2 "pattern" file.txt</code></td>
</tr>
<tr>
<td>在<code>file.txt</code>中查找包含"error"或"fail"的行并显示行号</td>
<td>grep -n -E "error|fail" file.txt</td>
</tr>
<tr>
<td>统计<code>file.txt</code>中"error"出现的总次数</td>
<td>grep -o "error" file.txt | wc -l</td>
</tr>
<tr>
<td>过滤掉文件中的空行和注释行（以<code>#</code>开头）</td>
<td>grep -vE '^#|^$' file.txt</td>
</tr>
<tr>
<td>递归查找<code>/etc/</code>下所有<code>.conf</code>文件中包含"port"的行</td>
<td><code>grep -r --include="*.conf" "port" /etc/</code></td>
</tr>
<tr>
<td>找出当前目录下包含"127.0.0.1"的文件</td>
<td><code>grep "127.0.0.1" \</code>find -type f``</td>
</tr>
<tr>
<td>匹配以数字开头的行</td>
<td><code>grep "^[0-9]" file.txt</code></td>
</tr>
<tr>
<td>匹配IPv4地址</td>
<td><code>grep -E "([0-9]{1,3}\.){3}[0-9]{1,3}" file.txt</code></td>
</tr>
</tbody>
</table>
<h3>4. 进阶考点：正则表达式引擎</h3>
<p><code>grep</code>默认使用<strong>NFA（非确定性有限自动机）引擎</strong>。需注意**灾难性回溯（Catastrophic Backtracking）**问题，例如模式<code>(a+)+b</code>匹配一长串<code>a</code>会消耗极长时间。生产环境中，对超大文件使用<code>-m</code>参数限制匹配次数是常见的优化手段。</p>
<hr />
<h2>三、sed（流编辑器）</h2>
<h3>1. 核心功能</h3>
<p>非交互式地<strong>编辑文本流</strong>，主要进行“取行、删除、替换、插入”等操作。</p>
<h3>2. 高频选项与命令</h3>
<table>
<thead>
<tr>
<th>选项/命令</th>
<th>含义</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>-n</code></td>
<td><strong>安静模式</strong>，仅显示被<code>p</code>命令处理的行</td>
</tr>
<tr>
<td><code>-i</code></td>
<td><strong>直接修改</strong>原文件（非常危险，生产环境慎用）</td>
</tr>
<tr>
<td><code>-E</code> / <code>-r</code></td>
<td>使用<strong>扩展正则表达式</strong></td>
</tr>
<tr>
<td><code>a</code></td>
<td><strong>追加</strong>，在指定行后添加新行</td>
</tr>
<tr>
<td><code>d</code></td>
<td><strong>删除</strong>指定行</td>
</tr>
<tr>
<td><code>p</code></td>
<td><strong>打印</strong>指定行</td>
</tr>
<tr>
<td><code>s/old/new/g</code></td>
<td><strong>替换</strong>，<code>g</code>可选，表示全局替换</td>
</tr>
<tr>
<td><code>/pattern/</code></td>
<td><strong>模式匹配</strong>，可包含正则，<code>/</code> 需要转义 <code>\/</code></td>
</tr>
<tr>
<td><code>a,b</code></td>
<td><strong>多行</strong>，从第 a 行到第 b 行</td>
</tr>
<tr>
<td><code>/pattern1/,/pattern2/</code></td>
<td><strong>按范围匹配</strong>，表示从匹配 pattern1 的行到匹配 pattern2 的行</td>
</tr>
<tr>
<td><code>/pattern1/,//</code></td>
<td><strong>按范围匹配</strong>，表示从匹配 pattern1 的行到下次匹配相同的行</td>
</tr>
</tbody>
</table>
<h3>3. 典型面试题</h3>
<table>
<thead>
<tr>
<th>题目</th>
<th>命令示例</th>
</tr>
</thead>
<tbody>
<tr>
<td>将文件中所有<code>apple</code>替换为<code>orange</code>并直接修改文件</td>
<td><code>sed -i 's/apple/orange/g' file.txt</code></td>
</tr>
<tr>
<td>删除文件的第5到第10行</td>
<td><code>sed '5,10d' file.txt</code></td>
</tr>
<tr>
<td>显示文件的第3到第10行</td>
<td><code>sed -n '3,10p' file.txt</code></td>
</tr>
<tr>
<td>删除文件中的所有空行</td>
<td><code>sed '/^$/d' file.txt</code></td>
</tr>
<tr>
<td>删除所有以<code>#</code>或<code>//</code>开头的注释行</td>
<td>sed '/^#|^///d' file.txt</td>
</tr>
<tr>
<td>在文件末尾追加一行"END OF FILE"</td>
<td><code>sed '$a END OF FILE' file.txt</code></td>
</tr>
<tr>
<td>将<code>/etc/passwd</code>中所有<code>nologin</code>替换为<code>bash</code></td>
<td><code>sed -i 's/nologin/bash/g' /etc/passwd</code></td>
</tr>
</tbody>
</table>
<h3>4. 进阶考点：定址与分组</h3>
<ul>
<li><strong>定址（Addressing）</strong>：可以指定操作的行范围，如<code>'10,20d'</code>删除10到20行。</li>
<li><strong>分组（Grouping）</strong>：替换时使用<code>(...)</code>分组，并用<code>\1</code>、<code>\2</code>引用。例如<code>sed -r 's#(^[^:]+):.*#\1#g'</code>可以提取<code>/etc/passwd</code>中的用户名。（实际取列推荐下面的<code>cut -d: -f1,2,...</code>，-d 指定分隔符，-f 跟提取第几列，提取多列用逗号分隔）</li>
</ul>
<table>
<thead>
<tr>
<th>正则</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>s#...#...#g</code></td>
<td>替换命令，用 <code>#</code> 作分隔符（避免和内容里的 <code>/</code> 冲突）</td>
</tr>
<tr>
<td><code>(^[^:]+)</code></td>
<td>第 1 个捕获组：行首开始，一个或多个<strong>非冒号</strong>字符</td>
</tr>
<tr>
<td><code>:</code></td>
<td>匹配一个冒号</td>
</tr>
<tr>
<td><code>.*</code></td>
<td>匹配冒号后面的所有内容</td>
</tr>
<tr>
<td><code>\1</code></td>
<td>替换为第 1 个捕获组的内容</td>
</tr>
</tbody>
</table>
<hr />
<h2>四、awk（文本分析器）</h2>
<h3>1. 核心功能</h3>
<p>一种强大的<strong>文本分析</strong>和<strong>格式化输出</strong>编程语言，以<strong>行</strong>为单位，默认以<strong>空白符</strong>为分隔符将行切分为<strong>字段</strong>。</p>
<h3>2. 高频内置变量</h3>
<table>
<thead>
<tr>
<th>变量</th>
<th>含义</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>$0</code></td>
<td><strong>整行内容</strong></td>
</tr>
<tr>
<td><code>$1, $2, ...</code></td>
<td><strong>第1、2...个字段</strong></td>
</tr>
<tr>
<td><code>NF</code></td>
<td><strong>当前行的字段总数</strong>（Number of Fields）</td>
</tr>
<tr>
<td><code>NR</code></td>
<td><strong>当前行的行号</strong>（Number of Records）</td>
</tr>
<tr>
<td><code>FS</code></td>
<td><strong>输入字段分隔符</strong>，默认为空白符</td>
</tr>
<tr>
<td><code>OFS</code></td>
<td><strong>输出字段分隔符</strong>，默认为空白符</td>
</tr>
<tr>
<td><code>RS</code></td>
<td><strong>输入记录分隔符</strong>，默认为换行符</td>
</tr>
<tr>
<td><code>ORS</code></td>
<td><strong>输出记录分隔符</strong>，默认为换行符</td>
</tr>
<tr>
<td><code>FILENAME</code></td>
<td>当前正在处理的<strong>文件名</strong></td>
</tr>
</tbody>
</table>
<h3>3. 高频选项与模式</h3>
<table>
<thead>
<tr>
<th>选项/模式</th>
<th>含义</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>-F</code></td>
<td>指定<strong>输入字段分隔符</strong>，如<code>-F:</code></td>
</tr>
<tr>
<td><code>BEGIN{...}</code></td>
<td>在<strong>处理第一行文本之前</strong>执行</td>
</tr>
<tr>
<td><code>END{...}</code></td>
<td>在<strong>处理完所有文本之后</strong>执行</td>
</tr>
<tr>
<td><code>/pattern/</code></td>
<td><strong>匹配</strong>正则表达式的行</td>
</tr>
<tr>
<td><code>! /pattern/</code></td>
<td><strong>不匹配</strong>正则表达式的行</td>
</tr>
</tbody>
</table>
<h3>4. 典型面试题</h3>
<table>
<thead>
<tr>
<th>题目</th>
<th>命令示例</th>
</tr>
</thead>
<tbody>
<tr>
<td>取出<code>/etc/passwd</code>的第一列（用户名）</td>
<td><code>awk -F: '{print $1}' /etc/passwd</code></td>
</tr>
<tr>
<td>打印文件<code>file.txt</code>的<strong>行号</strong>和<strong>整行内容</strong></td>
<td><code>awk '{print NR, $0}' file.txt</code></td>
</tr>
<tr>
<td>打印每行的<strong>字段总数</strong>（<code>NF</code>）</td>
<td><code>awk '{print NF}' file.txt</code></td>
</tr>
<tr>
<td>统计<code>/etc/passwd</code>中<strong>用户总数</strong></td>
<td><code>awk -F: 'END{print NR}' /etc/passwd</code></td>
</tr>
<tr>
<td>过滤出<strong>奇数行</strong></td>
<td><code>awk 'NR%2==1' file.txt</code></td>
</tr>
<tr>
<td>提取网卡IP地址（多种实现）</td>
<td>ip a | awk '/([0-9]{1,3}.){3}[0-9]{1,3}/{print $2}'</td>
</tr>
<tr>
<td>统计Nginx日志中<strong>独立IP数</strong></td>
<td><code>awk '{dic[$1]++}END{for(i in dic) print i}' access.log</code></td>
</tr>
<tr>
<td>求第二列和</td>
<td><code>awk '{sum+=$2} END{print sum}' 1.log</code></td>
</tr>
<tr>
<td>查空行</td>
<td><code>awk '/^$/{print NR}' file1.txt</code></td>
</tr>
</tbody>
</table>
<h3>5. 进阶考点：BEGIN与END</h3>
<p><code>awk</code>脚本可以包含<code>BEGIN</code>和<code>END</code>代码块，分别用于<strong>初始化和汇总</strong>。例如，在<code>BEGIN</code>中设置分隔符<code>FS=":"</code>，在<code>END</code>中打印统计总数，是极其常见的用法。</p>
<hr />
<h2>五、综合对比与常见组合</h2>
<h3>1. 三剑客对比总结</h3>
<table>
<thead>
<tr>
<th>工具</th>
<th>核心优势</th>
<th>适用场景</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>grep</strong></td>
<td><strong>速度最快</strong>，简单查找</td>
<td>日志过滤、内容搜索</td>
</tr>
<tr>
<td><strong>sed</strong></td>
<td><strong>行编辑</strong>能力最强</td>
<td>批量替换、文本格式化</td>
</tr>
<tr>
<td><strong>awk</strong></td>
<td><strong>列处理</strong>和<strong>统计</strong>能力最强</td>
<td>日志分析、数据报告</td>
</tr>
</tbody>
</table>
<h3>2. 经典组合用法</h3>
<ul>
<li><strong><code>grep</code> + <code>awk</code></strong>：先用<code>grep</code>粗筛，再用<code>awk</code>精处理。
<ul>
<li><code>grep "ERROR" app.log | awk '{print $1, $NF}'</code>。</li>
<li>统计<code>php.ini</code>中<strong>每个单词出现次数</strong>：<code>grep -oE '[a-zA-Z]+' php.ini | awk '{dic[$0]++}END{for(i in dic) print i, dic[i]}'</code></li>
</ul>
</li>
<li><strong><code>find</code> + <code>xargs</code> + <code>grep</code></strong>：在大量文件中搜索内容。例如：<code>find . -name "*.log" | xargs grep "timeout"</code>。</li>
<li><strong><code>grep</code> + <code>sed</code></strong>：查找并替换。例如：<code>grep -l "old" *.txt | xargs sed -i 's/old/new/g'</code>。</li>
</ul>
<h2>六、其他文本处理命令</h2>
<h3>1. 命令</h3>
<ul>
<li><strong><code>cut</code></strong>：按列切分，<code>cut -d: -f1 /etc/passwd</code>。</li>
<li><strong><code>sort</code></strong>：排序，<code>-n</code>（数字）、<code>-r</code>（逆序）、<code>-k</code>（指定列）、<code>-t</code>（指定分隔符）、<code>-u</code>（去重，等价于<code>sort 'filename' | uniq</code>）。</li>
<li><strong><code>uniq</code></strong>：去重（<strong>必须配合 <code>sort</code> 使用，仅去除相邻重复行</strong>）。常用 <code>sort | uniq -c</code> 统计次数。</li>
<li><strong><code>tr</code></strong>：字符替换/删除，<code>tr 'A-Z' 'a-z'</code>（大小写转换）、<code>tr -d '\r'</code>（删除回车）。</li>
<li><strong><code>xargs</code></strong>：将标准输入转为命令行参数。<code>find . -name "*.log" | xargs rm -f</code>（解决参数过长问题）。</li>
</ul>
<h3>2. 经典组合用法</h3>
<ul>
<li>查找当前目录所有包含有字符”123”的文件名称：<code>grep -r "123" /root | cut -d: -f1 | sort -u</code></li>
</ul>
<h2>七、正则表达式规则</h2>
<p><strong>基础正则表达式（BRE）</strong></p>
<ul>
<li><strong>^ / $</strong>：匹配行首、行尾</li>
<li><strong>.</strong>：匹配除换行符外的任意单字符</li>
<li>*：匹配前一字符 0 次或多次</li>
<li><strong>[list] / [^list]</strong>：匹配列表内或不在列表内的任意字符</li>
<li><strong>{n}, {n,}, {n,m}</strong>：匹配前一字符的固定或范围次数（需转义 <code>\{ \}</code> ）</li>
</ul>
<p><strong>扩展正则表达式（ERE）</strong></p>
<ul>
<li><strong>+</strong>：匹配前一字符 1 次或多次</li>
<li><strong>?</strong>：匹配前一字符 0 次或 1 次</li>
<li><strong>()</strong>：分组，将括号内模式视为整体</li>
<li><strong>|</strong>：逻辑或，匹配任一模式</li>
<li><strong>{n}, {n,}, {n,m}</strong>：次数匹配（无需转义）</li>
</ul>
]]></content>
    <author><name>L7WD3-Xiao</name></author>
    <category term="Interview"/>
  </entry>
  <entry>
    <title>Shell 八股</title>
    <link href="https://blog-l7wd3.pages.dev/posts/interview/shell/" rel="alternate" type="text/html"/>
    <id>https://blog-l7wd3.pages.dev/posts/interview/shell/</id>
    <published>2026-08-10T00:00:00.000Z</published>
    <updated>2026-08-10T00:00:00.000Z</updated>
    <summary>速记版 Shell 八股</summary>
    <content type="html"><![CDATA[<h2>基础语法</h2>
<p><strong>Shell脚本第一行是什么？为什么重要？</strong>
<code>#!/bin/bash</code>（shebang），指定脚本解释器。</p>
<p><strong>变量赋值为什么报错<code>command not found</code>？</strong>
等号两边不能有空格——<code>name = "Alice"</code>会被解析为命令，正确写法<code>name="Alice"</code>。</p>
<p><strong>如何定义只读变量？</strong>
<code>readonly var</code> 或 <code>declare -r var</code>，后续无法修改。</p>
<p><strong>如何撤销变量？</strong>
<code>unset var</code>（不能撤销只读变量）。</p>
<p><strong><code>[ ]</code>vs<code>[[ ]]</code>vs<code>(())</code></strong></p>
<table>
<thead>
<tr>
<th>语法</th>
<th>类型</th>
<th>支持逻辑运算符</th>
<th>支持正则/通配符</th>
<th>空变量安全性</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>[ ]</code></td>
<td>POSIX 通用</td>
<td><code>-a</code>（与）、<code>-o</code>（或）</td>
<td>不支持</td>
<td><strong>变量必须加双引号</strong>，否则报错</td>
</tr>
<tr>
<td><code>[[ ]]</code></td>
<td>Bash 特有</td>
<td><code>&amp;&amp;</code>、||、<code>!</code></td>
<td>支持 <code>=~</code>（正则）和 <code>==</code>（通配符）</td>
<td><strong>变量无需加引号</strong>，自动处理空值</td>
</tr>
<tr>
<td><code>(( ))</code></td>
<td>Bash 特有</td>
<td>数学比较（<code>&gt;</code>、<code>&lt;</code>、<code>&gt;=</code>）</td>
<td>专用于数值计算</td>
<td>专用于 <code>C</code> 风格循环和数值判断</td>
</tr>
</tbody>
</table>
<p><strong>判断文件是否存在且为普通文件</strong>
<code>[ -f "$file" ]</code> 或 <code>[[ -f $file ]]</code>。</p>
<p><strong>数值计算</strong></p>
<ul>
<li><strong><code>$((...))</code></strong>（最推荐，纯 Bash 内建，速度最快）：<code>sum=$((a + b))</code></li>
<li><strong><code>let</code> 命令</strong>：<code>let sum=a+b</code>（无需 <code>$</code> 前缀）</li>
<li><strong><code>expr</code> 外部命令</strong>（慢，过时）：<code>sum=$(expr $a + $b)</code>（注意加号两侧必须有空格）</li>
<li><strong>浮点数运算</strong>：必须借助 <code>bc</code> 或 <code>awk</code>，如 <code>echo "scale=2; 5/3" | bc</code></li>
</ul>
<p><strong>exit和return的区别？</strong>
<code>exit N</code>终止整个脚本；<code>return N</code>从函数返回。0表示成功，非0表示失败。</p>
<p><strong>位置参数<code>$0</code>、<code>$1</code>、<code>$#</code>、<code>$?</code>、<code>$$</code>、<code>$@</code>的含义？</strong></p>
<ul>
<li><code>$0</code>：脚本名，</li>
<li><code>$1</code>：第一个参数，</li>
<li><code>$#</code>：传递给脚本的参数个数，</li>
<li><code>$?</code>：上条命令退出码。</li>
<li><code>$$</code>：当前Shell进程的 <strong>PID</strong>（常用于生成临时唯一文件名，如 <code>/tmp/a_$$</code>）。</li>
<li><code>$!</code>：后台<strong>最后一个进程</strong>的 PID（配合 <code>wait $!</code> 使用）。</li>
<li><code>$@</code>：所有参数（带引号保留空格），</li>
</ul>
<p><strong>什么是IFS？<code>"$*"</code> 和 <code>"$@"</code> 区别？</strong></p>
<p>Internal Field Separator（内部字段分隔符），是 Shell 中的一个特殊环境变量，用来决定 <strong>Shell 在哪些字符处把一个字符串拆分成多个单词（字段）</strong>。默认值是：空格 + 制表符 + 换行符</p>
<p>假设这样调用：<code>./test.sh "a b" c d</code></p>
<table>
<thead>
<tr>
<th>写法</th>
<th>是否保留每个参数的独立性</th>
<th>是否受 <code>IFS</code> 影响</th>
<th>结果</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>$@</code> / <code>$*</code></td>
<td>❌ 会重新分词</td>
<td>✅ 受影响</td>
<td>"a" "b" "c" "d"</td>
</tr>
<tr>
<td><code>"$@"</code></td>
<td>✅ 每个参数独立</td>
<td>❌ 不受影响</td>
<td>"a b" "c" "d"</td>
</tr>
<tr>
<td><code>"$*"</code></td>
<td>❌ 合并成一个字符串</td>
<td>✅ 受影响（用 IFS 连接）</td>
<td>"a b c d"</td>
</tr>
</tbody>
</table>
<h2>重定向</h2>
<p>用于改变命令的输入来源或输出目的地，替代默认的 <strong>键盘输入(stdin)</strong> 和 <strong>屏幕输出(stdout/stderr)</strong></p>
<p><strong>常用语法</strong></p>
<ul>
<li><strong>输出覆盖</strong>：<code>command &gt; file</code>（覆盖写入）</li>
<li><strong>输出追加</strong>：<code>command &gt;&gt; file</code>（追加写入）</li>
<li><strong>输入重定向</strong>：<code>command &lt; file</code>（从文件读取输入）</li>
<li><strong>错误输出</strong>：<code>command 2&gt; file</code>（仅错误信息）</li>
<li><strong>合并输出</strong>：<code>command &gt; file 2&gt;&amp;1</code>（stdout 和 stderr 合并）</li>
<li><strong>Here Document</strong>：<code>command &lt;&lt; EOF ... EOF</code>（内联多行输入），常用生成配置文件。
<ul>
<li><code>&lt;&lt;-</code> 可以忽略前导 Tab（缩进友好）</li>
</ul>
</li>
<li><strong>Here String</strong>：<code>command &lt;&lt;&lt; "string"</code>（单行字符串作为标准输入）</li>
</ul>
<p><strong><code>2&gt;&amp;1</code> 必须放在哪？</strong></p>
<ul>
<li><strong>功能</strong>：将标准错误（2）重定向到标准输出（1）所在的地方。</li>
<li><strong>正确写法</strong>：<code>command &gt; file 2&gt;&amp;1</code>（先让标准输出去file，再把错误也指向file）。</li>
<li><strong>错误写法</strong>：<code>command 2&gt;&amp;1 &gt; file</code>（错误先指向了当前终端的标准输出，随后标准输出才去file，导致错误依然打印在屏幕上）</li>
<li><strong>简写</strong>：<code>command &amp;&gt; file</code> 等效于上面正确的写法。</li>
</ul>
<h2>作用域</h2>
<p><strong>子 Shell 与大括号 <code>{}</code> 的区别？</strong></p>
<ul>
<li><strong>小括号 <code>( ... )</code></strong>：在<strong>子 Shell</strong> 中执行，内部变量修改不影响父 Shell。</li>
<li><strong>大括号 <code>{ ... }</code></strong>：在<strong>当前 Shell</strong> 中执行，内部变量修改会影响父 Shell。<strong>注意大括号内部左侧必须有空格</strong>，且命令结尾必须加分号 <code>;</code>。</li>
</ul>
<p><strong>进程替换（<code>&lt;()</code>）与管道（<code>|</code>）的本质区别</strong></p>
<ul>
<li><strong>管道 <code>|</code></strong>：会创建<strong>子 Shell</strong>，导致父 Shell 变量无法被子进程修改。<code>a=1; echo "2" | read a; echo $a</code> 依然输出 1。</li>
<li><strong>进程替换 <code>&lt;()</code></strong>：不会引入子 Shell 的副作用，相当于把命令输出伪装成文件。<code>while read line; do ...; done &lt; &lt;(cat file)</code> 可以在当前 Shell 中执行循环（规避管道变零）。</li>
</ul>
<p><strong>export 变量和无export区别？</strong></p>
<ul>
<li><strong>有 <code>export</code></strong>：变量会传递给<strong>所有子进程</strong>（子Shell、外部命令、脚本等），称为<strong>环境变量</strong>。</li>
<li><strong>无 <code>export</code></strong>：变量仅在<strong>当前Shell进程</strong>中有效，子进程不可见，称为<strong>局部变量</strong>（或Shell变量）。</li>
</ul>
<p><strong>source/bash script.sh 和 ./script.sh 和 exec ./mod.sh的区别？</strong>
<code>source</code>在当前Shell执行，变量和环境变量保留；<code>./script.sh</code>在子Shell执行，变量不保留。</p>
<table>
<thead>
<tr>
<th>执行方式</th>
<th>执行环境</th>
<th>变量继承</th>
<th>是否影响当前环境</th>
<th>权限</th>
</tr>
</thead>
<tbody>
<tr>
<td>source 或 . script.sh</td>
<td>当前Shell</td>
<td>✅</td>
<td>✅</td>
<td>r</td>
</tr>
<tr>
<td>bash script.sh</td>
<td>子Shell</td>
<td>❌</td>
<td>❌</td>
<td>r</td>
</tr>
<tr>
<td>./script.sh</td>
<td>子Shell</td>
<td>❌</td>
<td>❌</td>
<td>rx</td>
</tr>
<tr>
<td>exec script.sh</td>
<td>脚本覆盖当前Shell</td>
<td>❌</td>
<td>❌</td>
<td>rx</td>
</tr>
</tbody>
</table>
<p><strong>综合上面两个：</strong></p>
<pre><code>#!/bin/bash
export PATH="aaa"
cd bbb
</code></pre>
<p>记上述脚本为<code>mod.sh</code>，问：<strong><code>source/bash mod.sh</code> 和 <code>exec ./mod.sh</code> 父 Shell 的 PATH 和当前目录怎么变化？为什么？</strong></p>
<table>
<thead>
<tr>
<th>执行方式</th>
<th>父Shell的 <code>PATH</code></th>
<th>父Shell的PWD</th>
<th>脚本运行结束后</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>source mod.sh</code></td>
<td><code>aaa</code></td>
<td><code>bbb</code></td>
<td>变量和目录变更<strong>永久保留</strong>在父Shell中</td>
</tr>
<tr>
<td><code>bash mod.sh</code></td>
<td>不变</td>
<td>不变</td>
<td>脚本在子Shell中运行，父Shell毫无影响</td>
</tr>
<tr>
<td><code>exec ./mod.sh</code></td>
<td>消失</td>
<td>消失</td>
<td>父Shell<strong>被脚本替换</strong>，进程随脚本结束而退出</td>
</tr>
</tbody>
</table>
<h2>进阶与陷阱</h2>
<p><strong>如何安全地逐行读取文件？</strong></p>
<pre><code>while IFS= read -r line; do
    echo "$line"
done &lt; file
</code></pre>
<p>使用<code>IFS=</code>将IFS设置为一个空字符串，以确保读取的行不会因为空格或制表符被截断，<code>-r</code>防止反斜杠转义。</p>
<p><strong>set -e的作用？</strong>
set命令用于显示或设置 Shell 的特性和变量，<code>-e</code> 遇到非零退出码立即退出脚本。
注意：管道中命令失败不一定触发（需配合<code>set -o pipefail</code>）。</p>
<p><strong><code>set -euxo pipefail</code> 各参数含义？</strong></p>
<ul>
<li><code>-e</code>：脚本中任何命令失败（非0退出码）立即<strong>终止退出</strong>（防止错误蔓延）。</li>
<li><code>-u</code>：引用<strong>未定义变量</strong>时报错退出（防止空变量酿成事故）。</li>
<li><code>-x</code>：<strong>调试模式</strong>，执行前把每条命令打印到终端（用于排错）。</li>
<li><code>-o pipefail</code>：管道中<strong>任何一个</strong>子命令失败，整个管道就算失败（弥补 <code>-e</code> 只管管道最后一条命令的缺陷）。</li>
<li><code>-n</code>：仅<strong>语法检查</strong>，不执行（可用于排错）。</li>
<li><code>-v</code>：打印读取到的<strong>原始输入行</strong>（与 <code>-x</code> 不同，<code>-x</code> 打印展开后的命令）。</li>
</ul>
<p><strong>如何避免<code>rm -rf</code>误删？</strong>
先<code>ls</code>确认再执行；用<code>rm -i</code>交互确认；关键操作前备份；脚本中用<code>set -u</code>防止空变量。</p>
<p><strong>Shell脚本中如何捕获信号并做清理？</strong>
<code>trap 'cleanup; exit' INT TERM</code> 捕获中断和终止信号。</p>
<p><strong>信号捕获与后台控制</strong></p>
<ul>
<li><strong><code>trap</code></strong>：捕获信号并执行自定义操作。常用于脚本退出时清理临时文件。
<ul>
<li>语法：<code>trap "rm -f /tmp/temp_$$" EXIT</code>（脚本无论正常/异常退出都会触发）。</li>
<li>常见信号：<code>INT</code>（Ctrl+C）、<code>TERM</code>、<code>EXIT</code>。</li>
</ul>
</li>
<li><strong><code>wait</code></strong>：阻塞等待后台进程结束。<code>wait $pid</code> 等待指定进程，无参则等待所有后台子进程。
<ul>
<li>面试题：<code>sleep 3 &amp; wait</code> 会等待 <code>sleep</code> 结束再继续。</li>
</ul>
</li>
</ul>
<p><strong>如何用Shell实现并发控制？</strong>
使用<code>&amp;</code>后台执行 + <code>wait</code>等待，或用<code>xargs -P N</code>、<code>GNU parallel</code>控制并发数。</p>
<p><strong>后台运行 <code>&amp;</code>  vs <code>nohup</code> 的区别</strong></p>
<ul>
<li><code>command &amp;</code>：放入<strong>后台</strong>运行，但进程依然属于当前终端（Shell）的子进程。<strong>终端关闭</strong>（收到 SIGHUP 信号），进程会<strong>随之消亡</strong>。</li>
<li><code>nohup command &amp;</code>：让进程<strong>忽略 SIGHUP 信号（1信号）</strong>，同时默认将输出重定向到 <code>nohup.out</code>。<strong>终端关闭后，进程依然存活</strong>。</li>
<li><strong>最佳实践</strong>：想退出终端后服务不挂，必须用 <code>nohup</code> + <code>&amp;</code>（或用 <code>screen</code>/<code>tmux</code>）。</li>
</ul>
<p><strong>如何定义函数并获取返回值？</strong></p>
<ul>
<li><strong>定义</strong>：<code>function func_name() { ... }</code> 或 <code>func_name() { ... }</code></li>
<li><strong>局部变量</strong>：必须用 <code>local</code> 声明，否则默认全局污染。</li>
<li><strong>返回值（数字）</strong>：<code>return 0</code>（成功）或 <code>return 1</code>（失败），通过 <code>$?</code> 接收。<strong>但 <code>return</code> 只能返回 0-255 的状态码</strong>。</li>
<li><strong>返回字符串</strong>：不能用 <code>return</code>，必须用 <code>echo</code> 输出，调用方用 <code>$(func)</code> 接收。</li>
</ul>
<p><strong>单引号 <code>'</code>、双引号 <code>"</code>、反引号 ``和 <code>$()</code> 的区别</strong></p>
<ul>
<li><strong>单引号 <code>''</code></strong>：<strong>所见即所得</strong>，完全保留字面意思，无视所有变量和转义符。</li>
<li><strong>双引号 <code>""</code></strong>：<strong>部分保留</strong>，允许变量展开 <code>$var</code> 和命令替换 <code>$()</code>，但会阻止通配符 <code>*</code> 展开。</li>
<li><strong>反引号 ``与 <code>$()</code></strong>：均为<strong>命令替换</strong>（先执行内部命令）。<strong>强烈推荐 <code>$()</code></strong>，\支持嵌套（如 <code>$(grep $(whoami) file)</code>）且引号处理更清晰。</li>
</ul>
<p><strong>登录Shell vs 非登录Shell 与 <code>.bashrc</code>/<code>.bash_profile</code> 加载逻辑</strong></p>
<ul>
<li><strong>登录Shell</strong>（如 <code>su -</code>、SSH登录）：先读 <code>/etc/profile</code>，再找 <code>~/.bash_profile</code>（或 <code>~/.bash_login</code>、<code>~/.profile</code>），<strong>通常不读</strong> <code>~/.bashrc</code>。</li>
<li><strong>非登录Shell</strong>（如手动敲 <code>bash</code>）：只读 <code>~/.bashrc</code>。</li>
<li><strong>解决痛点</strong>：为了登录Shell也能加载别名，通常在 <code>~/.bash_profile</code> 里强制写入 <code>source ~/.bashrc</code>。</li>
</ul>
<h2>其他</h2>
<h3>命令连接符/逻辑判断：<code>;</code> 、<code>&amp;&amp;</code> 与 <code>||</code></h3>
<ul>
<li><code>;</code>：<strong>无视成败</strong>，前面命令执行完（无论对错），<strong>无条件</strong>执行后面的命令。</li>
<li><code>&amp;&amp;</code>：<strong>短路与</strong>，前面命令执行<strong>成功</strong>（<code>$?=0</code>）才执行后面的。</li>
<li><code>||</code>：<strong>短路或</strong>，前面命令执行<strong>失败</strong>（<code>$?≠0</code>）才执行后面的。</li>
<li><strong>经典组合</strong>：<code>make &amp;&amp; make install || echo "Failed"</code>（编译成功才安装，任一失败则报错）。</li>
</ul>
<h3>分支与循环结构速记</h3>
<table>
<thead>
<tr>
<th>结构</th>
<th>语法要点</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>if-then-else</code></td>
<td><code>if [ condition ]; then ...; elif [ cond2 ]; then ...; else ...; fi</code>（<strong>分号和 <code>then</code> 同行</strong>）</td>
</tr>
<tr>
<td><code>case</code></td>
<td>适合字符串匹配，支持 <code>*</code> 通配符。末尾 <code>;;</code> 跳出，<code>*)</code> 为默认</td>
</tr>
<tr>
<td><code>for</code> 循环</td>
<td><code>for i in {1..10}; do ...; done</code>（序列） <code>for ((i=0; i&lt;10; i++)); do ...; done</code>（C风格）</td>
</tr>
<tr>
<td><code>while</code> 循环</td>
<td><code>while read line; do ...; done &lt; file</code>（<strong>逐行读文件标准做法</strong>）</td>
</tr>
<tr>
<td><code>until</code> 循环</td>
<td>条件为假时执行，与 <code>while</code> 相反</td>
</tr>
</tbody>
</table>
<h3>数组操作（Bash 特有）</h3>
<table>
<thead>
<tr>
<th>操作</th>
<th>语法</th>
</tr>
</thead>
<tbody>
<tr>
<td>定义索引数组</td>
<td><code>arr=(a b c)</code> 或 <code>arr[0]="a"</code></td>
</tr>
<tr>
<td>定义关联数组（类似Map）</td>
<td><code>declare -A map; map["key"]="value"</code></td>
</tr>
<tr>
<td>获取所有元素</td>
<td><code>${arr[@]}</code>（独立参数） vs <code>${arr[*]}</code>（单字符串，类似于 <code>"$@"</code> vs <code>"$*"</code>）</td>
</tr>
<tr>
<td>获取数组长度</td>
<td><code>${#arr[@]}</code> 或 <code>${#arr[*]}</code></td>
</tr>
<tr>
<td>获取索引列表</td>
<td><code>${!arr[@]}</code>（关联数组常用）</td>
</tr>
<tr>
<td>切片</td>
<td><code>${arr[@]:1:2}</code>（从下标1取2个）</td>
</tr>
</tbody>
</table>
]]></content>
    <author><name>L7WD3-Xiao</name></author>
    <category term="Interview"/>
  </entry>
  <entry>
    <title>Linux 场景题</title>
    <link href="https://blog-l7wd3.pages.dev/posts/interview/linux-scene/" rel="alternate" type="text/html"/>
    <id>https://blog-l7wd3.pages.dev/posts/interview/linux-scene/</id>
    <published>2026-08-05T00:00:00.000Z</published>
    <updated>2026-08-05T00:00:00.000Z</updated>
    <summary>详细版 Linux 场景题</summary>
    <content type="html"><![CDATA[<h2>CPU / 内存</h2>
<h3>内存泄漏如何排查？</h3>
<p><strong>思路</strong>：遵循 <strong>“自上而下+工具链”</strong> 的思路</p>
<ol>
<li><strong>确认现象</strong>：用 <code>free -h</code> 看 <code>available</code> 是否持续走低，用 <code>vmstat 1</code> 看 <code>si</code>/<code>so</code> 是否频繁交换。</li>
<li><strong>定位进程</strong>：用 <code>ps aux --sort=-%mem | head -n 10</code> 或 <code>top</code> 按 <code>M</code> 排序，找到内存占用最高的 PID。</li>
<li><strong>深入分析（按语言选工具）</strong>：
<ul>
<li><strong>Java 应用</strong>：用 <code>jmap -dump</code> 导出堆转储，配合 MAT 分析支配树和 GC Roots 引用链，定位泄漏对象。</li>
<li><strong>C/C++ 应用</strong>：可用 <code>memleak</code> (eBPF) 追踪未释放的分配调用栈，或 <code>valgrind</code> 在测试环境分析。</li>
</ul>
</li>
<li><strong>判断类型</strong>：结合 GC 日志判断是堆内存泄漏、元空间泄漏还是直接内存泄漏，再针对性修复</li>
</ol>
<h3>服务器CPU使用率飙升，如何排查？</h3>
<p><strong>思路</strong>：遵循 <strong>“自上而下+工具链”</strong> 的思路</p>
<p><strong>标准排查流程：系统级/进程→线程→栈→系统调用</strong></p>
<ol>
<li><strong>系统级分析</strong>： <code>top</code>按 <code>P</code> 排序，或<code>ps aux --sort=-%cpu | head -n 10</code>，找到 <code>%CPU</code> 最高的异常进程 PID。
<ul>
<li>使用 <code>vmstat 1</code> 观察 <code>r</code>（运行队列）是否长时间大于CPU核数，若 <code>sy</code>（内核态占用）飙升，可能是有频繁的上下文切换或中断。</li>
<li>排查是否遭受了 <strong>DDoS/恶意攻击</strong>（大量握手请求占满CPU软中断 <code>si</code>）。</li>
</ul>
</li>
<li><strong>转为线程级分析</strong>：
<ul>
<li>执行 <code>top -H -p [PID]</code> 找出该进程内最耗CPU的线程ID（十进制）。（-H 显示详细线程，-p 指定PID）</li>
<li>将线程ID转换为16进制：<code>printf "%x\n" [线程ID]</code>。</li>
</ul>
</li>
<li><strong>抓线程栈</strong>：
<ul>
<li><strong>JAVA</strong>：使用 <code>jstack [PID] | grep -A 20 [16进制线程ID]</code> 直接查看该线程正在执行的代码堆栈，定位到具体类和方法。</li>
<li>使用 <code>perf top -p [PID]</code> 查看 CPU 在执行哪些内核/用户态函数，精细定位热点函数（如垃圾回收GC线程，或者正则计算）</li>
</ul>
</li>
<li><strong>系统调用</strong>：如果栈显示卡在 <code>read</code>/<code>write</code>/<code>futex</code> 等，使用 <code>strace -p [PID]</code> 跟踪进程的系统调用，看是否卡在某个内核调用上（如不断重复读写文件）。</li>
</ol>
<h3>服务器频繁发生 OOM Kill，但业务进程内存占用并不高，如何排查？</h3>
<p><strong>根因</strong>：通常是 <strong>内核 slab 内存（内核缓存）暴涨</strong> 或 <strong>某个进程疯狂申请匿名内存页（如内存泄漏）但被漏看</strong>。</p>
<p><strong>排查步骤</strong>：</p>
<ol>
<li><code>dmesg -T | grep -i "Out of memory"</code> 查看 OOM 发生时被杀的进程名和 <code>oom_score</code>。</li>
<li>查看系统内存细分：<code>cat /proc/meminfo</code> 关注 <code>Slab</code>、<code>SReclaimable</code>、<code>KernelStack</code>。若 <code>Slab</code> 异常大，用 <code>slabtop</code> 看是哪个内核对象占用（如 <code>dentry</code>、<code>inode_cache</code> 暴涨，说明文件句柄泄露）。</li>
<li>统计所有进程实际占用总和：<code>ps aux | awk '{sum+=$6} END {print sum/1024 " MB"}'</code>（单位 KB），若总和远小于总内存，差值就是内核占用了。</li>
<li>检查是否有 <strong>内存超卖</strong> 情况（如 K8s Pod 的 limits 设置过小，虽然物理内存够，但 cgroup 限制内 OOM）。</li>
<li><code>vmstat -s</code> 查看 <code>pages paged in/out</code>，确认是否有频繁换页加剧内存压力。</li>
</ol>
<h3>服务器负载（Load Average）飙升，但 CPU 使用率很低，什么原因？如何排查？</h3>
<p><strong>现象</strong>：<code>top</code> 看 <code>load average</code> 高达几十，但 <code>%us</code> 和 <code>%sy</code> 都很低。</p>
<p><strong>根因</strong>：<strong>大量进程处于不可中断睡眠状态（D 状态）</strong>，通常是因为 I/O 阻塞（磁盘/网络/外部设备）。</p>
<p><strong>排查步骤</strong>：</p>
<ol>
<li><code>top</code> 按 <code>D</code> 键排序，查看处于 <code>D</code> 状态的进程数量。</li>
<li><code>ps aux | awk '$8=="D" {print $2,$NF}'</code> 列出所有 D 状态进程。</li>
<li><code>strace -p [PID]</code> 看卡在哪个系统调用（通常是 <code>read/write</code> 磁盘或 <code>NFS</code>）。</li>
<li><code>iostat -x 1</code> 查看 <code>%util</code> 和 <code>await</code>，若接近 100% 或 <code>await</code> 极高，说明磁盘 I/O 已饱和。</li>
<li>若磁盘正常，检查是否有 NFS 挂载超时：<code>df -h</code> 卡住不动说明 NFS 服务端不可达。</li>
</ol>
<hr />
<h2>存储</h2>
<h3>磁盘空间报警，但 <code>du</code> 统计的总大小远小于磁盘已用空间，怎么回事？</h3>
<p><strong>根因</strong>：<strong>文件被删除但进程仍持有句柄</strong>，空间未真正释放；或 <strong>inode 耗尽</strong>。</p>
<p><strong>排查步骤</strong>：</p>
<ol>
<li>先检查 inode：<code>df -i</code>，如果 <code>IUse%</code> 100%，说明小文件太多，用 <code>find / -type f | wc -l</code> 定位目录。</li>
<li>若 inode 正常，查句柄泄露：<code>lsof +L1</code> 列出所有已删除但未释放句柄的文件（标记为 <code>(deleted)</code>）。</li>
<li>找到占用进程后：<code>ls -l /proc/[PID]/fd/ | grep deleted</code> 确认，重启该进程即可释放空间。</li>
<li>若不重启，可 <code>echo &gt; /proc/[PID]/fd/[fd号]</code> 清空文件（风险操作，需确认业务影响）。</li>
</ol>
<hr />
<h2>网络</h2>
<h3>客户端无法访问服务器8080端口，怎么全链路排查？</h3>
<p><strong>思路</strong>：从服务器着手，从<strong>物理层 → 网络层 → 传输层 → 应用层</strong>逐层递进。</p>
<p><strong>排查步骤</strong>：</p>
<ol>
<li><strong>服务器本机确认（物理层）</strong>：<code>netstat -tlnp | grep 8080</code> 或 <code>ss -tlnp | grep 8080</code>，确认进程是否在监听 <code>0.0.0.0</code> 还是 <code>127.0.0.1</code>（后者仅本机可访）。</li>
<li><strong>服务器防火墙（网络层）</strong>：<code>iptables -L -n | grep 8080</code> 和 <code>firewall-cmd --list-all</code>，检查是否有 DROP/REJECT 规则。</li>
<li><strong>连通性测试（传输层）</strong>：从客户端 <code>telnet [IP] 8080</code> 或 <code>nc -zv [IP] 8080</code> 看是否通；不通则逐跳 <code>traceroute</code> 看丢在哪。</li>
<li><strong>抓包分析（传输层）</strong>：<code>tcpdump -i any port 8080 -nn</code>，看三次握手是否完成（SYN→SYN+ACK→ACK）。若只有 SYN 无响应，说明服务端未回包；若回 RST，说明进程拒绝连接（可能是 backlog 满了或应用层拒绝）。</li>
<li><strong>应用层</strong>：<code>curl -v [IP]:8080</code> 看 HTTP 状态码，或查看应用日志是否有 <code>Connection refused</code> 异常。</li>
</ol>
<h3>无法 Ping 通目标主机/域名，如何排查？</h3>
<p><strong>思路</strong>：若域名不通 IP 通，直接定位到 DNS 故障。都不通则按<strong>分层排查</strong>思路。<strong>从下往上（物理层 → 网络层 → 传输层 → 应用层）</strong>，逐层缩小范围。</p>
<p><strong>简洁版</strong>：先确认本地网卡和 IP 配置，再 ping 网关判断是否局域网问题；若网关通，用 <code>traceroute</code> 定位丢包节点，同时检查本机防火墙；若域名不通 IP 通，定位到 DNS 故障，检查 <code>/etc/resolv.conf</code>，ping 域名服务器确定是域名服务器不可达还是解析问题；最后，考虑到很多生产环境出于安全考虑会主动禁 ICMP 协议，我会改用 <code>telnet</code>  <strong>端口探测</strong> 代替 ping。</p>
<h4>第一层：自查（排除本地低级错误/物理层）</h4>
<ol>
<li><strong>网络连通性</strong>：先 ping <code>127.0.0.1</code> 确认本机 TCP/IP 协议栈正常；再 ping 本机 IP（非 lo）确认网卡驱动正常。</li>
<li><strong>网卡状态</strong>：<code>ip a</code> 或 <code>ifconfig</code> 确认网卡是否 <code>UP</code>，<code>RUNNING</code>；检查网线/WiFi（物理层）。</li>
</ol>
<h4>第二层：网关与路由（局域网/网络层）</h4>
<ol>
<li><strong>ping 网关</strong>：ping 默认网关（<code>ip route | grep default</code> 查），<strong>网关不通</strong> → 问题在交换机/路由器/无线信号；<strong>网关通</strong> → 问题在外网或目标端。</li>
<li><strong>traceroute 追踪</strong>：
<ul>
<li>Linux：<code>traceroute -n [目标IP]</code>（加 <code>-n</code> 跳过 DNS 解析更快）。</li>
<li>Windows：<code>tracert -d [目标IP]</code>。</li>
<li>看在哪一跳 <strong>超时/星号</strong>，大概率是该节点路由丢包或防火墙拦截了 ICMP 包（很多云厂商安全组默认屏蔽 ICMP）。</li>
</ul>
</li>
</ol>
<h4>第三层：防火墙/安全组拦截（传输层/网络层）</h4>
<ol>
<li><strong>本机防火墙</strong>：
<ul>
<li><code>iptables -L -n -v | grep DROP</code> 看是否有 DROP 规则。</li>
<li><code>firewall-cmd --state</code>（CentOS 7+）或 <code>ufw status</code>（Ubuntu）。</li>
</ul>
</li>
<li>如果有目标主机访问权，检查：
<ul>
<li><strong>云安全组/ACL</strong>：登录云控制台，检查目标机器的 <strong>入方向安全组</strong> 是否放行了 ICMP 协议（允许 <code>type 8</code> 入站）。</li>
<li><strong>目标主机防火墙</strong>：目标主机如果开启 <code>iptables</code> 且 DROP 了 ICMP，本地 ping 会超时（但业务端口如 80/443 可能依然通，要区分）。</li>
</ul>
</li>
</ol>
<h4>第四层：DNS 解析问题（应用层）</h4>
<p><strong>现象：ping 域名不通，但 ping IP 通</strong></p>
<ul>
<li><strong>排查命令</strong>：
<ol>
<li><code>cat /etc/resolv.conf</code> 检查 DNS Server 配置（如 <code>114.114.114.114</code> 或内网 DNS）。</li>
<li>ping 域名服务器确定是域名服务器不可达还是解析问题</li>
<li>检查 <code>/etc/hosts</code> 是否写死了解析（被错误覆盖）。</li>
</ol>
</li>
<li>如果有装 <code>nslookup</code> <code>dig</code> 可以使用 <code>dig @8.8.8.8</code> 或 <code>dig [目标域名]</code> 排查</li>
</ul>
<h4>第五层：目标服务存活（终极确认）</h4>
<p><strong>注意</strong>：很多安全策略会 <strong>禁止 ICMP 协议（ping）</strong>，但 <strong>业务端口（如 80/443）是通的</strong>。</p>
<ul>
<li>此时应该用 <strong>端口探测</strong> 代替 ping：
<ul>
<li><code>telnet [目标IP] [端口]</code> 或 <code>nc -zv [目标IP] [端口]</code></li>
<li>如果端口通但 ping 不通 → <strong>属于正常现象</strong>（云厂商/运维策略屏蔽 ICMP）。</li>
<li>如果端口也不通 → 目标服务挂了，或目标主机本身无法访问。</li>
</ul>
</li>
</ul>
<hr />
<h2>综合</h2>
<h3>如何查看服务/进程是否正常运行？</h3>
<p><strong>原则</strong>：<strong>进程在 ≠ 服务正常。</strong> 要分层判断：<strong>状态 → 进程 → 端口 → 健康探针 → 日志</strong>。</p>
<ol>
<li><strong>托管状态</strong>：systemd 用 <code>systemctl is-active</code> + <code>is-enabled</code>，SysV 用 <code>service xxx status</code>；</li>
<li><strong>进程</strong>：没有托管就用 <code>pgrep -af</code> / <code>ps -ef</code>；</li>
<li><strong>端口</strong>：<code>ss -lntp</code> 确认监听；</li>
<li><strong>健康探针</strong>：<code>curl</code> 打业务健康接口，这才是真验证；</li>
<li><strong>日志</strong>：<code>journalctl -u xxx</code> 或日志文件，看有无报错。</li>
</ol>
<p><strong>只做①②不算查完，必须做到④才算真健康。</strong></p>
<p>&lt;details&gt;
&lt;summary&gt;参考 Shell 脚本&lt;/summary&gt;</p>
<pre><code>#!/bin/bash
# check_service.sh &lt;name&gt; [port] [health_url]
NAME="$1"; PORT="$2"; URL="$3"
[ -z "$NAME" ] &amp;&amp; { echo "Usage: $0 &lt;name&gt; [port] [health_url]"; exit 2; }
FAIL=0

# systemd 优先
if systemctl list-unit-files 2&gt;/dev/null | grep -q "^${NAME}\.service"; then
    echo "&gt;&gt;&gt; systemd unit: $NAME.service"
    systemctl is-active --quiet "$NAME" \
        &amp;&amp; echo "[OK] active" \
        || { echo "[FAIL] not active"; FAIL=1; }
    systemctl is-enabled --quiet "$NAME" \
        || echo "[WARN] not enabled (won't start on boot)"
    PID=$(systemctl show -p MainPID --value "$NAME")
else
    echo "&gt;&gt;&gt; fallback: process check"
    if pgrep -f "$NAME" &gt;/dev/null; then
        PID=$(pgrep -f "$NAME" | head -1)
        echo "[OK] process PID=$PID"
    else
        echo "[FAIL] process not found"; FAIL=1; PID=""
    fi
fi

# 端口
if [ -n "$PORT" ]; then
    ss -lntp 2&gt;/dev/null | grep -q ":$PORT " \
        &amp;&amp; echo "[OK] port $PORT listening" \
        || { echo "[FAIL] port $PORT not listening"; FAIL=1; }
fi

# 健康探针
if [ -n "$URL" ]; then
    CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "$URL")
    [ "$CODE" = "200" ] \
        &amp;&amp; echo "[OK] health $URL -&gt; 200" \
        || { echo "[FAIL] health $URL -&gt; $CODE"; FAIL=1; }
fi

[ $FAIL -eq 0 ] &amp;&amp; echo "=== HEALTHY ===" || echo "=== UNHEALTHY ==="
exit $FAIL
</code></pre>
<p>&lt;/details&gt;</p>
<hr />
<h2>恢复</h2>
<h3>rm -rf / 误操作或误删重要文件后，怎么恢复（前提是进程未重启）？</h3>
<p><strong>核心原理</strong>：Linux 中文件名和 inode 分离，文件删除只是移除目录项，只要进程还在打开该文件，数据块未被回收。</p>
<p><strong>恢复步骤</strong>（假设误删 <code>/var/log/nginx/access.log</code>）：</p>
<ol>
<li><strong>立即停止写入</strong>：<code>lsof | grep deleted</code> 找到仍持有该文件句柄的进程（nginx 的 worker 进程）。</li>
<li><strong>查看 fd 路径</strong>：<code>ls -l /proc/[PID]/fd/</code> 找到对应 fd 编号（如 4），显示为 <code>/var/log/nginx/access.log (deleted)</code>。</li>
<li><strong>重定向恢复</strong>：<code>cat /proc/[PID]/fd/4 &gt; /var/log/nginx/access.log</code> 将文件内容原样拷回。</li>
<li><strong>安全操作</strong>：通过 kill -HUP [PID] 重新加载配置让进程重新打开新文件句柄。</li>
<li><em>注：若进程已重启，则需依赖 extundelete 或 TestDisk 等工具做文件系统级别的恢复，但成功率取决于是否立即卸载分区并停止写入。</em></li>
</ol>
]]></content>
    <author><name>L7WD3-Xiao</name></author>
    <category term="Interview"/>
  </entry>
  <entry>
    <title>Linux 八股</title>
    <link href="https://blog-l7wd3.pages.dev/posts/interview/linux/" rel="alternate" type="text/html"/>
    <id>https://blog-l7wd3.pages.dev/posts/interview/linux/</id>
    <published>2026-08-02T00:00:00.000Z</published>
    <updated>2026-08-02T00:00:00.000Z</updated>
    <summary>速记用 Linux 八股</summary>
    <content type="html"><![CDATA[<h2>1. 基础命令</h2>
<p><strong>Linux常用文件操作命令？</strong>
ls、cd、pwd、mkdir、touch、cp、mv、rm、cat、vi/vim。</p>
<p><strong>如何查看系统版本？</strong>
<code>uname -a</code></p>
<p><strong>如何查找文件？</strong></p>
<pre><code># 按名称查找
find [路径] [匹配条件] [动作]
find / -name "filename" 
# 基于数据库快速查找（-i 忽略大小写; -n x 显示前x条）
locate -i "filename" 
</code></pre>
<p><strong>如何查看文件内容 / 如何查看大文件？</strong>
<code>cat</code>（全部）、<code>less/more</code>（分页）、<code>head/tail</code>（头尾）、<code>grep</code>（过滤）。</p>
<p><strong>less/more 区别？</strong></p>
<ul>
<li>more 只能向前翻页，加载大文件慢，无交互搜索；</li>
<li>less 支持双向滚动，按需加载性能优，内置搜索功能强，且在管道中可回溯，更适合大型文件和复杂操作。</li>
</ul>
<p><strong>如何查看当前目录？</strong>
<code>pwd</code> （Print Working Directory）</p>
<p><strong>如何管理用户？</strong></p>
<pre><code>useradd 选项 用户名
userdel 选项 用户名
usermod 选项 用户名
passwd 选项 用户名
</code></pre>
<p><strong>如何管理权限？</strong></p>
<ul>
<li><code>chmod</code> 改变文件/目录权限，三个数字owner/group/others，八进制表示法（读4 写2 执行1）</li>
<li><code>chown</code> 改变所有者，<code>chgrp</code> 改变所在组</li>
</ul>
<p><strong>目录权限含义？</strong></p>
<ul>
<li><strong>读（r）</strong>：允许列出目录中的文件和子目录名称（ls）。</li>
<li><strong>写（w）</strong>：允许在目录中创建、删除、重命名文件或子目录（会改变目录的文件列表）。</li>
<li><strong>执行（x）</strong>：允许进入目录（cd）并访问其中的文件或子目录。</li>
<li>注意：
<ul>
<li>只有 <strong>x</strong> 权限而无 <strong>r</strong> 权限，可以进入目录但无法列出文件名。</li>
<li>只有 <strong>r</strong> 权限而无 <strong>x</strong> 权限，可以看到文件列表但无法进入目录或访问文件内容。</li>
</ul>
</li>
</ul>
<p><strong>子目录有写权限但父目录没有，能修改子目录吗？</strong></p>
<table>
<thead>
<tr>
<th>操作</th>
<th>需要父目录权限</th>
<th>需要子目录权限</th>
</tr>
</thead>
<tbody>
<tr>
<td>修改子目录内文件</td>
<td>x（穿过）</td>
<td>w + x</td>
</tr>
<tr>
<td>在子目录内创建/删除文件</td>
<td>x</td>
<td>w + x</td>
</tr>
<tr>
<td>进入子目录 <code>cd</code></td>
<td>x</td>
<td>x</td>
</tr>
<tr>
<td>删除/重命名子目录本身</td>
<td>w + x</td>
<td>—</td>
</tr>
<tr>
<td>列出父目录内容</td>
<td>r</td>
<td>—</td>
</tr>
</tbody>
</table>
<p><strong>如何设置定时任务？</strong>
<code>crontab -e</code> 编辑当前用户 crontab 文件，<code>* * * * * command</code> 分/时/日/月/星期</p>
<p><strong>硬链接与软链接（符号链接）的底层区别？</strong>
硬链接是<strong>同一inode</strong>的多个入口，不能跨文件系统，不能指向目录，删除源不影响；软链接是<strong>新inode</strong>存储路径字符串，指向源文件路径，源删除则失效（高亮闪烁）。</p>
<p><strong>硬链接为什么不能跨文件系统？</strong></p>
<ul>
<li>硬链接指向的是 inode，而 inode 号只在当前文件系统内有意义。每个文件系统都有自己的 inode 表，另一个文件系统里的同一个 inode 号不代表同一个文件。</li>
<li>软链接保存的是路径字符串，解析时按路径重新查找目标文件，所以可以跨文件系统</li>
</ul>
<p><strong>如何查看文件被哪个进程占用？</strong>
<code>lsof filename</code></p>
<p><strong>Linux系统结构，主要目录作用？</strong></p>
<p>分层的树形结构，主要目录作用：</p>
<ul>
<li>/bin 存放常用的用户命令，如ls、cp等，普通用户和管理员均可使用。</li>
<li>/sbin 存放系统管理员使用的管理命令，如ifconfig、reboot等。</li>
<li>/etc 存放系统配置文件和子目录，例如网络配置文件/etc/hosts和DNS配置文件/etc/resolv.conf。</li>
<li>/home 用户的主目录，每个用户都有一个以用户名命名的子目录。</li>
<li>/root 超级用户（root）的主目录，仅供系统管理员使用。</li>
<li>/lib和/lib64 存放系统的共享库文件，类似于Windows的DLL文件，几乎所有程序都依赖这些库。</li>
<li>/usr 存放用户应用程序和文件，包括/usr/bin（用户命令）、/usr/sbin（管理员命令）和/usr/local（本地安装的软件）。</li>
<li>/var 存放动态数据，如日志文件（/var/log）、邮件（/var/mail）和临时文件（/var/tmp）。</li>
<li>/tmp 存放临时文件，系统重启后会清空。</li>
<li>/dev 存放设备文件，例如硬盘（/dev/sda）和终端（/dev/tty）。</li>
<li>/proc 虚拟文件系统，存储内核和进程信息，例如CPU信息（/proc/cpuinfo）和内存信息（/proc/meminfo）。</li>
<li>/sys 类似于/proc，用于存储设备和内核模块的信息。</li>
<li>/boot 存放启动相关文件，包括内核和引导加载器配置文件。</li>
<li>/media和/mnt 用于挂载外部设备，如U盘和光驱。</li>
<li>/opt 存放第三方软件或可选应用程序。</li>
<li>/run 存储系统运行时的信息，系统重启后会清空。</li>
</ul>
<p><strong>如何查看文件类型？</strong></p>
<p><code>ls -l</code> 看第一列</p>
<ul>
<li>-：普通文件</li>
<li>d：目录文件</li>
<li>b：块设备文件（一般位于<code>/dev</code>，以块为单位进行读写的特殊设备文件，通常用于硬盘、光盘和U盘等存储设备）</li>
<li>c：字符设备文件（一般位于<code>/dev</code>，允许用户按<strong>字节顺序</strong>与设备交互，例子包括键盘、鼠标、串口和终端设备）</li>
<li>s：套接字文件</li>
<li>p：管道文件</li>
<li>l：链接文件（软链接）</li>
</ul>
<p><strong>su命令作用、参数？</strong></p>
<p>作用：切换用户。主要参数：</p>
<ul>
<li><code>- / -l / --login</code>	模拟完整登录，加载目标用户环境</li>
<li><code>-c "cmd"</code>	以目标用户执行一条命令后退出</li>
<li><code>-s shell</code>	指定使用的 shell</li>
<li><code>-m / -p</code>	保留当前环境变量（不重置）</li>
<li><code>-g group</code>	指定主组</li>
<li><code>-G group</code>	指定附加组</li>
</ul>
<p><strong>su user和su - user区别？</strong></p>
<table>
<thead>
<tr>
<th><code>su user</code></th>
<th><code>su - user</code></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td>环境变量</td>
<td>保留当前用户环境</td>
<td>重新加载目标用户环境</td>
</tr>
<tr>
<td>工作目录</td>
<td>不变（当前目录）</td>
<td>切换到目标用户 home</td>
</tr>
<tr>
<td>PATH</td>
<td>沿用原用户</td>
<td>重置为目标用户的 PATH</td>
</tr>
<tr>
<td>shell 类型</td>
<td>非登录 shell</td>
<td>登录 shell</td>
</tr>
<tr>
<td>读取配置</td>
<td>不读 <code>.bash_profile</code> / <code>.profile</code></td>
<td>读取 <code>.bash_profile</code> / <code>.profile</code> / <code>.bashrc</code></td>
</tr>
<tr>
<td>本质</td>
<td>只切换身份</td>
<td>完整登录模拟</td>
</tr>
</tbody>
</table>
<p>用 <code>su user</code> 跑脚本可能因 PATH 不对找不到命令；生产脚本建议用 <code>su - user -c "cmd"</code>。</p>
<h2>2. 进程管理</h2>
<h3>进程排查</h3>
<p><strong>如何查看所有运行中的进程？</strong>
<code>ps aux</code> 或 <code>ps -ef</code>。</p>
<p><strong>如何实时监控进程和系统资源？</strong>
<code>top</code>（实时刷新），关注%CPU、%MEM、load average。</p>
<p><strong>top命令顶部输出含义？</strong></p>
<ul>
<li><strong>Tasks（进程任务）</strong>：<code>total</code> 总进程数；<code>running</code> 正在运行；<code>sleeping</code> 睡眠；<code>stopped</code> 暂停；<code>zombie</code> 僵尸进程（重点警戒值，不为0需关注）。</li>
<li><strong>%Cpu(s)</strong>：<code>us</code> 用户态占用；<code>sy</code> 内核态占用；<code>id</code> 空闲；<code>wa</code> I/O等待；<code>hi</code>/<code>si</code> 硬/软中断。</li>
<li><strong>MiB Mem（物理内存）</strong>：<code>total</code> 总内存；<code>free</code> 完全空闲；<code>used</code> 已分配（含缓存）；<code>buff/cache</code> 缓冲区+页缓存（Linux会把空闲内存拿来当缓存，所以看可用内存应看 <code>available</code> 列）。</li>
</ul>
<p><strong>数值异常 &amp; 常见原因</strong></p>
<table>
<thead>
<tr>
<th>现象</th>
<th>可能原因</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>us</code> 高</td>
<td>应用死循环、计算密集、代码 bug、QPS 突增</td>
</tr>
<tr>
<td><code>sy</code> 高</td>
<td>系统调用频繁、上下文切换多、锁竞争、网络/磁盘驱动问题</td>
</tr>
<tr>
<td><code>wa</code> 高</td>
<td>磁盘 I/O 瓶颈、NFS 慢（Network FileSystem）、数据库慢查询、swap 抖动</td>
</tr>
<tr>
<td><code>si</code> 高</td>
<td>网络软中断集中、单核跑满、网卡多队列未配置、DDoS</td>
</tr>
<tr>
<td><code>hi</code> 高</td>
<td>硬件中断频繁、网卡/磁盘中断风暴</td>
</tr>
<tr>
<td><code>st</code> 高</td>
<td>虚拟机被宿主机超卖、邻居抢占资源（云主机常见）</td>
</tr>
<tr>
<td><code>ni</code> 高</td>
<td>有人用 <code>nice</code> 跑后台任务</td>
</tr>
<tr>
<td><code>id</code> 低但负载不高</td>
<td>可能是 <code>st</code> 或 <code>wa</code> 吃掉了，需结合 <code>vmstat</code> / <code>iostat</code> 看</td>
</tr>
</tbody>
</table>
<p>排查套路</p>
<pre><code>us 高 → top 看进程 → perf/火焰图
sy 高 → vmstat 看 cs/in → strace
wa 高 → iostat -x 1 → 看 %util / await
si 高 → mpstat -P ALL 1 → 看是否单核 → 调 RPS/RSS/多队列
st 高 → 找云厂商 / 换宿主机
</code></pre>
<p><strong>ps aux / top命令各个关键字段含义？</strong></p>
<ul>
<li><strong>PID</strong>：进程ID</li>
<li><strong>USER</strong>：所属用户</li>
<li><strong>PR/NI</strong>：优先级与 nice 值</li>
<li><strong>VIRT/RES/SHR</strong>：虚拟内存、常驻内存、共享内存</li>
<li><strong>%CPU/%MEM</strong>：CPU/内存占用率</li>
<li><strong>TIME+</strong>：累计 CPU 时间</li>
<li><strong>COMMAND</strong>：命令名或完整命令行</li>
</ul>
<p><strong>CPU硬中断和软中断区别？</strong>
硬中断由外部硬件触发，软中断由软件指令触发，它们在操作系统中分别处理实时事件和系统调用任务。</p>
<h3>进程清理</h3>
<p><strong>kill与kill -9的区别？</strong>
kill默认发SIGTERM（可捕获、可清理）；kill -9发SIGKILL（强制终止，不可捕获）。</p>
<p><strong>kill 命令中信号编号 1、2、3、9、15、18、19 的具体区别？</strong></p>
<ul>
<li><strong>1 (SIGHUP)</strong>：终端挂起，常用于让进程<strong>重读配置</strong>（如Nginx -s reload本质发此信号）。</li>
<li><strong>2 (SIGINT)</strong>：键盘中断（Ctrl+C），进程可捕获清理。</li>
<li><strong>3 (SIGQUIT)</strong>：键盘退出（Ctrl+\），会<strong>产生core dump</strong>用于调试。</li>
<li><strong>9 (SIGKILL)</strong>：强制终止，不可捕获/忽略，直接由内核回收，可能造成资源泄漏或脏数据。</li>
<li><strong>15 (SIGTERM)</strong>：默认终止信号，可捕获，优雅停机首选（超时后补9）。</li>
<li><strong>18 (SIGCONT)</strong>：继续运行被暂停的进程。</li>
<li><strong>19 (SIGSTOP)</strong>：暂停进程（Ctrl+z，不可捕获，类似外部挂起）。</li>
<li>补充：Ctrl+d 表示输入结束，不发送信号，若输入为空，则返回0，类似EOF</li>
</ul>
<p><strong>僵尸进程是什么？如何清理？</strong>
子进程退出后父进程未调用wait()回收，进程描述符残留在进程表中。无法kill僵尸本身，需kill父进程（PPID），父进程死后僵尸被init收养并清理。</p>
<p><strong>孤儿进程是什么？</strong>
父进程先退出，子进程被init（PID 1）收养，由init负责回收，通常无害。</p>
<h3>进程间七种通信方式</h3>
<ol>
<li><strong>管道（Pipe）</strong>：父子进程间单向传输，数据无格式。</li>
<li><strong>命名管道（FIFO）</strong>：通过文件节点，允许无亲缘关系进程通信。</li>
<li><strong>信号（Signal）</strong>：异步通知机制（如 <code>kill -9</code>）。</li>
<li><strong>消息队列（Message Queue）</strong>：内核维护的消息链表，有格式。</li>
<li><strong>共享内存（Shared Memory）</strong>：<strong>最快</strong>的方式，映射同一块物理内存，需配合信号量使用。</li>
<li><strong>信号量（Semaphore）</strong>：本质上是一个计数器，用于进程/线程间的同步与互斥，而非传输数据。</li>
<li><strong>套接字（Socket）</strong>：用于跨网络或本机不同进程间的通信（TCP/UDP）。</li>
</ol>
<h2>3. 内存与存储</h2>
<h3>基础</h3>
<p><strong>Buffer和Cache的区别？</strong>
Buffer（Buffers）：块设备元数据缓存（inode、分区信息）。Cache（Page Cache）：文件内容缓存，读写都经过它。</p>
<p><strong>手动drop_caches有什么风险？</strong>
<code>echo 3 &gt; /proc/sys/vm/drop_caches</code>会清空Page Cache，导致CPU飙升重建索引、磁盘I/O风暴（Cache Miss），生产环境慎用。</p>
<p><strong>如何查看内存使用情况？</strong>
<code>free -h</code>；<code>vmstat</code> 查看虚拟内存统计。</p>
<h3>虚拟地址</h3>
<p><strong>缺页中断与内存映射</strong></p>
<ul>
<li><strong>内存映射（mmap）</strong>：将磁盘文件或设备数据直接映射到进程的虚拟地址空间，使程序可以像访问内存一样访问文件数据，由内核在后台同步。</li>
<li><strong>懒加载（Lazy Loading）</strong>：数据只有在首次访问时才会触发缺页异常，由内核将对应文件页加载到物理内存。</li>
<li><strong>缺页异常（Page Fault）</strong>：当进程试图访问一个尚未加载到物理内存的虚拟页面时。
<ul>
<li><strong>次要异常/软缺页</strong>：数据已在 page cache，只需建立页表映射即可，很快。</li>
<li><strong>主要异常/硬缺页</strong>：数据不在 page cache，必须触发磁盘I/O将其读入内存（很慢，影响性能）。</li>
</ul>
</li>
</ul>
<h3>虚拟内存</h3>
<p><strong>什么是Swap？什么时候会用到？</strong></p>
<ul>
<li><strong>定义</strong>：Swap（交换分区）是硬盘上划出的一块空间。当物理内存（RAM）不足时，内核会将暂时不用的内存数据换出到Swap中，从而释放物理内存给当前活跃的进程。</li>
<li><strong>触发时机</strong>：
<ul>
<li><strong>物理内存耗尽</strong>（剩余可用内存低于阈值）。</li>
<li><strong>内存回收压力大</strong>（内核的 <code>kswapd0</code> 线程被频繁唤醒）。</li>
<li><strong>主动冷数据迁移</strong>（即使内存还有余量，内核也会根据 <code>vm.swappiness</code> 参数决定是否把不活跃的页换出去，默认值通常是60）。</li>
</ul>
</li>
</ul>
<h3>OOM</h3>
<p><strong>什么是OOM？什么会导致OOM？</strong></p>
<ul>
<li><strong>定义</strong>：<strong>Out Of Memory</strong>，即内存溢出。当系统物理内存和Swap全部耗尽，内核无法再为任何进程分配内存时，会触发 <strong>OOM Killer</strong> 机制，它会根据评分（<code>oom_score</code>）挑选一个进程强制杀掉（发送 SIGKILL 信号）以释放内存，保障操作系统存活。</li>
<li><strong>常见诱因</strong>：
<ul>
<li><strong>内存泄漏</strong>（程序申请的内存没有释放，积少成多耗尽资源）。</li>
<li><strong>不合理的大对象/大查询</strong>（如数据库一次性加载上亿行数据到内存）。</li>
<li><strong>流量突增</strong>（并发请求数暴增，导致创建大量线程或缓存对象）。</li>
<li><strong>内存分配速度超过GC/回收速度</strong>（特别是在 Java 或 Python 应用中）。</li>
</ul>
</li>
</ul>
<p><strong>Linux 下 OOM Killer 的运作机制？</strong>
系统内存耗尽时触发，根据 <code>oom_score</code>（受 <code>/proc/[pid]/oom_adj</code> 调节）打分，分数最高者被杀。可通过 <code>echo -17 &gt; /proc/[pid]/oom_adj</code> 禁止被杀（仅对特定关键进程）。</p>
<h3>ulimit</h3>
<p><strong>ulimit 什么作用？</strong>
ulimit 是 Linux 系统中用于控制 Shell 程序资源限制的内建命令。允许用户查看或设置当前会话的资源限制，包括文件大小、内存使用、CPU 时间等。</p>
<p><strong>Linux 下 ulimit 的 soft limit 和 hard limit 区别？</strong>
soft 当前生效值（进程可主动修改，最多涨到hard），hard 系统硬上限（只有 root 能改）。生产常见坑：<code>nofile</code>（最大文件句柄数）未调大导致"Too many open files"。</p>
<h3>LVM</h3>
<p><strong>LVM是什么？基本概念？</strong>
Logical Volume Manager，在线调整分区大小无需停机。三层：PV（物理卷）→ VG（卷组）→ LV（逻辑卷）。</p>
<p><strong>LVM常用操作命令？</strong>
<code>pvcreate</code>创建物理卷，<code>vgcreate</code>创建卷组，<code>lvcreate</code>创建逻辑卷，<code>vgextend</code>扩展卷组，<code>lvextend</code>扩展逻辑卷。</p>
<p><strong>LVM 在线扩容完整流程？（以虚拟机扩容为例）</strong></p>
<p><strong>最佳实践</strong>：生产环境一般不动现有磁盘/分区，而是作为新磁盘加入。
<strong>流程</strong>：虚拟层扩盘 → OS层执行rescan → 确认lsblk → PV/VG/LV操作 → xfs_growfs/resize2fs → df验证</p>
<ul>
<li><strong>虚拟化层操作</strong>：在虚拟机管理平台（如VMware/vCenter）中加新磁盘。</li>
<li><strong>OS层发现新设备</strong>：扫描SCSI总线 <code>echo "- - -" &gt; /sys/class/scsi_host/host0/scan</code> 或重启（生产慎用）</li>
<li><strong>确认识别新容量</strong>：用 <code>lsblk</code> （list block）确认新容量已识别。</li>
<li><strong>PV阶段</strong>：<code>pvcreate /dev/sdb</code></li>
<li><strong>VG阶段</strong>：<code>vgextend vg_data /dev/sdb</code></li>
<li><strong>LV阶段</strong>：<code>lvextend -l +100%FREE /dev/vg_data/lv_data</code></li>
<li><strong>文件系统层</strong>：<code>resize2fs</code>（ext4）或 <code>xfs_growfs</code>（xfs）。</li>
<li><strong>确认扩容</strong>：<code>df -h</code> 验证扩容完成</li>
<li>注意：缩减（reduce）极其危险且需离线，生产禁用。</li>
</ul>
<p>**传统扩容方法 / 给机器加一块磁盘，如何识别？**OS 层执行 rescan → 确认 lsblk → PV/VG/LV 操作 → mkfs → mount → df 验证 → fstab 永久挂载 → mount -a 验证。详细说明可参考 <a href="https://blog-l7wd3.pages.dev/lvm/">一文讲通 LVM 与传统扩容的异同</a>。</p>
<h3>磁盘与I/O</h3>
<p><strong>如何查看磁盘空间？</strong>
<code>df -h</code> 查看分区使用；<code>du -sh *</code> 查看目录大小。</p>
<p><strong>如何排查磁盘I/O瓶颈？</strong>
<code>iostat -x 1</code> 查看I/O负载；<code>iotop</code> 查看进程I/O。</p>
<h2>4. 网络</h2>
<p><strong>如何查看网络连接和监听端口？</strong>
<code>netstat -tunlp</code> 或 <code>ss -tunlp</code>（ss更快）。</p>
<p><strong>netstat 常用参数 / -tunlp 分别代表什么</strong></p>
<ul>
<li><strong>-a (--all)</strong>: 显示所有的网络连接和监听端口。</li>
<li><strong>-t (--tcp)</strong>: 仅显示TCP连接。</li>
<li><strong>-u (--udp)</strong>: 仅显示UDP连接。</li>
<li><strong>-n (--numeric)</strong>: 显示IP地址和端口号，而不是尝试解析域名和服务名。</li>
<li><strong>-l (--listen)</strong>: 显示监听中的端口</li>
<li><strong>-p (--programs)</strong>: 显示哪个进程正在使用哪个套接字或网络端口。</li>
<li><strong>-x (--unix)</strong>: 仅显示Unix域套接字连接。</li>
<li><strong>-r (--route)</strong>: 显示路由表。</li>
<li><strong>-i (--interfaces)</strong>: 显示网络接口信息。</li>
<li><strong>-s (--statistics)</strong>: 显示网络协议的统计数据。</li>
</ul>
<p><strong>如何抓包/监听80端口数据？</strong></p>
<p><code>tcpdump -i eth0 port 80 -w capture.pcap</code>。</p>
<ul>
<li>-i：指定接口</li>
<li>-w：保存到文件</li>
</ul>
<p><strong>如何测试网络连通性？</strong>
<code>ping</code> 测试连通性；<code>telnet</code>/<code>nc</code> 测试端口。</p>
<p><strong>如何追踪数据包？</strong>
<code>tracert</code> 追踪数据包经过的路由路径，一层层显示路径。</p>
<p><strong>常见的TCP内核参数调优（sysctl）？</strong>
<code>net.ipv4.tcp_tw_reuse=1</code>（允许复用TIME_WAIT）、<code>net.ipv4.tcp_fin_timeout=30</code>（缩短FIN超时）、<code>net.core.somaxconn=65535</code>（监听队列最大长度，Nginx配合backlog）。</p>
<p><strong>如何配置IP？</strong></p>
<pre><code># 持久化配置
# Ubuntu 系统，可以调整 /etc/network/interface
iface eth0 inet static
    address 192.168.1.100
    netmask 255.255.255.0
    gateway 192.168.1.1
# 然后重启网络服务
sudo systemctl restart networking
</code></pre>
<p><strong>如何配置DNS？</strong></p>
<pre><code>sudo sh -c 'echo "nameserver 8.8.8.8" &gt;&gt; /etc/resolv.conf'
</code></pre>
<p><strong>如何查看IP/DNS？</strong></p>
<ul>
<li>IP 看 <code>ip addr</code> 的 eth0，DNS 看 <code>cat /etc/resolv.conf</code>  下的 nameserver</li>
<li>DNS 常规为8.8.8.8，阿里云ECS默认100.100.2.136或138</li>
</ul>
<h2>5. 系统管理与启动</h2>
<p><strong>Linux系统从开机到登录的启动流程？</strong>
上电复位 → BIOS/UEFI自检 → 引导加载程序（GRUB，GNU GRand Unified <strong>Bootloader</strong>）→ 内核加载 → 挂载根文件系统 → init进程（Systemd）→ 运行级别（SysV）/目标（systemd） → 用户登录。</p>
<p><strong>Load Average是什么？怎么看？</strong>
系统平均负载，表示单位时间内处于运行或不可中断状态的进程数。<code>top</code>或<code>uptime</code>查看三个时间点（1/5/15分钟）。</p>
<p><strong>如何排查服务器性能瓶颈？</strong>
top看整体负载 → vmstat看CPU/内存/swap → iostat看磁盘I/O → netstat/iftop看网络。</p>
<p><strong>什么是服务？服务和进程区别？</strong></p>
<p>进程是操作系统资源调度的单位；服务是"被托管、可启停、可自愈、有生命周期管理"的长期运行进程，在后台持续运行、独立于用户会话，通常称为“守护进程”（Daemon）。</p>
<table>
<thead>
<tr>
<th>维度</th>
<th>进程 Process</th>
<th>服务 Service</th>
</tr>
</thead>
<tbody>
<tr>
<td>定义</td>
<td>正在运行的程序实例</td>
<td>被 init/systemd/管理器托管的长期进程</td>
</tr>
<tr>
<td>管理方</td>
<td>内核</td>
<td>systemd / SysV / supervisor / k8s 等</td>
</tr>
<tr>
<td>生命周期</td>
<td>手动起、手动死</td>
<td>有 start/stop/restart/reload</td>
</tr>
<tr>
<td>开机自启</td>
<td>不保证</td>
<td>可 enabled</td>
</tr>
<tr>
<td>崩溃自愈</td>
<td>不会</td>
<td>可自动重启（Restart=always）</td>
</tr>
<tr>
<td>状态查询</td>
<td><code>ps</code> / <code>pgrep</code></td>
<td><code>systemctl status</code> / <code>supervisorctl status</code></td>
</tr>
<tr>
<td>日志</td>
<td>stdout / 文件</td>
<td>journald / 统一日志</td>
</tr>
<tr>
<td>依赖管理</td>
<td>无</td>
<td><code>After=</code> / <code>Requires=</code></td>
</tr>
<tr>
<td>典型例子</td>
<td><code>bash</code>、<code>vim</code>、一个 <code>python xx.py</code></td>
<td><code>nginx.service</code>、<code>sshd.service</code></td>
</tr>
</tbody>
</table>
<p><strong>Systemd与Init的区别？</strong></p>
<ul>
<li>Systemd并行启动、按需启动、支持socket/DBus激活；</li>
<li>SysV init串行启动，依赖 <code>/etc/init.d</code> 目录下的脚本。</li>
</ul>
<p><strong>如何查看服务？</strong></p>
<p>Systemd 服务：</p>
<pre><code>systemctl --type=service
systemctl status xxx 看具体服务
systemctl start/restart/stop
</code></pre>
<p>SysV 服务：</p>
<pre><code>service --status-all
service xxx status
</code></pre>
<h2>6. 其他/操作系统</h2>
<p><strong>进程/线程区别？</strong></p>
<ul>
<li>内存分配
<ul>
<li>进程：资源分配的基本单位，拥有独立的内存空间</li>
<li>线程：CPU调度的基本单位，共享进程的资源</li>
</ul>
</li>
<li>切换开销
<ul>
<li>进程：大，有独立代码和数据段</li>
<li>线程：小，只维护独立的堆栈和程序计数器</li>
</ul>
</li>
<li>稳定性
<ul>
<li>进程：隔离性更强，某个进程崩溃不会影响其他进程</li>
<li>线程：弱，线程共享进程资源，崩溃可能导致整个进程异常</li>
</ul>
</li>
<li>安全性
<ul>
<li>进程：有独立内存空间，安全性高</li>
<li>线程：共享内存空间，存在数据竞争和线程安全问题，需要同步/互斥机制</li>
</ul>
</li>
<li>补充：MySQL 多线程，PostgreSQL/Oracle 多线程</li>
</ul>
<p><strong>并发/并行区别？</strong></p>
<ul>
<li><strong>并发（Concurrency）</strong>：<strong>逻辑上的“同时”</strong>。单核CPU通过时间片轮转快速切换，看起来像是同时处理多个任务，本质是交替执行（强调结构，处理多件事）。</li>
<li><strong>并行（Parallelism）</strong>：<strong>物理上的“同时”</strong>。多核CPU在同一时刻真正地执行多个任务（强调执行，同时做多件事）。</li>
</ul>
<p><strong>死锁的四个条件</strong></p>
<table>
<thead>
<tr>
<th>条件</th>
<th>含义</th>
<th>对应到 Java 或数据库</th>
</tr>
</thead>
<tbody>
<tr>
<td>互斥</td>
<td>某个资源同一时刻只能被一个执行单元占用</td>
<td><code>synchronized</code> 锁对象、行级排他锁、独占文件锁</td>
</tr>
<tr>
<td>请求与保持（占有并等待）</td>
<td>已经拿着一部分资源，同时继续等待其他资源</td>
<td>线程持有 <code>resource1</code> 时继续申请 <code>resource2</code></td>
</tr>
<tr>
<td>非抢占</td>
<td>资源不能被外部强行拿走，只能由持有者释放</td>
<td>Java 内置锁不能被另一个线程直接剥夺</td>
</tr>
<tr>
<td>循环等待</td>
<td>等待关系形成闭环</td>
<td>线程 1 等线程 2，线程 2 又等线程 1</td>
</tr>
</tbody>
</table>
<p><strong>用户态与内核态是什么？如何切换？</strong></p>
<ul>
<li>
<p><strong>用户态</strong>：用户态下的程序只能访问受限的内存，不能直接访问硬件设备。用户态程序的执行权限较低，不能执行特权指令。</p>
</li>
<li>
<p><strong>内核态</strong>：内核态下的程序可以访问所有的内存和硬件设备，具有最高的执行权限。内核态程序可以执行特权指令，如设置时钟、内存管理等。</p>
</li>
</ul>
<p>用户态和内核态之间的切换是通过<strong>系统调用</strong>、<strong>异常</strong>和<strong>中断</strong>来实现的。</p>
<ul>
<li><strong>系统调用</strong>：用户态程序通过系统调用请求操作系统提供的服务，从而切换到内核态。例如，内存分配函数*malloc()<em>会调用</em>sbrk()*系统调用，从而切换到内核态。</li>
<li><strong>异常</strong>：当CPU在执行用户态程序时发生异常（如缺页异常），会触发从用户态切换到内核态。</li>
<li><strong>中断</strong>：当外围设备完成用户请求的操作后，会向CPU发出中断信号，CPU会暂停当前用户态程序的执行，转而执行中断处理程序，从而切换到内核态。</li>
</ul>
<p><strong>听说过 CFS 吗？</strong>
CFS（Completely Fair Scheduler，完全公平调度器）用于Linux系统中普通进程的调度。vruntime，红黑树结构，有利于交互式进程。<a href="https://www.mianshiya.com/question/1863459885228146689">听说过 CFS 吗？（Linux ） - 面试鸭 | 2026最新面试题+详细答案解析</a></p>
<p><strong>ps 常用参数</strong></p>
<ul>
<li>-A：显示所有进程。</li>
<li>-e：与 -A 效果相同，显示所有进程。</li>
<li>-a：显示所有终端上的进程，包括其他用户的进程。</li>
<li>-x：显示没有控制终端的进程。</li>
<li>-u：以用户为主的格式显示进程信息。</li>
<li>-f：显示完整格式的进程信息，包括 UID、PPID 等。</li>
<li>-l：以长格式显示进程信息。</li>
<li>-o：自定义输出格式，例如 ps -o pid,cmd 仅显示进程 ID 和命令。</li>
<li>--sort：按指定字段排序，例如 --sort=-%cpu 按 CPU 使用率降序排列。</li>
</ul>
]]></content>
    <author><name>L7WD3-Xiao</name></author>
    <category term="Interview"/>
  </entry>
  <entry>
    <title>【408】计算机网络</title>
    <link href="https://blog-l7wd3.pages.dev/posts/interview/408-network/" rel="alternate" type="text/html"/>
    <id>https://blog-l7wd3.pages.dev/posts/interview/408-network/</id>
    <published>2026-08-01T00:00:00.000Z</published>
    <updated>2026-08-01T00:00:00.000Z</updated>
    <summary>面试速记版 计算机网络 八股</summary>
    <content type="html"><![CDATA[<blockquote>
<p>部分内容来源于 <a href="https://javaguide.cn/cs-basics/">Java-Guide</a></p>
</blockquote>
<h2>基础</h2>
<h3>分层模型</h3>
<p><strong>OSI七层模型与TCP/IP四层模型分别是什么？</strong></p>
<ul>
<li>OSI七层：物理层、数据链路层、网络层、传输层、会话层、表示层、应用层。</li>
<li>TCP/IP四层：网络接口层、网际层、传输层、应用层。</li>
</ul>
<p><strong>每层功能？</strong></p>
<ul>
<li>应用层：为用户提供服务</li>
<li>表示层：数据表示（转换、加解密、解压缩）</li>
<li>会话层：管理应用程序之间的会话</li>
<li>传输层：主机通信（TCP/UDP）</li>
<li>网络层：路由、寻址、IP</li>
<li>数据链路层：帧编码、纠错、MAC地址</li>
<li>物理层：网卡、光纤</li>
</ul>
<h3>计算机网络体系是怎么工作的？</h3>
<p>三个<strong>核心原则</strong>：</p>
<ol>
<li><strong>分层解耦（分工协作）</strong>
<ul>
<li>每一层只干自己的活，不越界。比如传输层只关心端到端可靠性，不关心数据内容；应用层只关心业务逻辑，不关心数据怎么走光纤。</li>
<li><strong>好处</strong>：各层独立升级（比如物理层从网线换光纤，应用层代码丝毫不用改）。</li>
</ul>
</li>
<li><strong>对等通信（虚拟通信）</strong>
<ul>
<li>虽然数据是物理上从上往下发、从下往上收，但<strong>逻辑上</strong>，发送端的“网络层”觉得是在和接收端的“网络层”直接沟通（实际上中间隔着下层）。这种“对等层之间的协议”决定了整个体系的稳定性。</li>
</ul>
</li>
<li><strong>封装与解封装（带信封装）</strong>
<ul>
<li><strong>发送方</strong>：数据每往下一层，就包一层“信封”（头部 Header）；</li>
<li><strong>接收方</strong>：每往上一层，就拆一层“信封”。</li>
</ul>
</li>
</ol>
<p>整个体系就是“数据不动，协议动”——用户数据本身没变过，每一层都在它外面加上了自己的控制信息（头部），直到变成比特流传输出去。</p>
<h2>网络层</h2>
<ul>
<li><strong>IP（Internet Protocol，网际协议）</strong>：主要作用是定义数据包的格式、对数据包进行路由和寻址，以便它们可以跨网络传播并到达正确的目的地。</li>
<li><strong>ARP（Address Resolution Protocol，地址解析协议）</strong>，用于在局域网（LAN）中进行主机通信，是一种用于将网络层的地址转换为数据链路层地址的重要网络协议。
在TCP/IP网络中，ARP的主要功能是将IP地址转换为MAC地址。</li>
<li><strong>ICMP（Internet Control Message Protocol，互联网控制报文协议）</strong>：一种用于传输网络状态和错误消息的协议，常用于网络诊断和故障排除。例如，<strong>Ping 工具</strong>就使用了 ICMP 协议来测试网络连通性。</li>
<li><strong>NAT（Network Address Translation，网络地址转换协议）</strong>：NAT 协议的应用场景如同它的名称——网络地址转换，应用于内部网到外部网的地址转换过程中。具体地说，在一个小的子网（局域网，LAN）内，各主机使用的是同一个 LAN 下的 IP 地址，但在该 LAN 以外，在广域网（WAN）中，需要一个统一的 IP 地址来标识该 LAN 在整个 Internet 上的位置。</li>
</ul>
<h2>传输层</h2>
<p><strong>TCP/UDP区别？</strong></p>
<table>
<thead>
<tr>
<th>特性</th>
<th>TCP</th>
<th>UDP</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>连接性</strong></td>
<td>面向连接</td>
<td>无连接</td>
</tr>
<tr>
<td><strong>可靠性</strong></td>
<td>可靠</td>
<td>不可靠（尽力而为）</td>
</tr>
<tr>
<td><strong>状态维护</strong></td>
<td>有状态</td>
<td>无状态</td>
</tr>
<tr>
<td><strong>传输效率</strong></td>
<td>较低</td>
<td>较高</td>
</tr>
<tr>
<td><strong>传输形式</strong></td>
<td>面向字节流</td>
<td>面向数据报（报文）</td>
</tr>
<tr>
<td><strong>头部开销</strong></td>
<td>20 - 60 字节</td>
<td>8 字节</td>
</tr>
<tr>
<td><strong>通信模式</strong></td>
<td>点对点（单播）</td>
<td>单播、多播、广播</td>
</tr>
<tr>
<td><strong>常见应用</strong></td>
<td>HTTP/HTTPS, FTP, SMTP, SSH</td>
<td>DNS, DHCP, SNMP, TFTP, VoIP, 视频流</td>
</tr>
</tbody>
</table>
<p><strong>TCP三次握手的过程？</strong>
客户端发送SYN → 服务端回复SYN+ACK → 客户端回复ACK，连接建立。</p>
<p><strong>TCP四次挥手的过程？</strong>
主动方发送FIN → 被动方回复ACK → 被动方发送FIN → 主动方回复ACK。</p>
<p><strong>TIME_WAIT状态的作用？</strong>
确保最后一个ACK能到达对方，让迟到的报文段有足够时间被丢弃。可通过<code>net.ipv4.tcp_tw_reuse</code>允许复用。</p>
<p><strong>CLOSE_WAIT状态是什么？成因？</strong>
被动关闭方收到FIN后回复ACK进入的状态，表示等待应用层调用close()。堆积说明程序没正确关闭连接。</p>
<p><strong>TCP 如何保证可靠传输？</strong>（重传/滑动窗口/拥塞控制）<a href="https://javaguide.cn/cs-basics/network/tcp-reliability-guarantee.html#tcp-%E5%A6%82%E4%BD%95%E4%BF%9D%E8%AF%81%E4%BC%A0%E8%BE%93%E7%9A%84%E5%8F%AF%E9%9D%A0%E6%80%A7">TCP 如何保证可靠传输？重传、滑动窗口与拥塞控制详解 | JavaGuide</a></p>
<h2>应用层</h2>
<p><strong>应用层常见协议？基于TCP还是UDP？</strong></p>
<ul>
<li><strong>HTTP（Hypertext Transfer Protocol，超文本传输协议）</strong>：是一种用于传输超文本和多媒体内容的应用层协议，主要为 Web 客户端与服务器之间的通信而设计。HTTP/1.x 和 HTTP/2 通常基于 TCP，HTTP/3 则运行在基于 UDP 的 QUIC 之上。</li>
<li><strong>SMTP（Simple Mail Transfer Protocol，简单邮件发送协议）</strong>：基于 TCP 协议，是一种用于发送电子邮件的协议。注意 ⚠️：SMTP 协议只负责邮件的发送，而不是接收。要从邮件服务器接收邮件，需要使用 POP3 或 IMAP 协议。</li>
<li><strong>POP3/IMAP（邮件接收协议）</strong>：基于 TCP 协议，两者都是负责邮件接收的协议。IMAP 协议是比 POP3 更新的协议，它在功能和性能上都更加强大。IMAP 支持邮件搜索、标记、分类、归档等高级功能，而且可以在多个设备之间同步邮件状态。几乎所有现代电子邮件客户端和服务器都支持 IMAP。</li>
<li><strong>FTP（File Transfer Protocol，文件传输协议）</strong>：基于 TCP 协议，是一种用于在计算机之间传输文件的协议，可以屏蔽操作系统和文件存储方式。注意 ⚠️：FTP 是一种不安全的协议，因为它在传输过程中不会对数据进行加密。建议在传输敏感数据时使用更安全的协议，如 SFTP。</li>
<li><strong>Telnet（远程登陆协议）</strong>：基于 TCP 协议，用于通过一个终端登陆到其他服务器。Telnet 协议的最大缺点之一是所有数据（包括用户名和密码）均以明文形式发送，这有潜在的安全风险。这就是为什么如今很少使用 Telnet，而是使用一种称为 SSH 的非常安全的网络传输协议的主要原因。</li>
<li><strong>SSH（Secure Shell Protocol，安全的网络传输协议）</strong>：基于 TCP 协议，通过加密和认证机制实现安全的访问和文件传输等业务</li>
<li><strong>RTP（Real-time Transport Protocol，实时传输协议）</strong>：通常基于 UDP 协议，但也支持 TCP 协议。它提供了端到端的实时传输数据的功能，但不包含资源预留存、不保证实时传输质量，这些功能由 WebRTC 实现。</li>
<li><strong>DNS（Domain Name System，域名管理系统）</strong>：通常基于 UDP 协议（端口 53），用于解决域名和 IP 地址的映射问题。当响应数据过大或进行区域传送时会改用 TCP。</li>
</ul>
<p><strong>HTTP状态码分类？</strong>
1xx信息、2xx成功、3xx重定向、4xx客户端错误、5xx服务端错误。</p>
<p><strong>长连接与短连接的区别？</strong>
短连接每次请求后断开；长连接保持连接复用（HTTP Keep-Alive），减少握手开销。</p>
<p><strong>DNS解析的查询顺序？</strong>
浏览器缓存 → 系统Hosts文件 → 本地DNS服务器 → 根域/顶级域递归查询。</p>
<p><strong>HTTPS与HTTP的区别？</strong>
HTTPS在HTTP下加TLS/SSL层，提供加密传输和身份认证。</p>
<p><strong>HTTP/1.1 和 HTTP/2.0 有什么区别？</strong></p>
<ul>
<li><strong>多路复用（Multiplexing）</strong>：HTTP/2.0 在同一连接上可以同时传输多个请求和响应（可以看作是 HTTP/1.1 中长链接的升级版本），互不干扰。HTTP/1.1 则使用串行方式，每个请求和响应都需要独立的连接，而浏览器为了控制资源会有 6-8 个 TCP 连接的限制。这使得 HTTP/2.0 在处理多个请求时更加高效，减少了网络延迟和提高了性能。</li>
<li><strong>二进制帧（Binary Frames）</strong>：HTTP/2.0 使用二进制帧进行数据传输，而 HTTP/1.1 则使用文本格式的报文。二进制帧更加紧凑和高效，减少了传输的数据量和带宽消耗。</li>
<li><strong>队头阻塞</strong>：HTTP/2 引入了多路复用技术，允许多个请求和响应在单个 TCP 连接上并行交错传输，解决了 HTTP/1.1 应用层的队头阻塞问题，但 HTTP/2 依然受到 TCP 层队头阻塞的影响。</li>
<li><strong>头部压缩（Header Compression）</strong>：HTTP/1.1 支持 <code>Body</code> 压缩，<code>Header</code> 不支持压缩。HTTP/2.0 支持对 <code>Header</code> 压缩，使用了专门为 <code>Header</code> 压缩而设计的 HPACK 算法，减少了网络开销。</li>
<li><strong>服务器推送（Server Push）</strong>：HTTP/2.0 支持服务器推送，可以在客户端请求一个资源时，将其他相关资源一并推送给客户端，从而减少了客户端的请求次数和延迟。而 HTTP/1.1 需要客户端自己发送请求来获取相关资源。</li>
</ul>
<p><strong>HTTP请求从输入URL到页面展示的完整流程？</strong></p>
<ol>
<li><strong>浏览器解析 URL 并检查缓存</strong>：浏览器解析 URL 的各组成部分，并检查 HTTP 缓存（强缓存、协商缓存）是否已有该资源的有效副本。</li>
<li><strong>DNS 解析</strong>：浏览器通过 DNS 协议（Hosts→本地DNS→递归查询），获取域名对应的 IP 地址。</li>
<li><strong>建立传输连接</strong>：HTTP/1.1 和 HTTP/2 通常先通过 TCP 三次握手建立连接；HTTP/3 则建立基于 UDP 的 QUIC 连接。</li>
<li><strong>建立安全通道（HTTPS）</strong>：HTTP/1.1 和 HTTP/2 通常在 TCP 建连后进行 TLS 握手；HTTP/3 在 QUIC 建连过程中集成 TLS 1.3，完成密钥协商和服务器身份验证。</li>
<li><strong>发送 HTTP 请求</strong>：浏览器在连接上向服务器发送 HTTP 请求报文，请求获取网页内容。</li>
<li><strong>服务器处理并返回响应</strong>：服务器收到请求后处理并返回 HTTP 响应报文。</li>
<li><strong>浏览器解析与渲染</strong>：浏览器解析 HTML、CSS，执行 JavaScript，并加载页面中引用的其他资源（图片、字体等）。</li>
<li><strong>连接管理</strong>：页面加载完成后，连接根据 keep-alive 策略复用或关闭。</li>
</ol>
<p>从OSI七层模型的角度看每一层做了什么：</p>
<p>第一阶段：发送请求（应用层 → 物理层）</p>
<ul>
<li><strong>应用层</strong>：浏览器解析出协议（HTTP/HTTPS）、域名（如 <code>www.baidu.com</code>）和路径。</li>
<li><strong>表示层</strong>：对数据进行“翻译”和“加密”。如果是 HTTPS，在此层进行 TLS/SSL 握手和加解密；同时将字符编码统一（如 UTF-8）。</li>
<li><strong>会话层</strong>：建立、管理、维持本次通信的会话连接（Session）。虽然 HTTP 是无状态的，但此层负责确保断线重连时能续传。</li>
<li><strong>传输层</strong>：通过 DNS 解析得到目标 IP 后，选择 <strong>TCP</strong> 或 <strong>UDP</strong> 协议。将数据切成数据段（Segment），并封装上<strong>源端口</strong>和<strong>目标端口</strong>（80/443）。</li>
<li><strong>网络层</strong>：添加 <strong>IP 头部</strong>（源 IP 和 目标 IP）。通过路由算法查询下一跳地址，决定数据包走哪条路到达服务器。</li>
<li><strong>数据链路层</strong>：添加 <strong>MAC 地址</strong>（源 MAC 和下一跳网关的 MAC）。通过 ARP 协议（地址解析协议）获取下一跳路由器的物理地址。</li>
<li><strong>物理层</strong>：将数据帧转化为二进制 0/1 的<strong>比特流</strong>，通过网线、光纤或 Wi-Fi 的电磁波发送出去。</li>
</ul>
<p>第二阶段：接收响应（物理层 → 应用层）<strong>从下往上</strong>逐层拆包（剥洋葱）：</p>
<ul>
<li><strong>物理层</strong>：接收电信号/光信号，还原成比特流。</li>
<li><strong>数据链路层</strong>：检查 MAC 地址是否匹配，拆掉 MAC 头部，交由上层。</li>
<li><strong>网络层</strong>：检查 IP 地址是否匹配，拆掉 IP 头部，交由上层。</li>
<li><strong>传输层</strong>：拆掉 TCP 头部，<strong>回传 ACK 确认包</strong>（三次握手的最后一步），并把乱序的数据包重排好，交给应用。</li>
<li><strong>会话/表示层</strong>：解密（HTTPS）、解压数据。</li>
<li><strong>应用层</strong>：解析 HTTP 响应报文（状态码 200、HTML 内容），浏览器开始渲染引擎<strong>解析 HTML、加载 CSS/JS</strong>，最终呈现出你看到的网页。</li>
</ul>
]]></content>
    <author><name>L7WD3-Xiao</name></author>
    <category term="Interview"/>
  </entry>
</feed>
