网站内容更新中(实际上Teambition的数据同步架构升级(附补充说明))
优采云 发布时间: 2021-11-23 19:05网站内容更新中(实际上Teambition的数据同步架构升级(附补充说明))
【补充说明】
这是2015年底的答案,其实Teambition的数据同步架构在2017年初就已经全面升级了,详情请看:
[原答案]
这些问题确实是我在开发大型web应用时遇到的非常棘手的问题。我想分析一下我在这里遇到的场景,希望大家能指点一下各种解决方案的优缺点。
首先,存先生提到的四个问题,在大多数情况下是不能单端解决的。在最佳场景下,前端和后端应该一起解决这些问题。比如很多人提到的websocket,长轮询。这些技术确实可以在一定程度上保证数据的实时性,但是在复杂的生产环境中,任何一个环节的错误都会导致代码无法按预期工作。所以即使你使用 websocket 和长轮询,你也可能会错过来自服务器的推送消息。在大型Web应用中,通过刷新页面(用户体验、资源加载时间)来重新同步数据是非常昂贵的。因此,非常有必要确保整个应用程序具有高容错性。
1. 所以第一个问题,我觉得李泽楷提到的解决方案很有价值。推特后端与websocket的设计我认为是一个非常好的数据同步解决方案。Teambition 后端也使用了类似的解决方案。采用架构进行数据同步,前后端配套方案开源:teambition/snapper-consumer·GitHub(基于engine.io的客户端socket同步工具)、teambition/snapper-core·GitHub(基于 redis 和 thunks nodejs 套接字推送方案)。
2. 假设现在我们可以正确获取数据并且可以愉快的渲染在页面上,那么我们很快就会遇到存先生提到的第二个问题,客户端中数据采集连接更新的问题。具体来说,teambition应用中有这样一个场景:
上图是teambition中的一个任务板。左边的箭头是任务分组,右边的箭头是整个项目的动态。我们现在假设这样一个场景:我在第一个 Opened 组中创建一个新任务。然后点击新建任务按钮后,会向服务器发送一个post请求,服务器会返回收录该任务所有信息的数据。同时socket会推送一条消息,收录的信息大概就是右边的项目动态。还有一条数据,动态对应的id是xxx。这个时候我们遇到的场景就是在左边的任务分组和右边的动态侧边栏同时渲染post返回的任务数据。teambition目前采用的方案是基于用户行为/网络请求/sockets,同时对任务进行分组,并在应用中动态建立相应的骨干集合。一个任务被保存为两个集合中的两个模型。然后不同的集合在添加新模型后会通知对应的View渲染新添加的数据。
这个解决方案看起来不错。它解决了不同视图渲染行为的差异,并建立了集合与对应视图之间的映射关系。每个View只需要*敏*感*词*与之相关的集合的对应事件,降低了模型和View的复杂度。花费。
3. 数据变化场景:
一旦数据开始变化,灾难就开始了。假设我在左边的任务栏中修改了一个任务的标题,我怎么知道右边的动态变化呢?我们知道主干模型提供了原生的变更事件支持。在此基础上,teambiton 做了一个数据同步工具,叫做仓库(他们没有放在github上)。仓库所做的很简单,抽象出所有数据类型(模型)的new、change、remove事件。相应的,teambition程序中的model默认会根据id*敏*感*词*这些事件,然后进行处理。通过这种方式,可以在任何特定模型上创建相应的侦听器来处理这些更改。因此,即使模型不在同一个集合中,因为它们的 id 相同,它们在更改/删除事件后的行为也会相同。而且仓库还抽象了一些处理socket事件的方法。这时候后端的配合就很重要了。当一些模型被移除时,socket会推送相应的事件来告诉前端哪个集合发生了变化。更改的行为和相应的模型 ID 收录在套接字消息中。中间。
到目前为止,这种设计似乎没有致命的问题。每个视图都有自己想要对应的集合。当add/remove/change等事件发生时,也可以得到相应的通知,采取不同的行为进行渲染。
但是,总会有后端无法推送消息的场景。在这些场景下,这个数据层的适应性非常弱。例如,有这样一个视图/集合:
这个视图/集合用于显示用户最近需要做的一些事情。这个集合涉及的变化特别复杂(无论是任务完成,日期改了,优先级改了,重复性计划/重复性任务创建),所以后端没有推送相应的消息给客户端一个相应的变化,客户端需要处理数据变化时的情况。
目前teambition处理的步骤大致如下:
还有一个更复杂的场景:直接在这个View上操作,会导致集合自身的model发生变化,变化后必须采取同样的措施来检查数据在哪个组。
那么问题来了。假设这个集合上一个model的变化也会触发仓库的通知(几乎肯定会触发),那么既然仓库会再次触发自己的监控回调,那么这里至少会触发2次这个监控回调.
这还没有结束。如果在change回调中还有其他与特定业务相关的逻辑触发了模型的另一个改变(一个很常见的场景是同一个模型的值在不同的视图中显示不同的值,比如日期显示),那么将触发新的更改事件。
当你认为这个级别的混乱结束时,有可能新的change事件被仓库广播到不同的采集后,每个不同的采集都会触发一个新的change事件,根据自己的业务再次通过仓库广播。对每个集合,来回迭代,直到数据稳定。
其实熟悉angular的同学已经注意到,angular root的dirty check几乎是一样的,更痛苦的是这个机制没有TTL,被*敏*感*词*的listener需要手写手工,这很复杂。上升到另一个维度。
其实看到这个,肯定会有人站出来问,为什么同一个数据要存储多个副本呢?为什么我们不只保存一份?所以每次有新的变化时,都会修补到现有的数据上。
我这里还有一个例子:
teambition/teambition-mobile-web · GitHub,这是teambition 的移动单页应用程序。它采用了完全不同的策略,所有数据只保存一份。对于不同的集合(这只是一个概念,不是backbone里的集合),只需要引用不同的model(不是backbone里的model),而不是维护自己的model,那么你只需要更新数据就可以了补丁被丢弃。由于所有集合都只是引用,它们会相应地自动更改。由于移动端应用使用的是Angular,所以不需要做任何事情,视图已经更新了。如果使用backbone,道理也差不多,只是在视图中手动注册相应的*敏*感*词*器即可。看起来很简单,层次清晰,逻辑简单,易于维护。
4. 删除数据。
但是,在这种情况下,数据的删除和添加将变得非常麻烦。幸运的是,在大多数场景下,socket 会推送对应的集合添加/删除事件,所以大多数时候你不需要担心添加行为。但是对于一些sockets无法触及的集合,具体的逻辑会很难抽象。由于没有使用仓库(由于没有更改带来的麻烦,仓库的优点没有了),我只能在socket层抽象出各个模型的add/remove/change的监控,然后手动这些事件在维护的集合中处理。
更大的问题在于移除。
一般情况下,View引用数据后,会建立对View-采集的引用。大多数情况下,当出现移除行为时,我只需要移除集合中对应的数据,视图就会自动正确渲染。的结果。但重要的一点是,也许我知道一个模型被删除了,但我不知道哪些集合引用了它,所以我必须监视不同集合中涉及的数据的所有套接字。添加remove change事件(替代那部分仓库功能),然后手动处理这些复杂的逻辑关系。
更恶心的是,在某些特定的View中,当涉及到对数据进行重新排序时,View会对获取到的数据建立新的引用关系。我发现没有办法在不使用事件的基础上取回View。正确的数据。而且如果使用事件,几乎违背了我在这个行为中保存所有数据的初衷。这个时候只能采取偷懒的方法了。每次有数据变化,我都会自动让View从对应的数据层重新取回数据(用zone.js实现),这样即使发生重排序这种事情,数据也会一直保持——迄今为止。
水平有限,答案乱七八糟,大家随便看看吧。