Chrome 浏览器多实例隔离(三):会话状态同步、锁机制与迁移
多智能体场景下状态同步的三种模式:乐观锁、悲观锁、无共享。以及当智能体需要迁移到另一个浏览器实例时,如何保留会话状态。
多智能体状态管理的核心矛盾
单智能体场景中,状态管理很简单:一个智能体操作一个页面,状态就在那个页面上。操作成功就继续,失败就重试。
多智能体场景中,问题变得复杂:
- 共享状态:两个智能体操作同一个目标网站时,谁更新了订单状态?谁修改了配置?
- 状态视图不一致:智能体 A 记录"已提交表单",智能体 B 在同一时刻查询仍是"未提交"。
- 会话移动:智能体 A 的会话需要迁到另一个节点,但 Cookie 和 localStorage 绑定在原来的 Chrome Profile 上。
问题 1:共享状态同步
场景
两个智能体管理同一个电商平台的多个店铺。智能体 A 更新了店铺 1 的产品库存,智能体 B 不知道,从缓存中读取了旧的库存数据,覆盖了 A 的更新。
方案:三种同步策略
乐观锁(Optimistic Locking)
假设冲突很少发生,操作时记录版本号,提交时检查版本号是否变化:
# 乐观锁:更新时检查版本号
def update_inventory(store_id, new_count, expected_version):
# SQL: UPDATE inventory SET count = new_count, version = version + 1
# WHERE store_id = ? AND version = expected_version
# 如果 affected_rows == 0,说明版本冲突,需要重试
result = db.execute(
"UPDATE inventory SET count = ?, version = version + 1 \
WHERE store_id = ? AND version = ?",
[new_count, store_id, expected_version]
)
if result.affected_rows == 0:
raise ConflictError("Inventory was updated by another agent")适合冲突概率低的场景(读取多、写入少)。
悲观锁(Pessimistic Locking)
操作前先锁定资源,操作完成后再释放:
# 悲观锁:操作前锁定资源
def lock_and_update(store_id):
lock_key = f"lock:inventory:{store_id}"
acquired = redis.setnx(lock_key, "locked", expire=30) # 30 秒自动过期
if not acquired:
raise ResourceBusyError(f"Store {store_id} is being updated by another agent")
try:
# 执行更新操作
update_inventory(store_id)
finally:
redis.delete(lock_key)适合写入频率高的场景。
无共享(Shared Nothing)
每个智能体只操作自己分配的资源,不交叉:
# 资源分区:每个智能体只操作自己的店铺
AGENT_A_STORES = ["store_1", "store_2"]
AGENT_B_STORES = ["store_3", "store_4"]任何同步操作都不需要,因为没有共享状态。这是理论上最简单、实际中最容易被忽略的方案。
问题 2:会话迁移
场景
一个智能体在浏览器实例 A 上登录了一个目标网站,跑了 20 分钟操作,积累了 Cookie、localStorage 和 IndexedDB。现在实例 A 需要重启(内存泄漏、版本更新、节点故障)。智能体的会话需要迁移到实例 B,不丢失登录态。
方案一:导出/导入存储状态
import json
def export_session_state(cdp_ws_url):
"""从 CDP 导出会话状态"""
cookies = page.evaluate("document.cookie")
local_storage = page.evaluate("JSON.stringify(window.localStorage)")
return {
"cookies": cookies,
"localStorage": local_storage,
"sessionStorage": page.evaluate("JSON.stringify(window.sessionStorage)"),
}
def import_session_state(cdp_ws_url, state):
"""导入会话状态到新的浏览器实例"""
# 设置 Cookie
for cookie in state["cookies"]:
page.set_cookie(cookie)
# 恢复 localStorage
for key, value in json.loads(state["localStorage"]).items():
page.evaluate(f"window.localStorage.setItem('{key}', '{value}')")这种方式的问题:导出的 Cookie 中的 HttpOnly 标志无法通过 JavaScript 读取,只能通过 CDP 的 Network.getCookies 获取。
方案二:CDP Network.getCookies + Network.setCookies
# 导出
cookie_result = cdp.send("Network.getCookies")
state["cookies"] = cookie_result["cookies"]
# 导入到新会话
cdp.send("Network.setCookies", {"cookies": state["cookies"]})这种方式可以导出 HttpOnly Cookie,但需要 CDP 级别的访问权限。
方案三:复用持久化 Profile
最可靠的方式:把整个 Chrome Profile 目录从一台机器复制到另一台机器:
# 源机器
tar czf profile_backup.tar.gz -C /data/profiles/agent_a .
# 目标机器
tar xzf profile_backup.tar.gz -C /data/profiles/agent_a/然后在新机器上使用相同的 Profile 路径启动 Chrome。所有会话状态保持完整。
这种方式的问题:Profile 目录可能包含大量缓存数据(数百 MB 到 GB),跨网络复制需要时间。更实际的做法是只复制关键的 Storage 子目录:
# 只复制需要的存储数据
tar czf session_state.tar.gz \
-C /data/profiles/agent_a/Default \
Cookies Cookies-journal Local\ Storage IndexedDB问题 3:操作编排
在多智能体场景中,另一个常见问题是操作时序。智能体 A 和 B 同时对同一个页面发出不同的操作指令,导致页面状态不可预测。
编排策略
最简单的解决方案是串行化:任何针对同一目标的操作都通过中心化队列调度:
# 串行化操作队列
import asyncio
import aioredis
class OperationQueue:
def __init__(self, redis):
self.redis = redis
async def enqueue(self, target_key, operation):
"""将操作加入队列"""
await self.redis.rpush(f"op_queue:{target_key}", operation)
async def dequeue(self, target_key):
"""从队列取出下一个操作"""
return await self.redis.blpop(f"op_queue:{target_key}", timeout=5)
# 使用
queue = OperationQueue(redis)
# 智能体 A 的操作
await queue.enqueue("store_1", {"action": "update_price", "value": 99.99})
# 智能体 B 的操作
await queue.enqueue("store_1", {"action": "update_stock", "value": 50})
# 执行器按顺序执行
while op := await queue.dequeue("store_1"):
execute_operation(op)总结
多智能体场景的状态管理比单智能体复杂至少一个数量级,因为你需要处理:
- 共享状态同步——乐观锁、悲观锁或无共享
- 会话迁移——CDP Cookie 导出/导入或 Profile 目录复制
- 操作编排——串行化队列保证操作顺序
无论选择哪种方案,有一条原则值得记住:两个智能体不应该同时操作同一个浏览器的同一个页面。 要么隔离资源(无共享),要么串行化操作(队列),不要让两个大脑同时控制同一双手。
需要企业代理方案?
我们可根据目标站点、并发规模与稳定性目标提供定制方案。