从零打造node.js版scf客户端
node.js是一个划时代的技术,它在原有的Web前端和后端技术的基础上总结并提炼出了许多新的概念和方法,堪称是十多年来Web开发经验的集大成者。转转公司在使用node.js方面,一起走在前沿。8月16日,转转公司的FE王澍老师,在镜泊湖会议室进行了一场主题为《nodejs全栈之路》的讲座。优秀的语言、平台、工具只有在优秀的程序员的手中才能显现出它的威力。一直听说转转公司在走精英化发展战略,所以学习下转转对node.js的使用方式,就显得很有必要。
对于大多数人使用node.js上的直观感受,就是模块、工具很齐全,要什么有什么。简单request一下模块,就可以开始写javasript代码了。然而出自58同城的转转,同样存在大量服务,使用着58自有的rpc框架scf。scf无论从设计还是实际效果,都算得上业内领先。只不过在跨平台的基础建设上,略显不足。从反编译的源码中,可以找到支持的平台有.net、java、c、php。非java平台的scf版本更新,也有些滞后。之前还听说肖指导管理的应用服务部,以“兼职”的方式开发过c++版客户端。而且也得到umcwrite等服务的实际运用。所以node.js解决好调用scf服务,是真正广泛应用的前提。这也正是我最关心的问题。
王澍老自己的演讲过程并没有介绍scf调用的解决方案,但在提问环节中,进行了解答。我能记住的内容是,目前的采用的方案是使用node-java模块,启动一个jvm进程,最终还是在node.js的项目中编写的java代码,性能尚可接受,但使用中内存占用很大;王澍老师也在尝试自己使用c++开发模块来弃用node-java。
这确实很让我很失望,我所理解的node.js应该是与性能有关的部分,几乎全部是c++编写的。之前肖指导要求发布公共服务,改写成使用scf提供的异步方式执行,借那次机会,我也阅读了一部分反编译的scf源码。感觉如果只是解决node.js调用scf的问题,不应该是个很难的事情。像管理平台、先知等外围功能,可以后期一点点加入。正巧我一直在质疑自己是不是基础差的问题,干脆写一个node.js版的scf客户端,来试试自己的水准。
结合自己之前对node.js的零散知识(其实现在也很零散)。对这次实践提出如下的一些设计要点:
1、序列化版本使用scfv3,虽然难度应该是最大的,但应该能在较长的时间内避免升级序列化版本的琐事。
2、使用管理平台读取配置,禁用scf.config类似的本地配置。想想之前许多部门,推进禁用线上服务直连的过程,就觉得很有必要(管理平台也用线下环境,线下调试根本不是阻碍)。
3、客户端支持全类型,之前偶尔听说了c++版客户端不支持枚举类型,使得有些服务只能调整接口。
4、c++使用libuv库,具备跨平台开发、调试能力。c++版客户端听说只支持linux平台。
5、只提供异步接口,这是当然的,不然node.js就别想用了。
现有的c++客户端,在3、4、5上与我的设想不符合,所以我决定亲自编写。
先是搜了本介绍libuv的pdf——《An Introduction to libuv》,看了几天,对libuv的使用方式有所了解,用上的只有tcp相关接口。(比起java,node.js的资料还是少,介绍的也少有深入的,像这样的底层类库,资料就更少了)
在58作为rd,如果不是做ios,是少有配macbook的员工的。所以我本次是在windows上编写的。不得不说node.js就是霸道,自动安装时,默认全部安装最新的版本。这样在windows平台上编译c++时,就要求visual studio不能低于2015。
网上搜索c++开发node.js模块,基本总是能找到那个addon的示例。可能是由于v8引擎的接口也有过变化,addon的示例使用的类型、接口也存在几种,终于还是试出了自己可以编译过的了。
首先在addon的基础上,写个运用libuv连接tcp的逻辑,一旦试通了,就可以一点点抄写反编译的scf客户端源码了。
在开发过程中,我的设计也进行了一些修改:
1、反序列化逻辑,通过tcp连接,交由一个java程序来执行(基于netty开发)。由于反序列化时,scf的二进制数据是没有足够的类型信息的。大体上,当读取到一个typeid时,如果本地没有对应的类型信息,完全不知道下一个字节是做什么用的。(我其实只希望得到一个类似多叉树的嵌套格式,也做不到。)如果非要使用c++来执行反序列化,也并非不可能。需要将scf反序列化用到的类型信息,整理成一种新的数据格式,存放于c++程序的内存中。为此需要开发一个输出类型配置数据的java离线工具,node.js模块需要开发:读取这个类型配置文件到内存,再将scf反序列化的逻辑使用c++抄一遍。综上来看,使用一个java的反序列化辅助进程,可以在性能几乎无损的情况下,极大的减少了开发量,同时避免了许多反序列化过程中的bug。这不正是一个极简的微服务嘛。
2、javascript入参对象中,需要自带scf序列化相关的类型信息,这样就能在全类型的支持scf对象了。当然我也设想过,有没有机会将序列化,也交由java辅助进程。那样就需要设计一个java对象在javascript中的表示形式,由java辅助进程,先转换为java对象,再序列化。再加上两次额外tcp传输。在没有减少工作量的情况下,浪费了不少性能。当然如果十分拒绝c++开发的话,倒是能因此少写些c++代码。
后续可以做的一些事情:
1、完善的重连、超时处理;
2、管理平台配置热更新;
3、管理平台数据上报;
4、先知;
5、加密、压缩(似乎和node.js的非计算密集场景有些冲突,而且公司的scf配置默认都是关闭这两个的。scf良好的用了这么些年,不开启这两个功能的功劳应该也不小)
当然已开发的内容中,也一定满是bug。等有人用了,我再考虑改bug的事。生产环境下的试错机会,才能让程序真正成长。