August 12, 2014

STM 的简单实现

STM 全称 Software transactional memory

在前年的项目里,我制作了一个类似的东西。随着 skynet 的日趋完善,我希望找到一个更为简单易用的方法实现类似的需求。

对于不常更新的数据,我在 skynet 里增加了 sharedata 模块,用于配置数据的共享。每次更新数据,就将全部数据打包成一个只读的树结构,允许多个 lua vm 共享读。改写的时候,重新生成一份,并将老数据设置脏标记,指示读取者去获取新版本。

这个方案有两个缺点,不适合实时的数据更新。其一,更新成本过大;其二,新版本的通告有较长时间的延迟。

我希望再设计一套方案解决这个实时性问题,可以用于频繁的数据交换。(注:在 mmorpg 中,很可能被用于同一地图上的多个对象间的数据交换)

一开始的想法是做一个支持事务的树结构。对于写方,每次对树的修改都同时修改本地的 lua table 以及被修改 patch 累计到一个尽量紧凑的序列化串中。一个事务结束时,调用 commit 将快速 merge patch 。并将整个序列化串共享出去。相当于快速做一个快照。

读取者,每次读取时则对最新版的快照增加一次引用,并要需反序列化它的一部分,变成本地的 lua table 。

我花了一整天实现这个想法,在写了几百行代码后,意识到设计过于复杂了。因为,对于最终在 lua 中操作的数据,实现一个复杂的数据结构,并提供复杂的 C 接口去操作它性能上不会太划算。更好的方法是把数据分成小片断(树的一个分支),按需通过序列化和反序列化做数据交换。

既然序列化过程是必须的,我们就不需要关注数据结构的问题。STM 需要管理的只是序列化后的消息的版本而已。这一部分(尤其是每个版本的生命期管理)虽然也不太容易做对,但结构简单的多。

阅读全文 "STM 的简单实现" »

August 02, 2014

Unity3D asset bundle 格式简析

Unity3D 的 asset bundle 的格式并没有公开。但为了做更好的差异更新,我们还是希望了解其打包格式。这样可以制作专门的差异比较合并工具,会比直接做二进制差异比较效果好的多。因为可以把 asset bundle 内的数据拆分为独立单元,只对变更的单元做差异比较即可。

网上能查到的资料并不是官方给出的,最为流行的是一个叫做 disunity 的开源工具。它是用 java 编写的,只有源代码,而没有给出格式说明(而后者比代码重要的多)。通过阅读 disunity 的代码,我整理出如下记录:


阅读全文 "Unity3D asset bundle 格式简析" »

July 30, 2014

skynet 中如何实现邮件达到通知服务

skynet 中可以独立的业务都是以独立服务形式存在的。昨天和同事讨论如何实现一个邮件通知服务。

目前大概是这样的:有一个独立的邮件中心服务,它可以处理三条协议:

  1. 向一个 mailbox 投递一封邮件。
  2. 查询一个 mailbox 里有多少封邮件。
  3. 收取 mailbox 里指定的一封邮件。

用户读了多少邮件没有放在邮件中心,而是记在玩家数据里的。

用户的界面上需要显示是否有几封未读邮件,如果有新邮件达到,这个数字会自动变更。你可以想像成 iOS 上的那种带数字的小红点。

当然,在 skynet 的设计惯例中,每个用户在服务器上有一个 agent 代理,所以我们不单独考虑和客户端数据交互的问题,而只用考虑 agent 如何和邮件中心的交互。

现在的做法是,在用户上线的时候,就去邮件中心查一次,比较邮件数量后知道是否有新邮件,然后推送给玩家。

在玩家特定的操作后,比如进出副本等,都会重新查询一次。如果玩家在一个场景停留太久,客户端也会定期发起查询请求。

如果邮件必须在新邮件达到时,立刻通知给玩家怎么办呢?那么系统中另外有个用户中心的服务。邮件服务可以把消息推送到那里;用户中心发现玩家不在线,就扔掉消息;如果在线就做消息推送。


我觉得这个方案有那么一点点不好,所以提出了我的想法。

阅读全文 "skynet 中如何实现邮件达到通知服务" »

July 28, 2014

sproto 的实现与评测

这个周末,我实现了上周设计的简化版 protocol buffers 协议 ,并重新命名为 sproto

在实现过程中,发现了许多编码格式上可以优化的地方,所以一边实现一边做调整,使结构更适合编码和解码,并且更紧凑。

做了如下改动:

由于这个东西主要 binding 到 lua 这样的动态语言中使用,所以我不需要按 Cap'n Proto 那样,直接访问编码后的数据结构(直接把数据结构映射为 C/C++ 对象),所以数据对齐是不必要的。

编码时的 tag 如果要求严格升序也可以更快的处理数据,减少实现的复杂度。数据段也要求按持续排列,且不准复用。这样可以让数据中有更多的 0 方便压缩。

把 boolean 数组按位打包的意义也不太大(会增加实现的复杂度)。

暂时先不实现 64bit id 的类型。(以后再加)

最终的 Wire Protocol 是这样的:

阅读全文 "sproto 的实现与评测" »

July 24, 2014

设计一种简化的 protocol buffer 协议

我们一直使用 google protocol buffer 协议做客户端服务器通讯,为此,我还编写了 pbc 库

经过近三年的使用,我发现其实我们用不着那么复杂的协议,里面很多东西都可以简化。而另一方面,我们总需要再其上再封装一层 RPC 协议。当我们做这层 RPC 协议封装的时候,这个封装层的复杂度足以比全新设计一套更合乎我们使用的全部协议更复杂了。

由于我们几乎一直在 lua 下使用它,所以可以按需定制,但也不局限于 lua 使用。这两天,我便构思了下面的东西:

阅读全文 "设计一种简化的 protocol buffer 协议" »

July 23, 2014

给 skynet 增加 http 服务器模块

一直没给 skynet 加 http 协议解析模块是因为这个领域我不熟悉,而懂这块(web 开发)的人很多,随便找个人做都应该比我做的好。世界上的 web 服务器实在是太多了,足见做一个的门槛也不高,我也没什么需求,所以就这样等着有需要的人来补上这一块。

但这两天实在等不了了。我们即将上线的一款游戏,运营方要求我们提供一组 web api 供运营使用。固然我们可以单独写一个进程挂在 nginx 的后面,并和 skynet 通讯,但游戏开发组的同学觉得不必把简单的事情做的这么绕。监听一个端口提供 http 协议的服务又不是什么特别麻烦的事情,结果就打算直接在 skynet 里提供。

阅读全文 "给 skynet 增加 http 服务器模块" »

July 15, 2014

skynet 消息服务器支持

周末终于把上周提到的 短连接服务 实现了。由于本质上是一个消息请求回应模式的服务器,并没有局限于长连接还是短连接,所以不打算用短连接服务来命名。

用户的登陆状态不再依赖于是否有连接保持,所以登陆服务也顺理成章的分离出来了。

用户先去统一的登陆服务器登陆,获得令牌,然后去游戏服务器连接握手。如果用户和游戏服务器的连接断开,只需要重新用令牌握手即可,不必重新回登陆服务器登陆。

当然用户也可以重新登陆,清除登陆状态,完成一个传统意义上的下线再上线的过程。

阅读全文 "skynet 消息服务器支持" »

Misc

Categories

Archives

Recent Comments