引言:结合工作实践和自己的一些思考,今天和大家分享在线Excel的协作方案。
如果你对在线文档的主题感兴趣还可以看这两篇文章:如何实现多人协作的在线文档,在线Excel存储方案
多个用户同时操作一个Excel文件。 场景中的实体有:用户、Excel。其中用户又分为「拥有者」、「阅读者」、「协作者」 拥有者:创建Excel的用户 阅读者:可以查看Excel的用户 协作者:可以编辑Excel内容的用户
协作的关键过程有: 「用户打开Excel」 「用户编辑Excel」 「用户退出Excel」 「用户删除Excel」 在所有的关键过程中,既需要客户端往服务端发送消息,也需要服务端往其他客户端广播消息。而且当用户频繁修改Excel内容时,为了保证每个人修改的内容实时同步到其他客户端,会有频繁的网络传输。这很像一个聊天室。在这种场景下长链接是比较合适的方案,「WebSocket」是实现长链接的常用方案之一。
和聊天室不同的是,聊天室更倾向于AP模型;在线Excel更倾向于CP模型,因为消息丢失或顺序不对,会导致文件内容错误,后果很严重。
以上这些关键过程的实现都需要知道一个Excel文件有多少人正在阅读、编辑。记录当前Excel的在线用户,才能在Excel内容变化时把变化的内容广播给他们。
当前有「多少人在协作」是实时变化的数据,而且需要频繁、高效的访问,使用redis存储比较合适。我们可以使用redis的Hash类型存放,Excel的唯一ID作为Key,把在线用户、打开文件时间等信息存储起来。
hset excel_id user_id "打开时间"
其他的存储类型,或redis的其他存储方式都是可以的。
WebSocket连接建立之后,客户端会和服务端的某一个副本「保持」长链接。用户打开Excel或者修改Excel内容,都需要根据当前excel_id,去redis中查找「在线用户」,然后发送「广播消息」,把状态变化同步到所有客户端。此场景下广播消息的发送有三种实现方案:
excel服务的所有请求,根据exce_id路由,这样同一个exce_id上的所有长链接都会在同一个副本上。需要发送广播消息时,当前exce_id的所有长链接都在此副本上,代码层面不用做任何特殊处理。
优点:实现简单,不侵入业务代码 缺点:
需要发送广播消息时,Excel所有副本都根据exce_id从redis中获取在线用户,对比当前副本持有链接的Sessions中是否存在此用户信息。如果存在则向此链接发送广播消息,如果不存在就忽略不做处理。
有广播消息时对其他所有副本发送通知,可以采用消息队列来实现。让所有副本订阅某频道,有广播消息时,通过消息队列通知到其他副本。 除了消息队列还可以根据应用ID调用云平台的接口返回所有pod的VIP,然后根据VIP给所有副本发送请求。
建议采取消息队列的方案,减少对云平台的依赖。
优点:
缺点:
由注册中心管理excel、用户以及副本的长链接关系,需要发送广播时,根据excel_id获取所有需要广播副本的vip/Host,调用其服务给客户端推送广播消息。
优点:
缺点: 需要引入注册中心,增加了系统的复杂性,增加了运维成本
当某用户打开Excel时,需要同步此用户的信息到所有正在阅读或协作此文档的客户端。这时的交互流程如下。
用户对Excel的操作类型特别多,比如修改单元格内容、修改行宽、增加列、合并单元格等等。我们把用户对Excel的所有操作归为两类:1.「修改单元格内容」 2.「其他操作」
对于修改单元格内容的操作我们采用互斥逻辑。互斥逻辑分为锁定、取消锁定、发送内容三部分。
如何判断取锁成功? 「excel_id和当前单元格坐标」不存在时说明没有用户操作此单元格,取锁成功。 「excel_id和当前单元格坐标」存在时,可以把用户ID当作锁的Value值,比较Value是否为当前用户,如果是也认为取锁成功,可以修改单元格内容。
加锁时设置默认超时时间,防止单元格内容被永远冻结。
此外还存在间隙问题:用户在客户端选中一个单元格后,“请求到服务端加锁,然后发送广播到其他客户端“ 的间隙时间较长,这中间如果有用户快速修改了同一个单元格的内容,会存在内容被覆盖 或者 修改失败两种风险。我们可以根据自己使用Excel的业务场景,决定允许当前状况发生,或者通过优化取锁逻辑来处理。
对于其他修改采用覆盖逻辑,时间靠后的操作,覆盖靠前的操作。
采用覆盖逻辑的原因:用户的很多操作无法做合并。比如:A用户把单元格第一行高度由30px调整为50px;B用户把第一行高度由30px调整为40px。此时程序无法按照预期设置第一行单元格的高度
当一个用户退出Excel时,需要同步这个人的信息到所有正在阅读或协作此文档的客户端。用户主动退出操作包含:点击页面左上角的回退按钮、浏览器的回退按钮、关闭浏览器等。还有可能因为异常的网络中断导致用户退出,所有的退出操作对应到服务端,就是WebSocket链接断开。可以采用WebSocket服务端的close事件当作用户退出的标识。交互流程如下:
此方案并没有解决协作中的所有问题,除了上文中已经提出的注意事项外,还有很多地方要注意。比如:遇到合并函数操作时,如何解决多个人操作的冲突?有人在修改一个单元格时,别的用户有合并单元格操作时如何处理?多个人同时修改一个单元格的逻辑能否优化? 消息传输层的问题尤其重要,需要单独说一下:
今天详细和大家介绍了,在线Excel协作的一些实现方案和关键流程,希望能起到抛砖引玉的作用。喜欢在线协作的同学可以一起来交流讨论。