skynet的用户聚会review

2014-12-13

今天去参加了skynet的用户聚会,虽然我不是skynet用户也不是skynet潜在用户。说实话还是有点不好意思去参加的,主要是去面试过他们公司但最终没选择他们家,要是被认出来,觉得会比较尴尬。简悦是家很不错的公司,我很欣赏他们的文化,因为什么样的文化就会聚起什么样的人。想着有云风和各路大神来做分享,难得的学习机会,纯粹是冲着技术,还是厚着脸皮去了。

其实外部人员贡献话题不多,早知道这样我应该带个话题去的,讲讲Go和skynet的模型比较,还可以混餐饭钱:-)

这里是回忆一下,随手记一记。毕竟一个下午吸收这么多的知识量,能消化多少不容易。

云风演讲的是skynet的发展历程。skynet从他在杭州的时候就开始构思,大概10年。发展到现在已基本稳定下来,算上夭折的斗罗大陆,skynet在简悦内部已经有5个项目使用了。应该1个多月lua5.3版能出来,接下来规划是升级到lua5.3。skynet也准备出1.0版了,1.0的正式版本发布之后,api基本上就固定下来。后面如果有大的修改就是2.0版本。提问环节有问到lua5.3的好处,新版本的lua有些库,可以简化skynet的代码量。最主要的一项应该是对64位浮点数的支持。

snax的rpc,service资源共享。按我的理解,snax模块的工作是将rpc调用封装得比较友好。资源共享提到的两种,shareData和STM。比较好玩的是Data拼写成了Date,引来全场大笑,"约么?"。sharedData主要用于共享读,很少更新的场合。比如策划的配置数据就是一个很大的shareData表。STM则是用于更新较为频繁的场合,软件事务内存,比较高端的东西。提问环节有问到shareData的数据一致性,更新延迟等。有人反应在一个服务内更新了shareData表字段,比如B,立马读出来,可能会读到B为nil。云风解释了shareData的原理,底层是一个共享的C指针,读的时候各个服务用于会取一部分副本,如果是更新操作,有一个字段标记是否为脏,然后再慢慢将新版本同步过来,所以会的更新延迟。

在并行的系统中,要维护一个全局一致的状态是会有很大的性能代价。而只要读到某个历史版本的副本,就算是正确的,这样可以很好地提升并性性。shareData保证的是,读取得到的一个完整的某个历史版本的数据,不会出现部分字段更新的不一致性,如果发生了,肯定是业务写的有bug。

还有人提问单个skynet的内存占用量的问题,简悦内部项目的数据大概是6M,这里提到一个使用技巧是bootstrap完以后手动调一次GC,因为加载过程中会有产生很多临时数据,一个空的skynet服务加载进来就有6M多了,如果调一次GC就会降到2M多。随着使用过程慢慢变大,大概最终维持在6M左右。这样一个64G的机器可以开1万个skynet服务。另外注意的就是lua垃圾回收机制,比如一个表占了几十M的内存,将这个表结构中的所有字段置空,这些内存也是不会自动被垃圾回收的,应该将表置空才能回收。skynet在某个版本的更新实现共享数据,多个vm存一份就够了,这个更新之后,skynet服务的内存占用并不是问题,CPU会先于内存成为瓶颈,一般内存不会是性能瓶颈。

中间演讲顺序可能有变动,有人报名了没来。我也就随手记记,不按什么顺序了,回忆得起来的就写一下。

利用skynet搭建HTTP服务,这个是他们对接腾讯时提出来的需求。skynet很容易可以扩展来做http。当然现在只是一个可用的状态,基本的接口,路由和handler什么的。像cookie和session都没有做。提问环节有问到为什么要做这东西,事实上用别的方式做也没什么问题,陌陌争霸是用python写的,然后由一个skynet服务提供查询。

还有一个分享了skynet整合ejoy2d,在客户端中的使用。这个主题大概是全场最没存在感的一个,我怀疑肯定有人听到最后都没明白作者在讲什么。skynet设计之初是一个lua的actor框架,并不限用于后端开发。即使不使用网络的功能,这个框架也可以做一些其它事情。ejoy2d那边会产生几类事件,像屏幕中的显示更新的交互事件,和手滑屏幕之类的交互事件,用skynet框架来做这些事件分发,可以不用自己去管理底层的消息。

同步问题。我主观认为,这是全场讲得最烂的一个话题,差评。烂在这几点,首先,选题上面,同步是一个比较难讲的问题,具体细节并不好短时间讲清楚。然后,演讲者对于时间概念的把握上面非常欠缺,对交互的把握也很差,好的演讲应该是尽快把话题引出来,留更多的讨论环节。这些也就罢了,顶多归结为经验不足而已。让我比较不能容忍的一点是,我认为演讲者没有充分地好好准备。比如提问环节,台下有人问,有没人测试数据?回答:没有具体测过,不过在本地的服务器,同一个wifi下面,没感觉到有延时。

对实时性要求高的游戏都会面临同步问题。客户端发送到服务器有延迟,服务器响应各个客户延迟也不同。各个手机设备的性能也相差很大。区分逻辑帧和渲染帧,比如渲染接30帧,而逻辑设置为10帧。动画特效的播放过程拉长,如果期间收到了服务器的应答帧就正常的。发生在一定范围内的延迟可以通过客户端快放慢放来弥补的,但是超过一定范围,就没法玩了。按国内的2G3G环境,我觉得这个话题是还没有验证可行性的东西就拿出来讲了。

《僵尸别动队》,我个人评价这个绝对是全场最精彩的一个。如果按掌声来投票,它的得票数估计要相当于其它所有演讲之和。这哥们特有意思,上来先是放了张照片:我们仨。一个厨子,一个翻译,一个化学老师,因为真的热爱游戏就这样拍着脑门去做了这款游戏,耗时22个月,发布在苹果app商店,卖出不到1万份,没赚到钱,甚至自己都不知道到底花费了多少钱。

开始我只当故事听,越听越觉得励志...

开始只当外行看他,越看越承认,这是一个很专业程序员...

技术方面提及的,navmesh寻路,box2d的碰撞避免挤到一块。A*寻路分散到多个帧中执行避免卡顿,一个单位寻好路后其它单位如果有直线可达则不再次寻路。200个单位同屏战斗。navmesh不太适合做动态寻路,如果按普通格子做寻路,比如一个格子4个单位,那么当这个格子有4个单位后就标记为占用。而navmesh的三角形不是固定大小的,没法这么做。最搞笑的是解释为什么是像素风:找到一外包,要价最低,好,就是他了。

服务过载话题,陌陌争霸刚上线时表现良好,于是把用户增加一倍,结果系统就挂了。经历5个小时的热修复恢复过来。定位问题是某个服务用了一个O(n)的算法成为瓶颈,服务过载了。服务过载的定位,看到某个服务CPU长时间100%,结合log很容易就能定位到了。服务过载的处理,云风给出的方案是,通知调用者&重新排队。服务过载的预防,一个异步调用的请求,要么成功要么失败,而如果要求业务层每个地方处理请求的服务发生超载,是不科学的。无限转菊花是一个很不好的用户体验。现在skynet是对请求队列延后处理的,服务收到消息后如果当前的承载已到上限,那么直接给请求者返回,让它稍后再试。

天天来战使用skynet的架构,是有一个master进程,若干个slave进程(注意此处和传统的主从概念不一样)。agent运行是在slave进程中的。大部分的不需要交互的操作都是在slave中完成的,只有需要交互类的操作会请求master。master中会有各种服务,像活动,交易什么的。假设master服务过载或者挂了什么的,slave去请求无法完成,比如打开商店,直接向客户端回一个空的。所有操作都不会转菊花,而是返回一个空的,那么客户端发现不对就会主动下线,下线了就再也登不进来了。慢慢的让在线用户减少到一定数量,就可以关系系统修复bug了。

skynet踩的坑,主要讲的是异步调用的问题。异步调用返回时,相对于调用时的状态可能已经发生改变了。比如交易要减金币,加物品,这是几步操作,没有事务是很难保证一致性的。用专门的服务来执行会修改状态的操作,agent通过rpc去调。二阶段提交发生失败是需要回滚的,而对于游戏来说,有些操作回滚根本不可能。也有一些其它一些做法比如直接把事务交给数据库去实现。

晓靖同学带来的是levent库,用lua重造的一个类似python的gevent的轮子。封装了libev,利用lua的coroutine,实现用户层阻塞而底层非阻塞的友好的io,redis性能数据大概是3w多。因为我也研究类似的事情,觉得这个轮子的意义不是大。先说好的方面,这是一个库而不是框架,很简洁,相比skynet轻了很多。不好的地方,单线程的。然后,这种级别的实现并不难,难的在于让coroutine交互。不过晓靖同学自己也没怎么用到levent,大多时候还是倾向于用Go,哈哈,还是我大Go语言牛逼。

最后是蜗牛同学讲简悦的log处理系统,我对这一块不是很熟,但我们公司正好也面临这边的问题,我们同事没去听真是好可惜。

skynet