七月份忙的狗一样,还一度要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也不需要对消息传递进行特殊处理,只要按普通函数的调用来做就好了。
综合来看,显式指定并行的边界,有助于底层并发的优化,但是没有合适的语法糖和基础设施,写业务的时候就会觉得好累了。
并行的边界