客户端收到 ConnectionLoss 时,请求到底成功了没有?这是 ZooKeeper 使用中最容易出错的地方。这篇用一个会丢弃响应的 TCP 代理构造「服务端执行了、客户端不知道」的情形,看每种操作重试时会发生什么。
第一次调用客户端看到: ConnectionLoss | 服务端实际有 /job: True 客户端重试 create → NodeExistsError
真实环境里的网络抖动、服务端切换都会造成这种情形。这里的代理是我用 Python 写的:转发客户端请求、丢弃服务端响应、随后断开连接,让客户端必然看到 ConnectionLoss。
第一次调用客户端看到: ConnectionLoss | 服务端此时 /seq 的子节点: ['task-0000000000'] 客户端重试 create(sequence) 之后: ['task-0000000000', 'task-0000000001']
第一次调用客户端看到: ConnectionLoss | 服务端数据: b'b' version: 1 客户端用同一个 version 重试 set → BadVersionError 第一次调用客户端看到: ConnectionLoss | 服务端已删除: True 客户端重试 delete → NoNodeError
第一次调用客户端看到: ConnectionLoss | 服务端数据: b'z' 重试后数据: b'z' | version = 2 (被写了两次,版本号 +2)
总结实测出来的规律:操作能不能安全重试,取决于它是不是「重放结果不变」。覆盖写数据是,顺序节点创建不是,带版本的写需要把 BadVersion 当作「可能已成功」处理。通用做法是让每个请求带一个唯一标识(写进节点数据或节点名),重试前先查询是否已经落地。这是设计建议,本文只验证了上面的现象,没有验证具体的去重方案。