容器化自动化测试中的 Podman 锁竞争排查

我们的端到端测试是一个由 Playwright、Testcontainers 和容器运行时组成的多层工作流。 测试工作流 每个 Playwright worker 通过 Testcontainers 管理一组相互隔离的容器和 bridge network。启动前会清理残留资源,测试期间按需启动浏览器、应用和数据库,结束后再停止并删除容器和网络。 清理残留资源 -> 创建 network 和容器 -> 执行浏览器测试 -> stop/remove 容器 -> remove network 这些操作都由 Testcontainers 通过连接 podman Docker 兼容 API 完成, worker 的 teardown 会让 stop、remove、network remove 与容器状态查询交错执行,这正是本次调查关注的并发场景。 实验条件 为了避免大型业务镜像的启动成本和业务行为干扰测试,只使用了一个小镜像: 工作负载:busybox:1.36.1 Podman:quay.io/podman/stable:v5.8.2-immutable Docker:docker:dind,Docker Engine 29.7.2 每轮 24 个并发操作,共 5 轮 两个运行时均运行在 privileged 容器内,状态目录使用 4 GiB tmpfs 每个并发操作创建 bridge network,创建并启动一个 busybox 容器,然后执行 stop、remove 和 network remove。teardown 阶段同时发起 24 个 GET /containers/json?all=1 请求,并持续采样 containers-list、/info 和 /_ping。 ...

August 27, 2026

多进程配置文件读写的演化历程

第一阶段:共享一套读写逻辑,谁都可以写 一开始,多个进程共用一套读写逻辑: if (!Exists(configPath)) { WriteDefaultConfig(configPath); } var config = ReadConfig(configPath); 问题有两个: 多个进程可能同时发现“文件不存在”,然后一起创建 一个进程正在写,另一个进程正好在读 如果是直接覆盖写,读取方可能看到: 文件刚被创建,内容还是空的 文件已经存在,但只写了一半 配置结构还没写完整 目标文件在替换过程中短暂不可用 对应的现象就是: 偶发读取失败 配置解析异常 文件内容丢失 某些进程启动时报错 根因很简单:读者看到了写入中的中间状态。 第二阶段:加锁、重试、缓存,先把系统稳住 先说 FileShare。 它想解决的其实是两个很具体的问题: 读取正好撞上写入,结果读到一半内容 两次写入互相重叠,结果把文件写坏 最直接的做法,就是用共享模式限制文件如何被同时打开。 它的目标不是提供原子替换,而是尽量减少下面这些情况: 读取撞上写入中的半成品 两次写入同时落到同一个目标文件 文件直接被另一进程占用导致操作失败 FileShare 带来的直接结果是:冲突不会消失,只会变成可以观察、可以重试的失败。 所以后面又加了重试。 这能减少两类问题: 少报“文件被占用” 让偶发冲突尽量靠重试跨过去 再往后,问题变成了独占时间。 配置读取很频繁,导致写入一直在等待读取释放文件锁。 所以开始引入缓存: var config = ReadConfig(configPath); var lastWriteTime = GetLastWriteTime(configPath); cache = (config, lastWriteTime); 下次访问时先比较时间戳: var currentWriteTime = GetLastWriteTime(configPath); if (currentWriteTime == cache.LastWriteTime) { return cache.Config; } cache = ReadConfig(configPath); return cache.Config; 没变,缓存有效;变了,缓存失效,重新加载。 ...

July 17, 2026