
项目问题分析文件上传速度异常缓慢现象测试中发现文件上传速度远低于预期通过 btop 观察系统资源使用情况发现磁盘SSDIO 满载实际速度甚至不如 HDD。原因分析初步推测原因为某处文件上传相关代码触发大量小写 IO排查文件上传相关代码后发现上传分块接收每收到一块就向 MySQL 写入一次上传进度received一个文件被拆成大量分块每一块都触发一次数据库写操作产生海量无意义的随机小写 IO导致吞吐骤降。解决方案调整进度持久化的时机传输过程中仅在内存中更新进度改为文件连接关闭传输结束或中断时一次性将 received 写入数据库。这样把上传过程中的数据库写入从每块一次降为每次连接一次消除了传输路径上的大量随机小写操作上传速度恢复正常。后续实验室局域网环境测试中文件传输速度依旧较慢现象在实验室局域网环境下进行大文件传输测试速度仅约几到十几 MB/s远低于预期。排查过程由于此前已解决了一个导致传输慢的代码问题且使用本地回环地址127.0.0.1连接测试时速度很快因此首先怀疑网络因素。利用两台电脑分别配备 2.5G 与 1000M 有线网卡用网线直连做对照实验排除局域网中其他干扰测试发现传输速度正常达到 1000Mbps 的水平说明问题不在服务端与应用程序本身而在局域网链路。随后使用 iperf3 对测试局域网测速并用 iw 相关命令查看该无线局域网信息发现该网络为 5GHz 频段但信道宽度仅为 20 MHz。关键输出如下# iperf3 单流测速上行 [ 5] 0.00-10.00 sec 83.8 MBytes 70.2 Mbits/sec 112 sender [ 5] 0.00-10.27 sec 83.4 MBytes 68.1 Mbits/sec receiver # iperf3 单流测速下行 -R [ 5] 0.00-10.04 sec 115 MBytes 96.1 Mbits/sec 0 sender [ 5] 0.00-10.00 sec 112 MBytes 93.9 Mbits/sec receiver # iw dev当前无线接口所在信道 Interface wlp0s20f3 channel 40 (5200 MHz), width: 20 MHz, center1: 5200 MHz # iw dev wlp0s20f3 link链路速率与信号 signal: -49 dBm rx bitrate: 258.0 MBit/s HE-MCS 10 HE-NSS 2 HE-GI 0 tx bitrate: 243.7 MBit/s HE-MCS 10 HE-NSS 2 HE-GI 1分析及推测20 MHz 的信道宽度将无线链路的 PHY 速率上限限制在约 258 Mbit/sWi-Fi 6、2 空间流而 TCP 实际吞吐通常只有 PHY 速率的约 1/3~1/2即约 70~100 Mbit/s约 9~12 MB/s与 iperf3 实测及文件传输速度高度吻合。信道宽度对速率的影响十分直接在调制方式与空间流数不变的情况下信道宽度从 20 MHz 提升到 40/80 MHzPHY 速率上限将相应翻倍/翻四倍而当前仅 20 MHz 的信道把速率限制在了约 9~12 MB/s。因此推测该 5GHz 20MHz 信道配置是文件传输慢的主要原因。答辩问题回顾红黑树红黑树是一种自平衡的二叉搜索树是 epoll 的一个关键数据结构它通过颜色标记和旋转操作来保持树的平衡确保基本操作查找、插入、删除的时间复杂度始终为 O(log n)即使在最坏情况下也不会退化为链表。性质每个节点要么是红色要么是黑色。根节点是黑色。叶子节点是黑色。红色节点的两个子结点都是黑色。从任意节点到其所有后代叶子节点的路径上黑色节点的数量相同。红黑树在 epoll 中有重要的作用红黑树提供稳定的 O(log n) 性能使 epoll 的控制操作增删改达到了极高的效率。TCP协议的细节下面是 TCP 三次握手的过程客户端 服务器 |-------- SYN ------------| |------- SYNACK ---------| |-------- ACK ------------|在这个过程中 ACK 有两种可能的丢失情况第二握手时 SYN ACK 丢失客户端侧会认为自己第一次握手时发送的的 SYN 丢失会重新发送 SYN。服务器侧在发出 SYNACK 后也会启动重传定时器如果未收到客户端的 ACK会重发 SYNACK。双方各自重传直到成功或一方超时放弃。第三次握手时客户端发送的 ACK 丢失在客户端视角发出 ACK 后会认为自己进入 ESTABLISHED 状态可以开始发送数据不会重新发送 ACK。在服务端视角未收到 ACK还处于 SYN_RCVD 状态不会认为连接已经建立会重新发送 SYNACK直到收到 ACK或超时。此时大致会出现三种情况(1). 客户端直接发数据隐含 ACK服务器收到该包后立即进入 ESTABLISHED 状态连接建立成功。(2). 客户端一直未发数据服务器重新发送 SYNACK客户端收到重复 SYNACK 后回应 ACK连接建立成功。(3). 客户端一直未发数据且一直未收到服务器重新发送的 SYNACK或服务器一直没有收到客户端发送的 ACK服务器超时关闭连接客户端后续发数据时触发错误处理。QUICQUIC 是基于 UDP 开发的传输层协议旨在解决 TCP 在现代网络环境下的一些固有问题如握手延迟、队头阻塞等并已成为 HTTP/3 的底层传输协议。核心特性快速握手内置加密多路复用连接迁移可选前向纠错改进的拥塞控制IPIP 地址是网络中每台设备的逻辑标识主要作用唯一标识主机在互联网中每台设备至少有一个 IP 地址用于区分不同的设备定位与路由路由器根据 IP 地址中的网络部分决定如何转发数据包确保数据能到达正确的目标主机。支持网络分层IP 地址分为网络号标识子网和主机号标识子网内的设备便于划分子网和管理。子网子网是 IP 网络中一个逻辑上独立的较小网络。通过将一个大网络划分为多个子网可以更好地管理资源、隔离广播流量、提高安全性和灵活性。子网掩码子网掩码是一个 32 位的二进制数用来区分一个 IP 地址中哪部分是网络号哪部分是主机号。其中网络号用于标识子网本身。主机号用于标识子网内的具体设备。将 IP 地址与子网掩码进行按位与运算结果就是子网号。Linux定时器Linux 提供了将定时能力与文件描述符结合的机制 timerfd它把定时器抽象成一个文件描述符定时器到期时该描述符变为可读因此可以直接接入 epoll 事件循环统一管理无需单独的定时器线程。核心特性到期可读定时器到期后 timerfd 变为可读读取其 8 字节计数即可获得到期次数。周期/单次可以分别配置首次到期时间与周期实现一次性或周期性定时。单调时钟不受系统时间修改影响保证定时稳定。B树和B树B树是一种多路平衡查找树每个节点可以存储多个关键字并拥有多个子节点相比二叉树大幅降低了树的高度从而减少了查找时的磁盘 IO 次数。B树是 B 树的变体是数据库索引的常用底层结构。核心特性多路平衡一个节点可存多个关键字树高更低磁盘 IO 更少。数据存储位置不同B树在所有节点都存数据B树只在叶子节点存数据内部节点仅存索引键可容纳更多键、树高更低。叶子节点有序链表B树的叶子节点通过指针相连且按键有序范围查询和顺序遍历非常高效。查询稳定B树的查询必须走到叶子节点每次查询的 IO 次数基本一致性能稳定。B树的这些特性使其非常适合作为数据库索引结构例如 MySQL 的 InnoDB 引擎索引基于B树实现。乐观锁乐观锁假设并发冲突很少发生因此读取数据时并不加锁而是在提交更新时检查数据是否被其他事务修改过若已被修改则更新失败并重试。适用场景适合读多写少、冲突概率低的场景若冲突频繁会导致大量更新失败与重试反而降低性能。与悲观锁相比乐观锁避免了对共享资源的长时间锁定更适用于高并发读、写入极少或冲突率低等场景。C智能指针weak_ptrweak_ptr 是一种不增加引用计数的智能指针它指向 shared_ptr 管理的对象但本身不拥有该对象用于打破 shared_ptr 的循环引用或作为安全的观察者访问对象。不增加引用计数weak_ptr 不延长对象的生命周期不影响对象的析构时机。不阻止对象释放当最后一个 shared_ptr 被销毁时对象随之释放weak_ptr 变为悬空状态。可检查对象是否存活通过 lock() 获取一个 shared_ptr若对象已销毁则返回空。主要用途打破 shared_ptr 循环引用如父子对象互相持有安全地观察对象状态。零拷贝零拷贝是减少数据在发送过程中在内核态与用户态之间多次拷贝的技术从而降低 CPU 开销和上下文切换提升 IO 性能。以文件发送到网络为例传统方式需要经过 磁盘-内核缓冲区-用户态缓冲区-内核 Socket 缓冲区-网卡 的多次拷贝。mmap()将文件直接映射到进程地址空间用户态可像访问内存一样读写文件省去了 read() 将数据从内核拷贝到用户态、以及 write() 再拷回内核的过程但数据发送到 Socket 时仍需一次从内核缓冲区到 Socket 缓冲区的拷贝。sendfile()专用于文件-Socket的发送场景数据直接从内核的页缓存发送到 Socket全程不经过用户态无需在用户与内核之间反复拷贝也减少了上下文切换。区别mmap 适合需要读写文件的场景仍需一次内核内拷贝sendfile 适合文件直发网络的场景内核内完成全部转发效率更高。哈希表底层哈希表是一种基于数组哈希函数的高效查找结构。通过哈希函数将键映射为数组下标理想情况下查找、插入、删除的时间复杂度均为 O(1)。哈希函数将任意类型的键通过哈希算法计算出一个整数再对数组长度取模得到下标。好的哈希函数应尽量让键均匀分布减少冲突。冲突解决不同键可能映射到同一下标即发生哈希冲突常见有两种方式链地址法拉链法数组每个槽位挂一个链表冲突的键挂到同一链表上实现简单、扩容方便C 的 unordered_map 即采用此方式。开放寻址法发生冲突时按一定规则线性探测、二次探测等在数组中继续寻找空闲位置无需额外内存但删除和扩容较复杂。扩容当元素数量与桶数量的比值负载因子超过阈值时哈希表会申请更大的数组并将所有元素重新哈希放入避免冲突过多导致性能退化。平均时间复杂度为 O(1)但若哈希函数设计不佳或负载因子过高冲突严重时可能退化为 O(n)。thread_localthread_local 是 C 的线程局部存储关键字用于声明每个线程各有一份的变量。同一变量在不同线程中各自独立、互不影响线程销毁时该副本自动释放。特性每线程独立副本不同线程访问的是各自的变量彼此隔离。线程级生命周期变量随线程的创建而初始化随线程的销毁而释放。天然无锁因为是线程私有数据访问时无需加锁避免了锁竞争。