标签: Linux

  • 迁移博客到 VPS

    在上篇文章优化了博客网站的性能过后,我对页面加载速度仍感到不太满意,Cloudflare 显示大部分页面仍需要等待接近 2 秒才能完成响应。一想到我之前在 cPanel 上执行了一些比较重的操作,网页的加载速度就会下降,所以可能之前的 Host Provider 并没有给我分配多少资源来折腾,这大概率就是网页访问速度慢的重要原因之一。

    为了验证这个观点,我尝试在 Homelab 中的 N100 主机上部署了一个 WordPress,并将博客网站复制了一份到这台服务器上。我发现在缺少 Cloudflare 缓存的情况下,就算是通过 Cloudflare Connector 从公网访问家庭内网中的网站都比我线上的博客网站要快 😂。

    确认了是 Host Provider 的性能问题后,我决定将博客网站迁移到我的一台 VPS 上,那么这篇文章的重点终于开始了。

    我的 Word Press 实例一开始就是通过 Host Provider 建立的,之前在不同的 Provider 之间迁移时,主要也是通过管理面板上提供的一键迁移功能完成操作。但我没想到的是,这些一键迁移功能所涉及的插件都是付费的,之前之所以能顺畅的使用,大概率是 Host Provider 已经为这些功能付过了钱。

    虽然插件市场中也有免费的备份恢复插件,但要么是备份出来之后不让恢复,要么是恢复备份会出错, 更离谱的是有些插件备份出来的数据竟然是加密的。

    不过好在管理面板提供了一个简单的 Full Backup 功能,可以将 WordPress 目录连同整个数据库 Dump 一起打包。

    在我的 VPS 上,出于方便我一直没安装任何面板,唯一的管理工具是 CapRover ,你将其简单的理解为自部署版 heroku。他自带的 WordPress 的部署模板,只要简单替换一下变量,就可以快速启动一个 WordPress 容器:

    在这里,需要注意的 WordPress 的 Docker 镜像默认不包含 WP-CLI,需要使用 6.9.0-cli 标签才能部署带 WP-CLI 的镜像。

    由于 CapRover 的限制,部署好的 WordPress 只能通过 CapRover 的 Nginx 网关访问,而 Nginx 网关已经处理了 TLS,所以转发到 WordPress 的流量只有 HTTP。

    如果你从零使用 Docker 创建 WordPress 实例,上述的情况不会影响你,但如果使用从其他地方复制过来的 WordPress 实例,就会遇到无限重定向的问题。具体表现为访问 /wp-admin 时,会响应当前重定向到当前 URL 。这时,就需要在 wp-config.php 的最顶部,添加下面的代码:

    if (strpos($_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') !== false)
       $_SERVER['HTTPS']='on';
    else
       $_SERVER['HTTPS']='off';

    后记

    上面就是迁移博客过程中比较大的坑点了,其实就算花 $9.9 买一个一键迁移插件可能也无法避开。但如果我在原来的 Provider 上加钱升级配置,可能也就不需要这次的迁移了。根据基本的经济学原理,我今天下午工作的价值就是下一档配置的 3 年订阅费,大概 $300,又省到钱了~

  • 解决 Qsirch 无法搜索文件夹的问题

    This entry is part 7 of 7 in the series homelab 历险记

    最近在整理家里的一些电子文件的时候,发现 QNAP 提供的 Qsirch 搜索功能无法搜索 /Public 目录下的文件了,不管是文件名还是文件内容,都查询不到搜索结果。

    在网络上搜索了一番,Reddit 中有网友提到可以在 Multimedia Console 中重建索引,然后去 Appcenter 中重启 Qsirch 应用。不过我试下来,发现并没有什么卵用。

    折腾半天,感觉实在没办法了,只能求助客服,在等待客服回复的过程中,突然想到,我之前通过 NFS 共享过 /Public 目录,当时似乎从 Docker 中访问时,弄乱过目录的访问权限。所以,会不会是因为目录权限错误,导致 Qsirch 无法对文件建立索引?

    为了验证我的猜想,SSH 登录 QNAP 后,发现 /Public 目录的权限果然有问题,该目录属于一个未知的 group,手动修改 Owner 为 admin/administrators 后,Qsirch 开始运行建立索引的后台任务了。等待索引建立完成,Qsirch 已经能够检索 /Public 目录的文件了~

  • N100 小主机遭遇 NVMe 硬盘故障:一次系统的诊断与反思

    This entry is part 6 of 7 in the series homelab 历险记

    我的 N100 小主机最近陷入了无响应的困境,一次系统级的卡顿,让我不得不深入诊断底层硬件问题。最终,故障源头被锁定在一块出现问题的 NVMe 硬盘上。

    故障的发生与初步判断

    故障最初表现为:系统在日常运行中,当需要读取某些特定数据时,NVMe 硬盘会陷入无响应状态。这直接导致相关进程卡滞在 D 状态(不可中断睡眠状态)。当这类进程累积过多时,整个操作系统陷入僵局,无法响应任何其他请求。

    首次尝试解决,我选择了重启。然而,系统通常能坚持不到十分钟,便会在 CPU 占用率不高的正常负载情况下再次卡死。这表明问题并非偶发,而是存在深层原因。

    精准定位故障源头

    为了区分软件与硬件故障,我启动了 SystemRescue Live CD。在 Live CD 环境下,我开始使用 smartctl 工具检查硬盘的健康状况。令人惊讶的是,仅仅执行 smartctl -x /path/to/disk 这样的详细健康状况检查命令,就能稳定复现系统卡死的故障。

    这一现象直接排除了操作系统层面的复杂问题,将故障的矛头清晰地指向了 NVMe 硬盘本身。

    为了进一步验证,我将这块故障硬盘从 N100 小主机上取下,安装到另一台笔记本电脑上进行测试。结果,同样的卡死问题依然能够重现。这最终确认了硬盘是故障的根本原因,而非 N100 主机的 PCIe 接口或其他主机侧问题。

    数据与保修:选择与取舍

    这块 NVMe 硬盘尚未过保,这意味着我可以直接申请售后换新。然而,更换新硬盘的代价是硬盘上的所有数据。这块硬盘主要保存着我部署在 Docker 上的应用数据以及一些从网络下载的影片。

    幸运的是,Docker 应用的关键数据每天都通过 restic 进行备份。因此,更换新硬盘对我来说,影响微乎其微,核心应用数据和配置都得以安全保存。至于影片文件,虽然会丢失,但并非不可恢复。

    突发状况的应急预案

    这次故障也促使我思考关键服务的紧急应对措施。我的 N100 宿主机上运行着一个软路由虚拟机,这意味着一旦宿主机故障,全屋的网络将随之瘫痪。

    发生故障时,我的临时解决方案是:使用一根网线连接器,将 N100 主机上原本连接 LAN 与 WAN 的网线直连起来,并让家中的光猫直接充当网关。这样可以在最短时间内恢复主要的网络连接,保障家庭网络的正常运行。这为后续的故障排查与修复争取了宝贵的时间。

    重要的经验教训

    尽管 Docker 应用数据有定期备份,但这次经历也暴露了一个疏漏:我却忽略了软路由虚拟机磁盘映象(VM Disk Image)的备份。软路由的配置和运行状态都包含在这个映象文件中,一旦丢失,重建起来会相当耗时。

    好在这次运气眷顾,通过 Live CD 成功抢救回了这份重要的虚拟机映象文件,避免了额外的麻烦。

  • 外接显示器 EDID 损坏如何处理

    This entry is part 4 of 4 in the series 机械革命无界 15XPro

    上周五,我的外置显示器突然在一次热插拔后无法被笔记本电脑识别 4K 分辨率了,在排查了笔记本电脑、线材问题后,基本可以确定是显示器本身出了问题。

    从内核日志来看,系统无法读取显示器 EDID 信息:

    [  174.781913] EDID has corrupt header
    [  174.781923] amdgpu 0000:65:00.0: [drm] *ERROR* EDID checksum invalid.
    [  178.681809] EDID has corrupt header
    [  178.681819] amdgpu 0000:65:00.0: [drm] *ERROR* EDID checksum invalid.
    [  232.892047] EDID has corrupt header
    [  232.892057] amdgpu 0000:65:00.0: [drm] *ERROR* EDID checksum invalid.
    [  235.172080] EDID has corrupt header
    [  235.172091] amdgpu 0000:65:00.0: [drm] *ERROR* EDID checksum invalid.
    [  623.720346] EDID has corrupt header
    [  623.720359] amdgpu 0000:65:00.0: [drm] *ERROR* EDID checksum invalid.
    [  625.949958] EDID has corrupt header
    [  625.949969] amdgpu 0000:65:00.0: [drm] *ERROR* EDID checksum invalid.

    EDID 是一份显示器能力识别数据,包含生产厂商、支持的分辨率、刷新率等信息。EDID 一般存储在显示器的芯片内,如果显示器有多个输入端口,那么每个端口上的 EDID 通常会被分别存储在独立的芯片内。当电脑连接显示器时,会通过 DDC 读取显示器上存储的 EDID 信息,从而能够让电脑正确识别显示器的型号并输出显示器支持的视频信号。

    当 EDID 存储器损坏导致电脑无法读取时,我们可以主动向操作系统提供某个视频输出端口的 EDID 信息。这样以来,解决问题的思路就导向了寻找一份可用的 EDID 文件。

    幸好我的显示器有多个输入端口——一个 HDMI 2.0 以及 两个 HDMI 1.4 ,可以尝试读取 HDMI 1.4 端口的 EDID 文件,然后用 EDID 编辑器为其添加 4K@60Hz 的输出设置,作为 HDMI 2.0 接口的 EDID 文件。

    读取显示器上的 EDID 文件

    在 ArchLinux 上,可以使用 eedid-tool (AUR) 读取并保存 EDID 信息。

    # 扫描已连接的显示器 i2c bus
    $ eedid-tool -s
    Bus 11 has EDID, mfg BOE
    1 EDID(s) found.
    
    $ eedid-tool -r -b 11 > ./edid_hdmi1.4.bin

    由于 HDMI 1.4 不支持 4K 60Hz 的输入规格,所以需要手动尝试为其添加。

    修改 EDID 文件

    为了魔改刚刚导出的 EDID 文件,我使用了 AW EDID Editor,这款软件可以完美地通过 Bottles (Wine)运行,打开 AW EDID Editor 后,选择要修改 EDID 文件,稍等片刻后,你将看到这样的界面。

    点击 CEA Extension 下面的 Add new CEA Block,选择 Detailed Timing Descriptor。

    这时你会看到一个输入框全是 0 的配置界面。

    接着点击 Use predefined format,选择希望添加的分辨率规格即可,对于我的显示器来说,需要选择 3840×2160 60Hz。

    当配置界面被填充满各种数据后,就可以点击左上角的 File 菜单,保存修改后的 EDID 文件。

    设置自动加载 EDID 文件

    首先需要将修改后的 EDID 文件保存到 /usr/lib/firmware/edid/ 目录下,Linux 固件相关的二进制文件通常都放在这里。

    接着,我采用了下面的 systemd 文件来自动加载 EDID:

    [Unit]
    Description=Override HDMI EDID for Koios Monitor
    After=sys-kernel-debug.mount
    Requires=sys-kernel-debug.mount
    
    [Service]
    Type=oneshot
    # 关键:告诉 systemd 即使脚本跑完了,服务也被视为"active",这样才能执行 stop
    RemainAfterExit=yes
    
    # --- 启动时执行 (应用 EDID) ---
    # 1. 写入自定义 EDID
    ExecStart=/bin/sh -c 'cat /usr/lib/firmware/edid/koios_hdmi2.0.bin > /sys/kernel/debug/dri/1/HDMI-A-1/edid_override'
    # 2. 触发热插拔,让内核应用更改
    ExecStart=/bin/sh -c 'echo 1 > /sys/kernel/debug/dri/1/HDMI-A-1/trigger_hotplug'
    
    # --- 停止时执行 (撤销修改) ---
    # 1. 清空 override 文件
    ExecStop=/bin/sh -c 'echo -n reset > /sys/kernel/debug/dri/1/HDMI-A-1/edid_override'
    # 2. 再次触发热插拔,让内核读取显示器原本的真实 EDID
    ExecStop=/bin/sh -c 'echo 1 > /sys/kernel/debug/dri/1/HDMI-A-1/trigger_hotplug'
    
    [Install]
    WantedBy=multi-user.target

    由于我经常只会用到这一台显示器,所以还设置了自动启动服务,当需要连接其他显示器时,就可以通过停止这个服务,让操作系统直接读取该显示器提供的 EDID 。

    参考链接

    在解决这个问题的过程中,参考了 Gemini 3 Pro 生成的回复,不过其中有不少事实性错误,仍应参考 Arch Wiki 的文档:

    https://wiki.archlinux.org/title/Kernel_mode_setting#Forcing_modes_and_EDID

    另外,为了防止笔记本内置显示器的 EDID 在未来也发生了无法读取的情况,以及方便其他拥有跟我同型号显示器的人需要 EDID 文件,我将这些 EDID 上传到了 IPFS 网络:

  • How to switch GitHub CLI account automatically

    Working with multiple GitHub accounts on the same machine can be tricky, but we can automate account switching when changing workspaces. Here’s my solution using Fish Shell and Direnv.

    Prerequisites

    Install direnv, this handy tool automatically loads environment variables when you cd into directories.

    The Setup

    Add this function to your Fish Shell configuration:

    function __gh_auth_switch_gh_account --on-variable GH_ACCOUNT
      if test -n "$GH_ACCOUNT"
        gh auth switch --user "$GH_ACCOUNT"
      end
    end

    How it works

    Let’s break down the snippet:

    1. --on-variable tells Fish Shell to run this function when the variable GH_ACCOUNT changes value.
    2. test -n $GH_ACCOUNT returns true if the length of GH_ACCOUNT is non-zero.
    3. gh auth switch --user "$GH_ACCOUNT" switches the active account to $GH_ACCOUNT.

    For example, if we need to switch to gh-user-1 under path/to/company/ . We can add a .envrc file under path/to/company

    export GH_ACCOUNT=gh-user-1

    When we cd into path/to/company/project1, direnv will set the GH_ACCOUNT variable automatically, and the callback function __gh_auth_switch_gh_account will be invoked by the Fish Shell to make gh-user-1 active.

  • How to simulate hard disconnection for websocket

    虽然 Chrome/Firefox 在开发者工具中提供了离线模式,但是这个功能无法中断已经跟服务端建立起来的 WebSocket 连接。

    如果你跟我一样使用 Linux 系统,那么就可以借助 iptables 命令来模拟连接中断的情况:

    # 中断所有访问 localhost:3001 的连接
    sudo iptables -I INPUT 1 -p tcp -i lo --dport 3001 -j DROP
    
    # 恢复连接
    sudo iptables -D INPUT -p tcp -i lo --dport 3001 -j DROP

    如果你发现上面的命令不生效,那么可能是因为你的浏览器在通过 IPv6 地址访问本地端口,你需要将 iptables 替换成 ip6tables

    如果你是基于 iptables 实现的 ufw 用户,你会发现 ufw 中建立的本地端口规则完全不生效,这是因为 ufw 不会处理任何从本机访问本机的连接,我们只能手动调用更底层的 iptables

  • 似乎修复了唤醒后键盘短暂失效的问题

    This entry is part 3 of 4 in the series 机械革命无界 15XPro

    之前一篇文章的评论区有人提到过机械革命15XPro 暴风雪在唤醒后键盘会短暂失效的问题,往往需要重新睡眠再唤醒才能激活键盘。经过我的排查后发现,在该问题出现时,有两个比较重要的 Log 线索:

    Aug 27 09:04:52 arch kernel: atkbd serio0: Failed to deactivate keyboard on isa0060/serio0
    Aug 27 09:04:52 zarch kernel: atkbd serio0: Failed to enable keyboard on isa0060/serio0

    根据 Claude 以及互联网上相关的讨论,这个问题大概率跟内置键盘的驱动有关,通常可以通过尝试不同的 i8042/atkbd 相关参数来解决。

    跟 Claude 讨论一番过后,我决定添加如下 kernel parameters:

    i8042.reset=1 i8042.nomux=1 i8042.nopnp i8042.noloop atkbd.reset=1

    实际体验下来,键盘失效的问题已经一周多都没有复现了。

  • 性能与公平:解决 Linux 桌面无响应的取舍之道

    已经记不得从什么时候开始,我的 Linux 桌面偶尔会遇到无响应的问题,这个问题经常跟比较重的磁盘读写相关,例如更新 JetBrains IDE,JetBrains IDE 创建索引。之所以会把无响应的问题归结到磁盘读写上,是因为卡顿往往跟内存占用、CPU 使用率、进程数无关。卡顿的表现为:鼠标可以移动,但是 KDE 桌面包括状态栏无法更新;Kitty 可以创建新的 Tab,但是 Fish 启动会卡住;已有终端下虽然可以流畅输入命令,但是执行命令会卡住。这个周末因为成都一直在下雨,不太方便出门,决定来尝试解决卡顿问题。

    我的电脑配置如下:

    出现问题的固态硬盘型号是 KINGSTON SNVS1000G, SMART 信息如下:

    # nvme smart-log /dev/nvme1
    Smart Log for NVME device:nvme1 namespace-id:ffffffff
    critical_warning			: 0
    temperature				: 89 °F (305 K)
    available_spare				: 100%
    available_spare_threshold		: 10%
    percentage_used				: 31%
    endurance group critical warning summary: 0
    Data Units Read				: 61665672 (31.57 TB)
    Data Units Written			: 171536497 (87.83 TB)
    host_read_commands			: 967548759
    host_write_commands			: 2052988968
    controller_busy_time			: 75703
    power_cycles				: 79989
    power_on_hours				: 15798
    unsafe_shutdowns			: 241
    media_errors				: 0
    num_err_log_entries			: 0
    Warning Temperature Time		: 0
    Critical Composite Temperature Time	: 0
    Thermal Management T1 Trans Count	: 0
    Thermal Management T2 Trans Count	: 0
    Thermal Management T1 Total Time	: 0
    Thermal Management T2 Total Time	: 0

    官网上查到这块固态硬盘的设计 TBW 是 240TB,目前写入了 87.83TB,按照这篇文章的估算方法,这块硬盘正值壮年,属于如果挂掉了会令人惋惜的状态。既然硬盘是健康的,那么就需要从软件的角度来排查问题了。

    复现问题

    根据我的日常使用经验,卡顿往往伴随高磁盘 IO,故可以使用硬盘跑分工具测试来复现问题:

    fio --filename=/path/to/no_cow_test.fio --size=8GB --direct=1 --rw=randrw --bs=4k --ioengine=libaio --iodepth=256 --runtime=120 --numjobs=4 --time_based --group_reporting --name=iops-test-job --eta-newline=1

    fio 运行时,可以通过 htop 的 IO Tab 看到有明显的磁盘读写,这时如果运行不太常用的命令,比如前端切图仔电脑上的 rustc --version,就可以观察到明显的延迟。通过这种方式比较稳定地复现问题场景后,就可以继续接下来的排查了。

    检查日志

    journalctl -b -p err --no-pagerdmsg -T 展示的错误日志通常对排查问题非常重要,但是在我的卡顿问题复现时却没有记录下任何错误信息。问题的排查一度陷入僵局,这时就得靠 Gemini 大师帮我排查一下有哪些设置会影响存储设备的性能了。

    脏页

    在 Linux 系统中,当应用程序向文件写入数据时,这些数据通常不会立即被写入到物理磁盘上。相反,它们首先被写入到内存中的一个缓冲区(称为「页缓存」或「page cache」)。这些已经修改但尚未写入磁盘的内存页就被称为「脏页」。

    脏页机制可以有效提升磁盘性能,但为了防止脏页无限增长,我们可以设定 vm.dirty_ratio 参数限制脏页数据占总内存的百分比,更多介绍请看 Wiki

    对于我的配置而言,如果 dirty_ratio 过大,比如 20%,意味着脏页数据量最高达到 6GB,不仅会挤占其他应用的可用内存空间,还可能由于操作系统需要一次性写入大量数据造成卡顿。

    但在我的测试下,将 vm.ditry_ratio 以及相关参数调小一些带来的效果并不明显,所以还得向其他的方向上挖掘。

    IO 调度器

    IO 调度器是 Linux 内核的一个组件,它的主要任务是优化对存储设备(如硬盘、SSD、NVMe)的读写请求顺序。

    对于 SSD 设备来说,IO 调度器可以帮助确保每个进程都能获得合理的 IO 访问机会,避免某些进程被「饿死」,另外不同的调度算法会在数据吞吐量与请求延迟之间有不同的取舍,以适应不同的应用场景。

    在默认情况下,Linux 不会为 NVME 设备设置 IO 调度器,它假设这类设备上的控制器有自己的优化机制,不需要额外的软件层面调度。我们可以通过下面的命令查看当前在使用的调度器(被方括号包围):

    cat /sys/block/nvme1n1/queue/scheduler
    [none] mq-deadline kyber bfq

    然而在调度器的选择上,Gemini 跟 Arch Wiki 出现了分歧,Gemini 认为 NVME 以及 SSD 就应该选择 none 作为 io scheduler,但 Arch Wiki 认为 it depends

    The best choice of scheduler depends on both the device and the exact nature of the workload. Also, the throughput in MB/s is not the only measure of performance: deadline or fairness deteriorate the overall throughput but may improve system responsiveness. Benchmarking may be useful to indicate each I/O scheduler performance.

    考虑到我的日常使用场景是桌面应用,NVME 上自带的控制器完全不可能知道我正在使用的窗口应用,也就没法保障我主要使用场景下的响应速度,于是我决定采用 Pop_OS 的推荐,使用 Kyber 调度器。

    重试问题场景后,卡顿的情况缓解了一些,但跟我的预期还有很大偏差,很多命令可能需要 10s 以上才能输出结果。于是又尝试了使用 BFQ scheduler,终于 rustc --version 可以在后台运行重 IO 程序的情况下在 1s 内输出结果了。使用下面的 udev rule 就可以持久化设置 IO 调度器。

    # /etc/udev/rules.d/60-ioschedulers.rules
    ACTION=="add|change", KERNEL=="nvme[0-9]*", ATTR{queue/rotational}=="0", ATTR{queue/scheduler}="bfq"

    通过设置 IO 调度器很好的解决了我的问题,那么代价是什么呢?

    I/O 调度器性能对比

    相较于 none 调度器,其他的软件调度器都加入了额外的一层抽象,这势必会引入更多的开销,为了搞清楚降低延迟的开销,我进行了下面的测试。

    fio --filename=/path/to/no_cow/test.fio --size=8GB --direct=1 --rw=randrw --bs=4k --ioengine=libaio --iodepth=256 --runtime=20 --numjobs=4 --time_based --group_reporting --name=iops-test-job --eta-newline=1
    iops-test-job: (g=0): rw=randrw, bs=(R) 4096B-4096B, (W) 4096B-4096B, (T) 4096B-4096B, ioengine=libaio, iodepth=256
    指标none(基准)BFQkyber
    读取 IOPS7,2147,018 (-2.7%)7,141 (-1.0%)
    写入 IOPS7,2357,039 (-2.7%)7,161 (-1.0%)
    总 IOPS14,44914,057 (-2.7%)14,302 (-1.0%)
    读取带宽28.2 MiB/s27.4 MiB/s (-2.8%)27.9 MiB/s (-1.1%)
    写入带宽28.3 MiB/s27.5 MiB/s (-2.8%)28.0 MiB/s (-1.1%)
    吞吐量性能指标
    延迟指标none(基准)BFQkyber
    读取平均延迟70.5ms72.4ms (+2.7%)71.2ms (+1.0%)
    写入平均延迟70.9ms72.9ms (+2.8%)71.7ms (+1.1%)
    50% 延迟70.8ms71ms (+0.3%)71ms (+0.3%)
    95% 延迟78ms89ms (+14.1%)81ms (+3.8%)
    99% 延迟82ms114ms (+39.0%)91-92ms (+11%)
    99.9% 延迟88ms132-133ms (+50.0%)128ms (+45%)
    延迟性能对比

    可以看到在单个进程的读写测试下, BFQ 跟 kyber 在吞吐量跟延迟上的表现要略逊一筹。而这些开销给 Linux 桌面场景下带来的提升量化后又有多少呢?这需要设计一个更加复杂的跑分测试了,我们需要让一个延迟敏感应用跟一个高吞吐量应用同时运行,然后对比不同 IO 调度器如何平衡两者的硬盘操作:

    # 低延迟小请求 (模拟交互式应用)
    fio --name=latency_sensitive \
        --filename=$TEST_DIR/latency_sensitive.fio \
        --size=2GB --direct=1 --rw=randread \
        --bs=4k --ioengine=libaio --iodepth=1 \
        --runtime=$RUNTIME --time_based \
        --group_reporting --output-format=json \
        --output=/tmp/latency_sensitive.json &
    
    # 高吞吐量请求 (模拟批处理)
    fio --name=throughput_heavy \
        --filename=$TEST_DIR/throughput_heavy.fio \
        --size=4GB --direct=1 --rw=randread \
        --bs=4k --ioengine=libaio --iodepth=512 \
        --runtime=$RUNTIME --time_based \
        --group_reporting --output-format=json \
        --output=/tmp/throughput_heavy.json &

    不同的 IO 调度器在我电脑上的实际测试结果如下:

    调度器延迟敏感应用 IOPS延迟敏感应用平均延迟高吞吐应用 IOPSIOPS 比率
    BFQ5,4740.18 ms129,35023.63
    kyber4,6110.22 ms138,90430.12
    none3,1540.32 ms146,10446.32

    IOPS 比率:高吞吐应用获得的 IOPS 是延迟敏感应用的多少倍

    通过上面的测试可以看到:

    1. BFQ 在公平性方面表现最佳,真正保护了延迟敏感应用
    2. None 调度器确实能提供最高的原始吞吐量,但以牺牲公平性为代价
    3. Kyber 提供了较好的平衡,但在极端公平性方面不如 BFQ

    结论

    正如 Arch Wiki 所说,如何选择 IO 调度器取决于你的使用场景跟工作负载,在我的工作电脑上,BFQ 才是那个能够让我这块中年硬盘提供最佳桌面应用使用体验的调度器。

  • 提升机械革命无界 15XPro 暴风雪的充电速度

    This entry is part 2 of 4 in the series 机械革命无界 15XPro

    无界15XPro 的 99Wh 电池容量属实很大,但充电速度一言难尽,基本上要 4~5 小时左右才能从 10% 充到 100%。经过一番搜索后,总算找到了提升充电速度的方法。

    安装 Tuxedo Control Center 后,可以在 Settings 中找到 Battery Charging Options,只需要将 USB-C charging options 设置为 Priorize battery charing speed 就行。另外,如果长时间插电使用,还可以将 Charging profiles 设置成 Stationary use,这将会在硬件层面限制电池充电到 80% (但系统设置仍会显示充电到 100%)。

    如果你没安装 Tuxedo Control Center 也没关系,只要安装了驱动程序 (aur/mechrevo-drivers-dkms) ,就能通过下面两个 sysfs 接口实现同样的功能:

    cat /sys/bus/platform/drivers/tuxedo_keyboard/tuxedo_keyboard/charging_profile/charging_profile
    cat /sys/bus/platform/drivers/tuxedo_keyboard/tuxedo_keyboard/charging_priority/charging_prio
  • OpenWRT 上的 zerotier 突然无法启动了

    This entry is part 5 of 7 in the series homelab 历险记

    在我之前的文章中,介绍了我从公网远程访问家庭内网的方式,但在上个周末,这套配置出现了故障。故障的表现是 OpenWRT 上的 zerotier 客户端无法启动:

    zerotier-one[21492]: terminate called after throwing an instance of 'std::bad_cast'

    通过搜索得知这个故障通常伴随着硬盘问题,比如磁盘空间不足。通过 df -h 指令可以看到 /tmp 分区满了,而在 OpenWRT 上 /var 实际上会被链接到 /tmp 分区,这就导致 zerotier 客户端在启动时无法将配置写入 /var/lib/zerotier-one/networks.d 文件夹中。

    使用 du 命令,可以看到是 singbox 的日志文件充满了 /tmp 分区,强行删除日志文件后,虽然 df 命令可以看到空间被释放了,但实际上尝试写入 /var 仍然会报空间不足的错误。

    在 Gemini 的指点下,得知这很可能是因为仍然存在进程在使用这个文件,导致磁盘空间并没有被释放。果然,重启 singbox 进程后, zerotier 客户端终于能正常启动了。

    为了避免以后再有类似的情况出现,我干脆关闭了 singbox 的日志,等需要 debug 的时候再开启。