七月份忙的狗一样,还一度要 12 点赶回公司修 bug,真累。。。

入职以来的这两个月,一直在业务层摸索,苦于没有文档,rpc 协议、单件服务的启动、为热更新去准备的环境变量等等,都要一一在摸索中问出来。幸而,终于走过了这一段。这也促使我开始思考,skynet、gevent、golang 之间,各自的优点和缺点是什么。

先从 skynet 说起,最直观的感受是,skynet 在 lua/c 里几乎完全复制了 python 的标准库。上至打包用的 serialize、json 库,下至打印字典 (table) 的 prettyprint,都实现了一遍。所以,使用过程还是比较愉快的,前提是你找到了这个库。。另外,底层 rpc 使用了 Google 的 protobuf 负责打包,实际使用中发现,我们基本就用 int32 和 string,枚举和布尔值也用的不多。其实就跟上家公司一样,对 rpc 只要支持字符串、整型、数组和自定义结构体就够了。protobuf 单个结构的定义过程和标准的一样,不同的是协议的定义。使用了一个 protolist 文件标记所有的协议,然后又分默认写法、请求为空写法、回复为空写法。与其支持这么多东西,倒不如直接全部自己定义,不搞默认值那一套了。

最令人蛋疼的,是服务之间的交互。起服务的时候,要记得在两个地方注册,因为 skynet 有集群模式和单机模式的区别。然后服务之间交互,需要手动写 call,告诉 skynet 我调的服务叫什么名字,如果没有名字就传一个固定的 id 进去识别。然后写完逻辑,返回数据的时候,要记得用 skynet retpack 一下,要不发出请求的服务就会一直卡在等待状态,而且没有任何报错提示。如果说受限于 lua 的语法,不方便增加新的写法以表明跨越了协程,至少我写完 call,skyne 返回数据的时候能打包吧?据说,这个支持是为了在函数体中间给 skynet 返回结果,再继续做其他事用的。但是这种想象中的灵活写法,为业务层开发带来了隐含的信息假设,而项目又没有文档说明这个事情。

对于 skynet 的 agent 模式,我还是比较欣赏的。但是和 gevent、golang 不同,跨 agent 之间的通信需要显式调用底层的接口,这就比较疼了。我希望 agent 服务能够有自己的超时机制,如果跨 agent 的调用过一段时间没有返回,至少你给我个提示信息来查一下嘛。往大里做的话,还需要给 agent 的相互调用添加监视,控制调用频率、是否重试等等。作为分布式系统,其实是将单进程 / 单线程的复杂性转移到整个架构部署的复杂度上来,这样每个服务会变简单,复杂性就体现在服务的交互过程和服务本身的维护上了。

golang 没有显式的标记一个服务,任何函数只要用 go 这个关键字去跑,就可以作为一个协程单独跑起来。交互的时候,使用了 channel 这种东西去告诉底层,发生了一个跨越边界的消息传递。而 gevent 则是通过 spawn 这个函数去跑起一个新的 greenlet 作为协程。由于不会跨越线程 / 进程边界,gevent 也不需要对消息传递进行特殊处理,只要按普通函数的调用来做就好了。

综合来看,显式指定并行的边界,有助于底层并发的优化,但是没有合适的语法糖和基础设施,写业务的时候就会觉得好累了。