记一次处理“跨终端数据同步”
需求:
终端A,终端B同时共享一个list或数个list。终端A更新list时,终端B能在较短时间自动同步更新。
技术基础:
前后端使用websocket协议进行双向通信。(ajax轮询的方案服务器压力过大,同步的延迟时间也较长)
项目现状与方案:
前端为保证各个页面数据共享,减少部分页面响应延迟在全局存在数据模型(companyModel, typeModel, customerModel 等)。有缓存数据,必然会出现与服务器数据保持不一致的问题。在老项目里靠刷新页面的同步方式如今是断然不能在移动端或重数据操作的项目上进行。
前后端业务服务已经推进了数个迭代。推倒现有http协议而全部改有websocket工作量将十分的大,意味着完全重组现有数据请求逻辑。可行的方案只能是两个相结合。仍然由http请求承担数据拉取的工作,前端数据接收的流程不变。何时拉取新的数据,拉取哪一个接口的数据。这些问题交由websocket来通知。实现上服务端将在http的响应流程中增加拦截中间件,并判断是否需要推送更新信号。
数据同步,便会涉及是增量同步还是全量同步的问题。在目前的项目现状中,希望保持原有数据获取流程不变。方便的自是针对不同的接口来进行全量数据同步。当然,这也是一个优化点。要想高效的节省网络请求,终端与服务器需校对数据的差异,在通知时即详细告知具体差异和差异的内容。
实现与细节:
我们了解了方案之后便可开始相关的整理工作。
搭建基础的websocket通信:
前端的构建十分简单,不再赘述。
后端的搭建有许多十分成熟的库如:ws, socket.io, websocket 等。诸位根据自己的需要来选择。我在筛选之后选择了ws这个库来实现nodejs部分的websocket服务。
使用websocket我们都熟知是在http的请求发起之后建立了websocket通信。那么中间发生了什么?
关注http。http协议允许进行协议变更,存在upgrade机制。
这个http请求明确的告诉了自己的类型 Upgrade。 Upgrade的目标协议websocket。
服务器在收到这条请求后将决定是否变更当前协议至websocket。
nodejs中的代码实现可以参考ws模块的例子来实现。
https://github.com/websockets/ws#api-docs
websocket内容通信约定:
{ "code":0, "msg":"0k", "Op":"put", "Model":"company" }
Op即数据操作类型;
Model即是哪一个数据模型发生了更新。
前端代码,在解析出推送数据后,便执行相应接口的fetch操作。刷新数据模型中的对应列表。以达到同步服务器数据的任务。
后端需要决定在何时推送什么样的数据,以及推送给谁。
1.推送给谁
http的通信识别身份一般由cookie或者header携带的字段实现,标示出来自于哪一位用户。websocket通信也一样。在创建时它是http带来,身份信息自然在http上。socket创建后一直在服务器的内存中保留。标示身份类似与赋值一个属性给一个对象。标示了身份,便可以通过业务系统关联相关的socket。boardcast 给每一个关联的socket。
(Risk: socket如果过多,物理机的socket是有上限,服务本身的内存问题也不可忽视)
2.何时推送,推送什么样的数据
我的http服务框架由koa搭建。给koa增加一个中间件拦截往来的http,依据method和url判断出是哪一个模型发生了操作,然后广播。
export async function updateClientModel(ctx: Koa.Context, next: Function): Promise<any> { const method = ctx.method; const { header, loginInfo } = ctx; const { token } = header; if (!loginInfo) { return next(); } const { company } = loginInfo; if (method === "GET") { return next(); } await next(); // 仅请求成功时操作 if (!ctx.body || ctx.body.code != 0) { return; } // 检查是否为纳入推送范围的数据 let { Op, Model } = getOpAndModel(ctx.url, method); if (!Op || !Model) { return console.log("无法推送该API数据更新", ctx.url, method); } // 将来需要合并多次推送请求。 boardCast({ hasSelf: true, Op, Model, companyId: company.id, token }); }
总结:
基于此,当多终端数据需要同步时便可在秒级响应同步信息。
本文主在介绍实现思路,并未就详细过程粘贴实现代码。编程是个细致活,过程实现需要考虑很多细节。投向生产的代码,简单的原理却不是简单的事情。
往诸君共勉!