在AI构建的应用里实现实时协作(同时不破坏别人的工作)
当两个人同时编辑同一个应用时,实时协作就会出问题——一个人的改动会悄无声息地消失、被覆盖,或者和另一个人看到的内容互相矛盾。三种故障模式,三个逐一实现的修复方案,就能解决它。
当两个人同时编辑同一个应用时会发生什么?
实时协作,就是防止两个人在同时编辑同一份应用数据时互相覆盖对方工作的机制——如果跳过它,后保存的那个人可能会悄悄抹掉前一个人的改动。下面是这件事在某个团队身上真实发生的样子。
一位用户和团队一起搭建了一个共享任务清单。周五下午,两位同事同时打开了它。两人看到的都是:
- 任务1:买菜
- 任务2:给妈妈打电话
- 任务3:安排会议
同事A把”买菜”打了勾。同事B添加了”修路由器”。两人都点了保存。
当同事A刷新页面时,看到的是:
- 任务1:买菜(已勾选)
- 任务2:给妈妈打电话
- 任务3:安排会议
“修路由器”不见了。同事B的工作凭空消失。
这就是一次”碰撞”:同时写入,其中一人的改动丢失。听起来像是个”功能”,但它实际上是对数据丢失的修复。没有它,只要有两个人同时动手,你的应用就会崩坏。
最常见的实时协作缺陷有哪些?
实时协作通常以三种方式失效:一次写入被悄悄丢弃、屏幕显示的是过时数据,或者两个人最终看到互相矛盾的事实。每一种表现都不同,也各自需要不同的修复方案。
故障一:丢失的写入(静默数据丢失)
两个人同时保存。后一次保存覆盖了前一次。后保存的人看到自己的改动生效了,先保存的人却什么都看不到……或者刷新页面后,纳闷自己的工作去哪儿了。
真实故事: 一位婚礼策划师和她的助理一起整理宾客名单。助理添加了三个回执的同时,策划师把两个人标成了”确定”。策划师的标记消失了。直到策划师在跟进电话里重复统计、给已经答应赴约的人又打了一次电话,才发现问题。
大多数成熟的应用都是这样解决这个问题的:每敲一次键就保存,而不是只在点击”保存”按钮时才保存。Google Sheets、Notion、Figma都是这么做的。你的应用也需要这种行为。
故障二:过时的刷新(看到旧数据)
A编辑了一个任务。B的页面还开着,看到的是旧版本。B基于这份过时的数据做了改动。于是产生了一个双方都看不见的冲突。
真实故事: 一位保险理赔员和一位承包商在处理同一份理赔。理赔员根据新照片,把”预估维修费用:3000美元”改成了”5000美元”。承包商的页面上显示的仍然是3000美元。他按3000美元提交了审批表。后来,两人才发现了这个冲突。
没有实时更新,两个人都会以为自己在处理同一个版本。但其实并不是。
故障三:级联矛盾(两个真相)
一位用户删除了一条记录。另一位用户正在查看这条记录的详情。一个人看到的是”已删除”,另一个人看到的仍是完整记录。两人现在基于不同的事实在行动。
真实故事: 一位志愿者协调员把某个班次标记为”已取消”。志愿者还没刷新页面,看到的仍是”可报名”。于是他开始为这个班次招募人手。几个小时后,两个人出现在了一个根本不存在的班次上。
该如何修复实时协作缺陷?
按顺序逐一修复:用增量保存来检测写入冲突,在合并刷新数据时不丢失本地编辑,然后把冲突摆到明面上而不是藏起来。你不需要在第一天就把实时协作做到完美。
修复一:检测写入冲突(增量保存)
让每一次改动都立即保存,而不是只在点击”保存”时才保存。这是最重要的一项修复。
当用户编辑一个字段时,立刻把它发送到你的数据库。显示一个小小的”已保存”提示,或者一个同步完成后就消失的圆点。如果第二个人同时保存,你的数据库应该这样处理:
- A的改动先到达。
- B的改动后到达。
- B获胜(后写入者优先)。
这个做法很直接,却是诚实的:至少有一个人会发现自己的改动没保存住,然后可以重新来一遍。
给开发者的建议: 在每次按键、或用户停止输入2秒后触发保存,而不是依赖”保存”按钮。显示一个同步指示器。测试方法:在两个浏览器窗口里打开你的应用,编辑同一个字段。一个改动应该可见地覆盖另一个。
修复二:刷新时不丢失本地编辑
如果你每5秒轮询一次数据库(或者通过WebSocket推送更新),合并新数据时要不压垮用户当前正在进行的编辑。
错误做法: 重新加载整个页面。所有本地编辑都没了。
正确做法: 只更新用户当前没有在编辑的字段。如果他们正在输入标题,就别动它。如果他们没碰截止日期,就用服务器的数据更新它。
给开发者的建议: 从数据库拉取最新数据时,做合并处理:保留本地编辑,更新其余所有内容。在真实框架里,这通常只需要两行代码。测试方法:在一个窗口里编辑一个字段,同时在另一个窗口里编辑另一个字段。两处改动都应该保留下来。
修复三:清楚地展示事实
当出现冲突或数据过时时,把它展示出来。不要藏着掖着。
例如:
- “这个任务已被其他人删除。撤销?”
- “你在输入的时候,有人往这个清单里添加了三项内容。[查看新增内容]”
- “你现在看到的是2分钟前的版本。刷新以查看最新内容。”
给开发者的建议: 加载时检查你展示的数据是否带有时间戳。如果它已经超过30秒,而用户又试图编辑,就显示一条警告并重新拉取数据。如果你展示的是一个列表,提供一个”刷新”按钮——把它做成一个合理的用户操作,而不是一种故障提示。
实时协作被彻底解决之后,是什么样子?
黄金标准是这样的:你和我编辑同一份共享文档,我在输入,你能看到我的光标在移动,文字瞬间出现在两块屏幕上,谁的工作都不会丢失。要做到这一点,需要三样东西协同工作:
- 每次按键都立即保存 — 不等按钮点击。
- 冲突按规则解决 — 如果我们俩同时编辑同一个词,系统会选出一个赢家(通常是后写入者优先,或者弹出冲突提示)。
- 更新即时到达 — 通过WebSocket、Server-Sent Events,或者像Firebase这样能主动推送的数据库。
大多数应用第一天并不需要做到这个程度。先从增量保存开始(修复一)。当有两个人同时使用时,再加上轮询加合并(修复二)。只有当冲突真的造成困扰时,才有必要加上即时推送。
上线前该如何测试实时协作?
上线前,在两个浏览器窗口里跑三项测试:同时保存测试、过时数据测试,以及刷新测试。每一项都有明确的通过或失败标准。
测试一:同时保存测试
- 在两个浏览器窗口里打开你的应用。
- 在窗口1里编辑字段X并保存。
- 在窗口2里编辑字段Y,紧接着保存。
- 刷新两个窗口。
- 通过: 两处编辑都还在。失败: 有一处编辑不见了。
测试二:过时数据测试
- 在窗口1里打开应用,先别动它。
- 在窗口2里做一次较大的改动(增删一行、修改标题)。
- 回到窗口1(仍显示旧数据)。
- 尝试编辑窗口1里那份过时的版本。
- 通过: 你会收到警告,或者数据被干净地合并了。失败: 你覆盖了窗口2的改动。
测试三:刷新测试
- 让页面上有一份实质性的进行中工作(一份填了一半的表单、一条草稿消息)。
- 刷新页面。
- 通过: 你的工作还在。失败: 它没了。
应该每次按键都保存,还是等用户点保存按钮?
每次按键都保存。这一个决定就能让你完成实时协作80%的工作——剩下的就是把它可视化,并处理好碰撞。
现在的用户已经默认应用该有这个行为。Gmail、Google Docs、Slack——每一款现代应用都是这么做的。你的应用也该如此。
要先做的一件事: 让每一次改动都自动保存。显示一个小小的提示(“保存中……“,然后消失)。观察当两个人同时编辑时会发生什么。如果有一个人的改动消失了,那就是你接下来要修的问题。一次解决一个问题,胜过试图在第一天就把协作做到完美。