Openclaw在Windows的docker中访问缓慢

前言

114 秒的 Event Loop 阻塞,因为挂载目录导致 WSL2 9P 文件系统引发的 OpenClaw 性能故障

在 Windows 上通过 WSL2 部署 OpenClaw 后,WebChat 体验一度让我怀疑是不是 API 限速了——每条消息都要等上几分钟才回复,页面卡得像是回到了拨号上网时代。

起初以为是模型 API 慢,直到一条 Gateway 日志把真相拍在了脸上。

环境

  • Windows 11 + WSL2

  • OpenClaw 以 docker 容器运行在 WSL2 中

  • .openclaw 数据目录位于 Windows D 盘(D:\),通过 WSL2 的 /mnt/d 挂载访问

    services:
      openclaw:
        image: 1panel/openclaw:latest
        container_name: openclaw
        restart: unless-stopped
        ports:
          - "18789:18789"
        volumes:
          - /home/xxx/openclaw-data/config:/home/node/.openclaw
          - /home/xxx/openclaw-data/workspace:/home/node/.openclaw/workspace
        environment:
          - TZ=Asia/Shanghai
        cpus: 4.0
        mem_limit: 4g
    

现象:WebChat 卡到无法使用

日常使用中:

  • 发送消息后,前端长时间旋转,每条回复等待 2-5 分钟
  • 偶尔直接超时无响应
  • 简单问候也需要几十秒
  • 整个过程 CPU 风扇不转、内存充裕,排除算力瓶颈

关键日志:Liveness Warning

2026-05-01 16:11:42,Gateway 输出了这样一条诊断警告:

[diagnostic] liveness warning:
  reasons=event_loop_delay
  interval=136s
  eventLoopDelayP99Ms=20.3
  eventLoopDelayMaxMs=114487.7
  eventLoopUtilization=0.843
  cpuCoreRatio=0.093
  active=1 waiting=0 queued=1

几个数字非常可疑:

指标 数值 含义
eventLoopDelayMaxMs 114,487 ms Node.js 事件循环被阻塞了 114 秒
eventLoopUtilization 0.843 事件循环 84.3% 的时间在等 I/O
cpuCoreRatio 0.093 CPU 几乎空闲,不是算力问题

这三个数字放在一起,结论已经很明确了:CPU 没干活,事件循环在死等磁盘 I/O

排查过程

第一步:确认不是模型 API 的锅

日志里的 eventLoopDelayMax=114s 远超任何 API 超时时间,而且 cpuCoreRatio=0.093 说明进程根本没在计算。模型响应时间正常,问题出在 Gateway 内部。

第二步:查看进程 I/O 统计

检查 Gateway 进程的系统调用计数:

cat /proc/$(pgrep -f openclaw)/status | grep -E "State|voluntary_ctxt|nonvoluntary"
cat /proc/$(pgrep -f openclaw)/io

发现:

  • 进程频繁处于 D 状态(不可中断睡眠)——典型特征是卡在磁盘 I/O 上
  • 3 小时内累计系统调用 syscr=7,054,000 次,全是小文件读写

第三步:找到根本原因——9P 文件系统

检查 .openclaw 目录实际挂载位置:

df -h /home/node/.openclaw
# 输出指向 /mnt/d,即 Windows D 盘

mount | grep /mnt/d
# D:\ on /mnt/d type 9p (rw,noatime,dirsync,...)

根因确诊.openclaw 目录挂在 Windows D 盘上,通过 WSL2 的 9P (Plan 9) 网络文件协议访问。

根因分析:9P 为什么这么慢?

9P 是什么

9P 是 Plan 9 操作系统的网络文件协议。WSL2 用它来让 Linux VM 访问 Windows 宿主机的文件系统。它在架构上是这样的:

Node.js 进程
    ↓ read() / write()
ext4 内核 VFS
    ↓
9P 协议层(网络传输)
    ↓
Windows 宿主 NTFS
    ↓ 实际磁盘 I/O
    ↑ 数据原路返回

性能差异

每一次文件操作(read、write、fsync、stat)都要完整穿越这个调用链。在原生 ext4 上一次 fsync 约 0.1ms,在 9P 上是 10-50ms——慢了 100-500 倍。

为什么 OpenClaw 是 9P 的"受害者"

OpenClaw docker 的 I/O 模式恰恰是最不适合 9P 的那种:

  • SQLite 频繁写锁 + fsync:会话状态持久化,每次写入需要 fsync 确认落盘
  • 每秒 650+ 次 read 系统调用:JSONL 会话记录、memory 文件、配置热加载
  • 大量小文件读写:AGENTS.md、TOOLS.md、MEMORY.md、memory/*.md 的读取和 inotify 监听

所有这些操作在 9P 上会形成严重的排队效应。SQLite 的一次 fsync 堵住事件循环,后面几百次小文件 read 全部排队等待,最终表现为用户看到的 114 秒事件循环阻塞

证据链完整对应

现象 原因
eventLoopDelayMaxMs=114,487 SQLite 写锁 + 9P fsync 阻塞主线程
eventLoopUtilization=0.843 Node.js 事件循环 84% 时间在等 I/O 返回
cpuCoreRatio=0.093 CPU 几乎空闲——不是算力问题
syscr=7,054,000(3h) 海量小文件操作全部穿越 9P 协议栈
进程频繁 D 状态 卡在不可中断的 9P 磁盘等待
WebChat 响应数分钟 每次对话产生的读写排队等 9P

解决方案

核心思路:把 .openclaw 数据目录从 Windows 文件系统(NTFS,走 9P)搬到 WSL2 虚拟机内部的 ext4 文件系统,让所有 I/O 操作在 Linux 内核内部完成。

操作步骤

# 1. 停止容器
docker compose down

# 2. 备份现有数据
mv /home/node/.openclaw /home/node/.openclaw.9p-backup

# 3. 复制数据到 WSL2 原生 ext4 位置
#    注意:目标路径必须在 WSL2 内部,不能以 /mnt/c 或 /mnt/d 开头
cp -a /home/node/.openclaw.9p-backup /home/node/.openclaw

# 4. 验证新路径不在 9P 上
df -h /home/node/.openclaw
# 应该显示 ext4 而非 9p

# 5. 在WSL中重启
docker compose up -d

关键注意

  • /home/node/ 在 WSL2 内部是 ext4,不走 9P
  • /mnt/d/ 是 9P 挂载点,任何经过这里的 I/O 都会变慢
  • 迁移后数据在 ext4 上,不影响原有备份

效果验证

迁移后,同样的 Gateway 日志:

eventLoopDelayMaxMs: < 100ms   (之前 114,487ms)
eventLoopUtilization: < 0.05   (之前 0.843)
syscr 3h 累计: < 500,000       (之前 7,054,000)
进程状态: S (sleeping)          (之前 D 状态频繁)

WebChat 响应恢复到 1-2 秒,不再出现超时。

原理总结

┌──────────────────────────────────────────────────────┐
│                    Windows 宿主                        │
│  ┌───────────┐      9P 协议       ┌─────────────────┐│
│  │ D: (NTFS) │◄═════════════════►│ WSL2 Linux VM   ││
│  │           │   慢!100-500x    │                 ││
│  │  .openclaw│    每次 fsync     │  ┌───────────┐  ││
│  └───────────┘    10-50ms       │  │ ext4 ✓    │  ││
│                                  │  │ .openclaw │  ││
│                                  │  │ fsync     │  ││
│                                  │  │ 0.1ms     │  ││
│                                  │  └───────────┘  ││
│                                  └─────────────────┘│
└──────────────────────────────────────────────────────┘

问题路径:Node.js → ext4 VFS → 9P → NTFS  瓶颈在 9P
解决路径:Node.js → ext4                    没有瓶颈

替代方案

如果你用 Docker Desktop

同样的问题也会出现在 Docker Desktop 的 bind mount 上——Docker Desktop 在底层也是通过 9P 把 Windows 路径暴露给 WSL2 VM。解决方式一样:

# ❌ 不要这样(经过 9P)
volumes:
  - D:\openclaw\workspace:/home/node/.openclaw/workspace

# ✅ 把数据放在 WSL2 内部再挂载(docker-compose.yml自然在WSL2内部了)
volumes:
  - /home/user/openclaw/workspace:/home/node/.openclaw/workspace

如果你的docker命令无法在WSL2内部使用

🔧 解决步骤:在 Docker Desktop 中启用 WSL 2 集成

  1. 找到设置入口:在 Windows 系统托盘中找到 Docker Desktop 的鲸鱼图标,右键点击它,选择 “Settings”(设置)
  2. 定位到集成页面:在设置窗口左侧的菜单中,点击 “Resources”(资源),然后选择 “WSL Integration”(WSL 集成)
  3. 开启集成:在右侧页面,你会看到两个关键选项:
    • 首先,勾选 “Enable integration with my default WSL distro”(启用与我的默认 WSL 发行版的集成)
    • 然后,在下方的“Enable integration with additional distros”(启用与其他发行版的集成)列表中,找到你正在使用的 WSL 发行版(比如 Ubuntu),并确保它的开关也被打开
  4. 应用并重启:点击右下角的 “Apply & restart”(应用并重启) 按钮。Docker Desktop 会自动重启,整个过程只需要几分钟。

✅ 验证和使用

  • 验证:等 Docker Desktop 重启完成,回到你的 WSL 2 终端,再次运行 docker-compose up -ddocker ps,命令应该就能正常执行了。
  • 启动服务:如果一切正常,别忘了先用 cd ~/openclaw-docker-ext4 回到你的项目目录,再运行 docker-compose up -d 启动服务。

如果必须在 Windows 上编辑配置文件

使用 VS Code 的 Remote-WSL 扩展,可以直接在 Windows 的 VS Code 中打开 WSL2 内部目录。或者通过 \\wsl$\ 网络路径访问。

后记

这个问题的确诊过程让我学到一件事:当 CPU 空闲但系统卡顿,先看 I/O 路径

9P 不是 bug,WSL2 也不是 bug。9P 为了兼容性牺牲了性能,对日常 ls 和少量文件编辑完全够用。但当你的应用每秒做几百次 read、每次对话都触发 SQLite fsync,这些累积的延迟就不再是无感的了。

一句话总结:让频繁读写的数据待在 ext4 上,把 9P 留给偶尔访问的静态文件。


遇到过类似问题?欢迎交流。识别方法很简单:mount | grep 9p 看看你的热数据在不在里面。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容