« 用 gdb 分析 coredump 的一些技巧 | 返回首页 | epoll 的一个设计问题 »

Lua 表的差异同步

最近同事碰到的一个需求:需要频繁把一组数据在 skynet 中跨网络传递,而这组数据实际变化并不频繁,所以做了大量重复的序列化和传输工作。

更具体一点说,他在 skynet 中设计了一个网关节点,这个网关服务可以负责把一条消息广播给一组客户端,每个客户端由内部的一个 uuid 串识别,而每条消息都附带有客户端 uuid 列表。而实际上这些 uuid 列表组有大量的重复。每条广播消息都重复打包了列表组,且列表组有大量重复信息。

一开始我想的方法是专门针对这个需求设计一组协议,给发送过的数据组编上 id ,然后在发送方和接收方都根据 id 压缩通讯数据。即,第一次发送时,发送全量信息,之后再根据数据变化发送差异;如果完全没有变化,则只需要发送 id 。

之后我想,能不能设计一种较为通用的差异同步方法,可以在跨节点传递数据组的时候,避免将相同的数据重复传输,而采用差异同步的方法同步对象。

晚饭后,我试着实现了一个这样一个简单的模块。

https://github.com/cloudwu/syncobj

我们可以先在通讯的两端建立对等的通道,构造数据的一端用 syncobj.source() 创建一个发送通道;而同步数据的另一端用 syncobj.clone() 创建一个接收通道。

当我们在发送端需要构造一组数据的时候,利用 obj = source:new() 创建出一个依附在这个通道上的对象 obj;为了简化实现,这个对象是一个单层的 lua 表。

我们可以在发送端像一个普通表一样访问这个对象,当我们需要同步到另一端时,可以用 diff = source:diff(obj) 生成一个和上个版本之间的差异数据;然后可以把 patch 作为普通的对象序列化发送。

而接收端用 cobj = clone:patch(diff) 就可以还原这个对象。由于 diff 中只是每次对象变化的差异数据,所以数据量会比直接全量传输 obj 要小。

如果通讯过程出了问题,我们还可以用 diff = source:reset(obj) 生成一个全量数据,而接收端则可以无视差异包和全量包的差别,都可以用 clone:patch(diff) 还原数据。


在内部协议中,实际上是给每组数据附加了一个单调递增的 id 号的,这个 id 号包含在 diff 数据中,所以 clone:patch 可以正确识别出如何还原数据。source:reset 生成全量包的过程,其实是赋予了对象一个全新的 id 。

当然,这个同步方案长期工作的话,就会存在一些对象已经在 source 端废弃,lua 的 gc 虽然会回收那些不再使用的对象内存,但 clone 端并不知晓;另外 source:reset 也会在 clone 端制造垃圾(老的版本已经废弃)。

所以,还提供了 removeset = source:collect() 返回一个不再使用的 id 列表;只要定期调用,把返回值传递给 clone:collect(removeset) 就可以让接收端清理不用对象。

Comments

@lizs
pub/sub跟diff同层的话,那就是消息数量的问题。
不同层,那就是不同层面,一般pub/sub可做上层,diff在下层做差异同步。

利用pub/sub消息模型就能完美解决这种需求。感觉云风绕远了。

用户名变成Anonymous,不太良好。rancode

diff差分是很不错的用法,不过没有做分级吗?类似linux写文件的懒人触发模式。
如果数据量多又超大,而且实时性又不高的话 ,可以积累到一定量后才集体压缩发送。
加一个接口:
source:comp(obj []) ,这样使用场景覆盖面更好.
以上,拙见。

大神不准备研究研究typescript吗?

好主意,看到标题我在想怎么做,开始也是想用id, 后来看到你用diff,激动了

Post a comment

非这个主题相关的留言请到:留言本