[关闭]
@wsd1 2017-01-19T08:03:52.000000Z 字数 2924 阅读 1554

阻塞,还是回调? 我眼中的skynet

skynet 原创文章 20170106


对现场不同的处理方法 形成 编程语言不同的风格

适合人思考方式的程序是顺序事务的,比如做个西红柿炒蛋,切西红柿->热油->下锅炒,程序一般写成那样顺序的,最符合直觉。
但做事情往往会遇到需要某些外部事件才能继续的情况,比如,炒菜过程中,cache中没油了,就得去买,就得悬挂起现场等着。

计算机等候时不能闲着啥都不干,等着这段时间,要干点其他事情。最重要的问题是,如何收拾、恢复现场。啥是现场?现场(context)就是厨房中的 菜板、你切好的菜。计算机世界中就是 当前寄存器、栈里的内容。

程序语言对这个问题的不同解决方法,形成了各种语言不同的风格。

低级语言,比如C往往会直接使用堆栈保存现场,利用操作系统API,直接切出进程,更换堆栈,返回时相反。这种过程称之为阻塞:它利用堆栈保存现场,等外部事件发生后切回堆栈运行。缺点是堆栈空间比较大(单个不算大,只是浪费,尤其是多进程时尤为明显),Apache之所以不能支持大并发就是这个原因。

C语言其实也可以不用堵塞,通过编程艺术,可以选择使用回调函数方法安排“后事”,回调函数最怕多进程(在其他进程运行回调,很可能产生竞争),因此,这种形式多用于消息循环类型的程序结构中。使用起来也是需要显式的申请资源、传递某个context类型的对象,把所有现场都放在里面,在事务完成时,找合适的机会释放掉。
比较典型的是 libuv这类C语言库,Nginx也是这类编程方法的代表。可以想见,这种方式对程序员的要求比较高,毕竟,你得亲自安排malloc各种对象,在回调中传递,合适的时机对其进行销毁,各种麻烦。所以,如果不是为了高性能,很多程序员都喜欢使用高级语言来写代码。

都高级语言了,对现场的处理都是完全软件化的了,各种牛逼酷炫的技术就可以走起了。

高级语言(解释型或虚拟机)也是使用阻塞式和回调式来处理事件等候这个问题。不过,前者依赖的是虚拟机中栈切换(不用系统进程了,虚拟机栈可以做的很节省)或者优化的系统栈结构,lua语言和Golang就是这类实现。lua中的协程(coroutine)以其节省资源而为广大程序员所喜爱(主要是我喜爱),据说每开个协程只需要额外占用208字节的资源。Golang采用了split-stack技术,即使是编译运行,也大大减少了对堆栈的占用,这是支持大量并发的核心科技。

高级语言中回调过程处理现场往往是通过语言特征对状态做缓存处理,用一个比较学术的名词描述,就是“闭包(closure)”。javascript就是这类的典型,闭包是啥,就是你在函数A里定义使用一个回调函数,这个回调函数可以使用函数A中的局部变量,用到哪些存哪些,回收全靠GC,这个技术很牛逼,妈妈再也不用担心我的现场。

我的感觉,javascript大量的使用回调函数,是因为其底层的消息循环机制和没有线程、协程什么概念有关。js的每一段代码都是要回消息循环的,天生的非阻塞,不能使用平铺直叙的编程方法(不能堵住)。
这还可能和其出身于前端有关,毕竟处理UI事务,基于事件形态的编程方法最适合与UI树状数据结构(DOM)配合。
在后端编程环境,NodeJS中就遇到回调金字塔困境:当异步事务过多的时候,可能出现大串函数套函数的壮观景象。这也是Nodejs生态圈中出现各种编程艺术(类如promise)的原因。ES7中就有万众期待的 await和async了。

罗嗦完毕,后面说说skynet。

事务处理上顺序执行更优美

skynet让我喜爱的,是没有在lua环境中过多使用回调——虽然底层C是消息循环,但是上层lua依然包装成优美的顺序结构(lua有闭包,用回调方法也不难)。作为对比,github上有个项目叫Node-Lua,lua绑定libuv,用lua取代js,以回调为美,我不以为然。

在我看来,重事务的后端能够用平铺顺序的方法写代码是一种幸福。更何况skynet还完美的包装好了socket,这把瑞士军刀简直就是“要你命3000”,周边各种基础设施全线接驳,什么redis、mysql、mongo这类的,只要你可以用socket连,都是它的菜。

组织松耦合 运行高聚合

在程序层面,skynet以服务节点的形态将业务划分成很多部分,之间可以相互调用。
在运行层面,skynet大量起用了coroutine,底层消息处理对上层处理业务完全透明。还提供了类如 wait() wakeup() timeout() response()这些辅助API。尤其是response(),将回应打成闭包,可以扔给任何节点做回应,简直就是天才的设计(不了解skynet的消息和在此之上的call机制,你肯定不知道在说什么)。

使用skynet,我可以将各种功能组织在不同的文件里。在运行时,它们之间又可以有效配合,单进程运行,没有竞争,无需信号量。这种语言形态的松耦合和运行时的互操作,是其他语言环境很难完成的。在这一点上,你可以说skynet是一个操作系统了。

高低级语言搭配干活不累

skynet是一个融合了低级语言(C)消息框架和高级动态语言(lua)的混合体,歪果名叫做hybrid framework。你可以选择运行高效的C来写服务节点,也可以选择同样开发高效而且安全隔离的lua来写上层业务。

现在我使用它在一个物联网项目上,传输层协议使用C语言编写,因为预期支持数量众多,所以需要精细控制资源,高效处理安全加密有关的细节。而分发层和业务逻辑就放在lua层面来完成,充分利用对文本处理便捷,虚拟机也可以把不同业务隔离开来。

核心精简 模块化设计

skynet的主要核心包括两部分。
1、C语言实现的消息循环和组件加载机制
2、lua实现的以消息为中心的进入退出coroutine的包装层
总共代码估计不超过5000行(不包括周边模块),只要你有心不是很艰难就可以把握住脉络。对于能力不咋地,但掌控欲很强的程序员,比如我(嚯嚯呵呵哈哈)那就是天上掉下的神器啊! 统治世界就靠它了~

还有,不得不说的组件化能力(虚拟机lua语言的动态能力就不必说了)。
skynet内核(C部分)自身支持加载模块(.so文件),你完全可以使用C语言去写性能有要求的服务节点,通过消息与其他节点配合。
lua又是一个对C语言极为友好的动态语言,所以你可以找到很多现成的lua的C扩展,skynet/3rd路径下可以放置你需要的各种组件,比如我就扩展了CJSON、sqlite和OpenSSL,这种感觉就是神器加持各种元素属性加成,完全无敌了。

分布式潜力

skynet还支持cluster,天然的能够隔离处理进程,能汇聚业务接口。这是与第三方后端安全整合的关键技术

设计者本身可靠

从代码可以看到风格,从风格可以获取信心。
skynet历程5年,凝练了云风的思考和心血,很多部件都有重构,重要部件往往经历几年的打磨,我用了1年时间翻看各个部件的历史变革,才算真正把握住这个框架的机制和思路,领会到设计者的气质,开始对质量放心。

这就是我眼中的skynet,构建松耦合高聚合的后端通讯程序,至今(201701)还没有看到更好的。

2:08 2017/1/6

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注