今年(2026)开春,我帮一家做企业服务工具的团队把 OpenClaw 从个人玩具升级成企业级服务平台,结果第一个晚上就被session file locked这个报错按在地上摩擦。当时 OpenClaw 跑在单台服务器上,同时接入飞书、Teams 和 Discord 三个渠道,用户一多,同一个会话的请求并发进来,进程内锁直接超时,60 秒后 agent 拒绝响应。那个晚上我意识到一件事:OpenClaw 本身再强,单节点跑法也撑不起“高可用”这三个字。
这篇文章想聊的就是多节点 OpenClaw 集群部署怎么做,以及负载均衡这套东西在企业场景下怎么落地。内容围绕三个问题展开:单机为什么扛不住、集群架构怎么设计、部署过程中会踩到什么坑。适合正在把 Agent 平台化的后端开发、架构师和 SRE 看;如果你只想在自己电脑上跑一个 OpenClaw 机器人,这篇可能帮不了你太多,但分布式锁、会话存储、通道绑定这些思路,提前了解没坏处。
1. 为什么单节点扛不住:高可用背后的三个本质问题
1.1 单机模式的“一进程一锁”模型
大多数 AI Agent 框架默认是单体进程运行,OpenClaw 也不例外。每个用户或者每个会话对应一份上下文,进程内用一个锁对象串行化对这个上下文的读写。平时没什么问题,可一旦外部工具调用变慢、模型接口超时,锁就会被占用很久。这时候同一个会话再来新消息,只能排队等锁。等到默认超时时间(OpenClaw 是 60000ms)还是没拿到锁,就直接报agent failed before reply: session file locked。
这个模型在个人使用场景没毛病,但在企业级并发下问题会被无限放大:同一会话并发、多条消息同时触发、定时任务和人工消息冲突,这些都是家常便饭。关键是,本地文件锁只能锁住当前进程,换到另一台机器上就完全失去约束。这就是多节点化后暴露的第一道坎。
我们要正视一个事实:OpenClaw 这类 agent 框架,核心难点从来不是“能不能跑”,而是“状态怎么管”。单机模式下,进程、状态、连接三者全绑死在一台服务器上,看起来简单,实际上把所有的风险也一起绑进去了。
1.2 99.9% 可用性的量化与单点故障风险
做技术方案之前,先要跟目标团队对齐可用性目标。企业级一般会签 SLA,比如 99.9%。99.9% 听起来很厉害,算下来一年大约允许 8.76 小时不可用(365×24×60×0.001≈525.6 分钟),再拆到每天不到 1.5 分钟。单节点模式下,一次升级、一次磁盘写满、一次模型 API Key 失效,都可能造成远超这个数字的中断。
单节点还会遇到三个具体问题。一是会话状态和进程强绑定:节点重启,所有正在进行的对话直接失去上下文,用户问“刚才说的那个方案你忘了?”,agent 一脸茫然。二是通道长连接断了以后,没有另一个节点能接管,飞书机器人掉线多久,业务就中断多久。三是扩容方式只有“升级机器”,成本线性上升,但故障域并没有缩小,反而因为单机承载更多业务,出问题时影响面更大了。
所以做高可用,本质不是把机器买贵,而是把“进程、状态、连接”三者解耦。只有把这个解耦做完,后面谈多节点才有意义。否则哪怕凑齐了十台机器,也只是一堆独立的单点,并没有形成真正的集群。
1.3 多节点集群最终拓扑与数据流
最终架构我建议这样搭:最外层是负载均衡(生产环境用 Nginx 足够,规模再大再上云 LB 或者 LVS),后面挂三个 OpenClaw Worker 节点,再往后是共享的 Redis(存会话状态和分布式锁)和 PostgreSQL(存用户、配置、审计日志)。
数据流上,IM 平台的消息回调先进负载均衡,负载均衡按用户维度哈希到固定 Worker,Worker 需要读写会话状态时走 Redis,需要记录审计或持久化用户数据时走 PostgreSQL。这套拓扑的好处很明显:任何一台 Worker 宕机,负载均衡自动摘除,Redis 里的会话状态还在,另一个节点可以立刻接管会话,用户基本无感知。任何一个通道断了,重启对应 Connector 进程就行,不影响其他通道。
有些团队会问,要不要上消息队列,比如 Redis Streams 或 RabbitMQ。我的观点是前期不需要,单一入口加一致性哈希已经能解决 90% 的问题。消息队列会引入消息有序性、消费位点、重复投递这些额外复杂度,等真出现事件风暴再引入不迟。Kafka 三节点、Redis 三主三从那些集群套路大家都熟,但 agent 服务集群的第一优先级是把状态管好,而不是把消息管道铺得很豪华。
2. 集群架构设计:关键选型与“为什么这样选”
2.1 无状态化改造:把锁和会话从本地文件挪到 Redis
多节点第一步,就是把 OpenClaw 变成无状态服务。所谓无状态,不是说没有状态,而是状态不在本地进程里,而是放在所有节点都能访问的共享存储中。这个改造是整篇文章最重要的一步,没有它,后面所有负载均衡都是自欺欺人。
具体有两个改动。第一,会话存储从本地文件换到 Redis Hash,每个 session 对应一个 key,保存对话历史和上下文 Meta。第二,把文件锁替换成 Redis 分布式锁,核心是SET NX PX这个原子操作,拿到锁的节点才能操作对应会话,锁带 TTL,防止节点宕机后锁永远不释放。OpenClaw 的配置里如果原来写的是本地文件路径,我们需要把它改成 Redis 地址。
这里有个很容易踩的坑:锁的 TTL 设置。我见过有人图省事把锁 TTL 设成 60 秒甚至更长,觉得跟原来超时时间一致就行。实际上如果业务处理本身要 30 秒,TTL 设 20 秒就会误杀正在运行的请求;设得太长,又会在节点宕机后造成长时间等待。合理做法是先统计线上单次任务耗时的 P99,把 TTL 设成 P99 的两倍左右,同时给锁加上 owner 标记,释放时只释放自己持有的锁,避免误删别人的锁。
storage: type: redis redis: addr: "redis://10.0.0.20:6379" password: "YOUR_REDIS_PASSWORD" db: 0 session_ttl: 3600 lock: type: redis redis: addr: "redis://10.0.0.20:6379" ttl_ms: 30000这段配置是我按常见改法给的示例,具体字段名要以你所用 OpenClaw 版本和配置规范为准。但思路是通用的:本地文件存储换掉之后,你才敢放心地横向加节点。
2.2 节点角色划分:Connector 和 Worker 为什么要分离
很多团队做多节点时犯的第一个错误,是把三个节点都配置成同时连接飞书、Teams、Discord,结果每个平台的消息被三个节点重复消费,用户收到三条一模一样的回复,场面非常尴尬。
原因是 IM 平台的 WebSocket 长连接没法通过负载均衡的轮询来共享。同一个机器人账号只能有一个有效连接,否则事件会被广播到所有连接。所以我的建议是将节点分为两种角色。Connector 节点专门维持 IM 通道长连接,只处理消息收发和事件解析;Worker 节点专注 agent 推理和工具调用。通道和节点绑定关系在配置里写死,例如飞书和 Teams 绑定到 node-03,Discord 绑定到 node-01。
channels: feishu: enabled: true connector_node: node-03 teams: enabled: true connector_node: node-03 discord: enabled: true connector_node: node-01这样分离还有一个额外的好处:模型升级或者工具逻辑变更时,只需要重启 Worker 节点,IM 通道保持在线,用户无感知。反过来,飞书 WebSocket 断线重连只影响 Connector 节点自身,不会把推理节点也拖下水。如果你只有一个节点,那两种角色当然可以并存;但节点数量上来以后,越早分离越省心。
2.3 负载均衡选型:Nginx、LVS 还是云厂商 LB
老有人问,负载均衡用 Nginx 还是 LVS,要不要上云 LB。其实对 OpenClaw 这类 agent 服务,核心不是性能,而是“同一用户的请求必须落到同一节点”。IM 回调是短连接,本身压力不大,机器的性能瓶颈通常在模型调用和上下文处理上,而不是在网关转发上。
Nginx 配upstream_hash按client_id做一致性哈希,是最稳妥的起步方案。好处是同一用户的会话上下文大概率命中同一节点,而且一致性哈希在节点增删时只需要迁移少量 session。不要用ip_hash,因为企业内部网络有大量用户共用出口 IP,哈希会严重倾斜;也尽量不要裸用四层 LB,LVS 和 Windows Server 那套 NLB 虽然能做分发,但只看 IP 和端口,做不了应用层 user_id 哈希,最终还是要靠七层来保证用户粘性。
upstream openclaw_backend { hash $http_x_client_id consistent; server 10.0.0.11:8080 max_fails=3 fail_timeout=30s; server 10.0.0.12:8080 max_fails=3 fail_timeout=30s; server 10.0.0.13:8080 max_fails=3 fail_timeout=30s; } server { listen 443 ssl; server_name agent.example.internal; ssl_certificate /etc/nginx/ssl/fullchain.pem; ssl_certificate_key /etc/nginx/ssl/privkey.pem; location /api/ { proxy_pass http://openclaw_backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_read_timeout 120s; proxy_send_timeout 120s; } location /healthz { proxy_pass http://openclaw_backend; access_log off; proxy_connect_timeout 5s; } }要注意,Nginx 的$http_x_client_id不是内置变量,而是读取请求头X-Client-ID。OpenClaw 入口侧需要加一个小中间件,把回调解析出的用户标识塞进这个 header,Nginx 才能按用户哈希。这一步如果没做,负载均衡就退化成普通轮询,会话还是会乱跳节点。至于 MoE 负载均衡那种算法,那是模型推理层的事,agent 接入层用不上,别被概念绕晕。
如果后面节点规模超过几十个,再考虑在入口加一层 LVS 做四层转发,后面挂多个 Nginx 做七层路由,这是经典的分层负载均衡结构。前期真没必要。
2.4 配置管理:密钥与模型配置的集中化
多节点之后,每个节点都需要配置,手动一个个改不现实。我建议至少做到两件事:第一,把 API Key、数据库密码从配置文件里抽出来,放到环境变量或者 Secret 管理工具里;第二,用同一份配置模板生成各节点配置,只通过环境变量区分节点角色。
模型接入层也要统一。团队里有人用千问(Qwen),有人用 DeepSeek,还有人用 GPT 系列,这很正常。但节点多了以后,API Key 配额分散在各个节点,容易出现某个 key 被限流、另一个 key 闲置的情况。建议把模型网关也集中化,或者明确每个节点使用哪套模型配置,并做好配额监控。企业里跑 agent,模型成本往往比服务器成本高,这一块值得专门盯。
3. 实操部署:从单机到三节点集群的完整落地
3.1 环境准备与节点规划
部署之前先做好规划。下面是我推荐的一套三节点起步配置,按每周百万级消息量的中等规模估算的,实际可以根据你的调用频率调整。
| 节点 | 角色 | 配置建议 | 主要职责 |
|---|---|---|---|
| node-01 | Connector + Worker | 8C16G | Discord 通道、常规 worker |
| node-02 | Worker | 8C16G | 推理与工具调用 |
| node-03 | Connector + Worker | 8C16G | 飞书、Teams 通道、常规 worker |
| redis-01 | 存储 | 4C8G | 会话状态、分布式锁,开启 AOF + RDB |
| pg-01 | 存储 | 4C8G | 用户数据、审计日志 |
实际操作中,也可以把所有节点都作为 Worker,只有 node-01 和 node-03 额外开启通道连接。安装基础环境时,建议统一操作系统版本,至少保持同一个大版本,避免 glibc 和 OpenSSL 版本不一致导致的兼容问题。如果通过 Docker 跑,锁定镜像 Tag,不要用 latest。我习惯用二进制部署,因为排障更直观;容器化部署逻辑完全一样,只是把配置目录挂载出来,关键点在后面。
3.2 存储层迁移:先把“老数据”搬到共享存储
迁移前先停机,不要在运行中迁移会话数据,否则会出现两边都拿不到完整上下文的情况。步骤很简单:先在 Redis 里建好库,把单机本地 session 文件按 key 导入 Redis Hash;再把用户信息和审计日志导入 PostgreSQL;最后在单机配置上关掉本地 session 存储,切换到 Redis,验证原会话还能继续对话。
导入脚本写起来不复杂,最关键是 key 结构要和 OpenClaw 预期一致。如果框架没有提供迁移工具,就用它自己的 dump/export 功能导出 JSON,再按官方字段逐个写入 Redis。切完以后一定要拿三个历史会话做测试,确认上下文、记忆、最终回复都正常,再考虑放量。
Redis 本身要做持久化,开启 AOF 和 RDB 双写。有条件的话直接上主从,一个实例挂了,从节点顶上。会话数据不像缓存那样丢了就能重建,它直接影响用户的连续对话体验,持久化的优先级要拔高。
3.3 三节点 OpenClaw 配置改造与启动
每个节点的配置差异主要在 channels 段,以及node_id。我用 node-02 为例,它不开启任何通道,只做 Worker。node-01 和 node-03 的配置在 channels 段不同,分别开启各自负责的通道。
mode: cluster node_id: node-02 storage: type: redis redis: addr: "redis://10.0.0.20:6379" password: "YOUR_REDIS_PASSWORD" db: 0 session_ttl: 3600 lock: type: redis redis: addr: "redis://10.0.0.20:6379" ttl_ms: 30000 database: type: postgres dsn: "postgres://openclaw:password@10.0.0.21:5432/openclaw" channels: feishu: enabled: false connector_node: node-03 teams: enabled: false connector_node: node-03 discord: enabled: false connector_node: node-01三个节点都起来后,分别查看日志,确认成功连上 Redis 和 PostgreSQL。启动顺序也有讲究:先启动存储层,再启动负载均衡,最后启动 OpenClaw 节点。因为 Nginx 健康检查会探测后端端口,后端没起来时会把节点标记为 unhealthy,等节点起来后需要一个周期才能重新上线。顺序搞反了,会白白多等一阵子。
3.4 负载均衡配置与灰度验证
Nginx 配置我在前面已经给出,这里补充几个验证要点。先用 curl 直接打三个节点各自的健康接口,确认都返回 200;然后通过 Nginx 入口发真实消息,确认回复正常且会话上下文连贯。最后做一次小流量压测,模拟 20 个用户同时发消息,观察节点 CPU 和 Redis 连接数,判断容量是否够用。
灰度上线很重要。不要一次性把生产流量全部切过去,先在一个通道上切 10% 的用户,跑 24 小时,确认没有 session 错乱、重复回复、消息丢失,再逐渐放量。我见过太多团队一把梭哈,结果第二天早上用户反馈“机器人疯了”,那种情况下想定位问题,难度成倍上升。
4. 故障排查与常见问题实录
4.1 session file locked(timeout 60000ms)的完整复盘
这个报错值得单独拿出来讲,因为它是单机模式最常见、多节点模式最容易复发的坑。报错原文是agent failed before reply: session file locked (timeout 60000ms),字面意思:等了 60 秒还没拿到 session 文件的锁,于是放弃处理。
产生原因有三类。第一,单机模式下,一个任务卡在外部调用上,锁被占住。第二,进程被 kill -9 后,锁文件残留,重启后新进程拿不到锁。第三,切换多节点后,两个节点同时尝试操作同一个会话,而锁还在本地文件里,互相看不见。排查思路是:先ps aux | grep openclaw看有没有多个进程;如果只有一个进程,用lsof看 session 文件被谁占用;如果已经切了 Redis 锁,用redis-cli -h 127.0.0.1 keys "session:*:lock"查看锁 key 和 TTL。
解决步骤分三种情况:单机残留,停掉所有 OpenClaw 进程,删掉本地锁文件,再启动;多节点场景,最彻底的办法就是把存储和锁全部切换到 Redis,关闭本地文件锁功能;如果 Redis 锁出现死锁,检查是否忘了设置 TTL,或者节点宕机后锁没释放。生产环境建议锁 TTL 设在 30 秒左右,并定期清理超过 2 个 TTL 周期未更新的锁。
4.2 通道绑定与消息重复、截断问题
多节点最常见的表现就是重复回复。三个节点同时连着同一个飞书机器人,飞书把消息推送三次,三个节点各答一遍。解决方式就是前面说的 Connector 角色分离,一个通道只绑定一个节点。这在部署文档里写清楚,比出问题后再排查高效得多。
飞书输出容易被截断的问题,跟集群没有直接关系,但多节点之后会更明显。原因是平台对单条消息长度有限制,agent 一次性输出太长就会被截断。我的处理方式是在 OpenClaw 的输出层加一个分片发送逻辑:超过长度阈值就按业务语义切成多段,依次发送;或者改用消息卡片、富文本,容量比纯文本大得多。
Teams 接入失败多见于回调地址配置错误或者证书问题。排查时先看 OpenClaw 日志里的回调事件是否到达,再到 Teams 管理后台看 Bot 的回调 URL 是否指向负载均衡地址,公网地址必须能通到 Nginx。如果公网到 Nginx 的链路没问题,再往后面查,一层一层缩小范围。
4.3 节点宕机后的会话迁移与状态丢失
节点宕机时,最怕出现两种现象:一种是用户继续收到 502,另一种是重启后用户发现 agent“失忆”了,上下文全都不认。第一种说明负载均衡没把故障节点摘掉,检查 Nginx 的max_fails和fail_timeout是否配置正确,健康检查路径是否真的存在。第二种说明会话状态不在 Redis 里,或者 Redis 里的 key 已经过期。
session_ttl默认一小时,如果业务需要跨天对话,就把它调长,同时注意 Redis 内存。建议按会话活跃度做区分:活跃会话 TTL 延长,超期自动清理。想做得更稳,可以再加一层持久化备份,把 Redis 中的 session 定期 dump 到对象存储,节点宕机后能恢复最近一次的会话状态。故障演练也很重要,一个月至少手动 kill 一台 worker,看看负载均衡和 Redis 能不能把会话接住。
4.4 避坑清单速查表
| 现象 | 根因 | 解决方案 |
|---|---|---|
| session file locked 报错 | 本地文件锁或进程残留 | 切 Redis 存储与锁,清残留锁 |
| 多节点重复回复 | 同一通道被多个节点连接 | Connector 角色分离,通道绑定单节点 |
| 消息被截断 | 超过平台单条消息限制 | 输出分片发送,或改用卡片消息 |
| 重启后上下文丢失 | 会话未存到共享存储或 key 过期 | 会话存 Redis,合理设置 TTL |
| 节点宕机后 502 | 健康检查未摘除故障节点 | 配置 healthz 与 fail_timeout |
| 同一用户会话乱跳节点 | 负载均衡未按用户哈希 | Nginx hash 按 client_id 一致性哈希 |
| 模型 API Key 限流不均 | 各节点独立配额 | 模型网关集中化或按节点分配模型 |
这张表基本覆盖了我实际部署过程中踩过的大多数坑。遇到问题先对着表看一遍,比自己翻文档高效得多。
最后说点我自己的体会。折腾完这套集群,回头看我最大的收获不是把 OpenClaw 从一台机器变成了三台,而是把它的状态管理彻底梳理清楚了。只要你还在用本地文件存 session,不管前面挂多少层负载均衡,都是假高可用。先把状态放到 Redis,再谈多节点、再谈扩容,这条路径我已经替大家验证过了,代价是会多踩几个坑,但每一步都走得扎实。
如果你现在还在单节点跑,我建议不要急着一次上三台机器。先在测试环境把存储切到 Redis,加上 Nginx 负载均衡,模拟一次节点宕机,看看能不能自动恢复。能扛住一次故障,再推生产也不迟。