« June 2015 | Main | August 2015 »

July 29, 2015

第一次提交绿光

这两天都在忙一个事情, 把我们的一个新游戏提交到 steam 绿光计划上。

去年底开始,公司里就有两个人开始在忙一个小项目。最初的一个月,他们只是做了一个原型,本来打算演化成一个手机游戏,作为 2015 年公司的新项目去推广的。这个原型只有一个简单的战斗动画,提出想法的同学是一个格斗游戏迷,他只是想简化格斗游戏里的操作,提取出一些核心乐趣带给大众玩家。

当然,他同时也是一名优秀的美术设计人员,所以原型做的非常绚丽,一下子就吸引了公司里很多人。

对于这款游戏做出来后如何盈利,最初的两个开发人员(一个美术和一个程序)一点想法都没有。他们没有沉浸过任何网络性质的手游,不懂怎么挖坑赚大 R 的钱。只知道打磨那些 demo 中自己认为有趣的地方。所以对于这个项目是否立项,作为公司的决策人是很为难的。

正巧我玩了一年多的 steam ,并在上面已经买了几十个游戏,交了很多好友。让我感觉到,其实,制作精良的单机游戏也是有机会收回开发成本的。游戏玩家始终还是一个小群体,只是网络游戏的热潮把所谓游戏玩家的群体放大了。但原来玩单机游戏的那一部分小众依然存在,且只要游戏质量没问题,为游戏买单的人依旧在那里。

所以,我说服了公司的管理层,放任这两个同学继续做下去,公司依旧给他们开工资,可以全职做自己想做的游戏,他们只需要把游戏做的自己喜欢,不用考虑怎么收费的问题。到时候,我们争取上 steam 平台,不卖 IAP ,也不搞免费游戏那一套。

到上个月,游戏已经初具雏形了。我自己试玩了三个小时,用最简单的难度把已经设计完的关卡全部过了一遍。画面自然不用多说,毕竟是做过多年游戏的美术尽心之作,非常绚丽。但我更关心这是不是一款 游戏。虽然有很多不足,但是我能感觉到这款游戏是可以玩的,有他一些独特的玩点,虽然操作比较简单,但还是有许多策略性,在后期,你需要在 5 个角色的十多个技能里选择,把握恰当的时机,用正确的持续把招数连起来,根据敌人的位置:远近、空中地面单位,需要有不同的对策。

这让我对这个项目有了一点信心。多一点时间打磨,会是款合格的游戏吧。不求所有玩家都喜欢,但求可以满足一部分玩家的乐趣。


我们公司的市场运营人员基本都是从网游、手游这条路子走过来的,从来没有搞过单机游戏的发行。在登录 steam 这件事上,他们有点束手无策。也不知道该怎么卖掉产品。什么次日留存、七日留存、付费率、Arpu 值、LTV 值…… 似乎和这个项目完全不搭边。

我便自告奋勇的来帮这个团队做 steam 发行相关的工作。

其实还是满容易的,新注册一个公司用 steam 账号,支付 100 美元的费用就可以向绿光提交产品了。写上产品介绍、截图、视频上传 youtube ,绿光页面就创建了出来。

花了点时间,帮忙修改了他们开发组原本的游戏介绍文案,把一些花哨的段落都删掉了,提取出核心玩法的介绍。比较头痛的是英文版本,很多词不知道怎么表达,最终找了一个在 2K 工作的策划朋友帮忙翻译了一下。

剩下的工作就是在微博、steamcn、steam 贴吧发帖拉票。( btw, steamcn 论坛的拉票帖一直在审核 :( 有人可以帮忙通融一下走走绿色通道么 ;)

最后,附上这个项目在绿光上的投票页面:希望看到的朋友多多支持。http://steamcommunity.com/sharedfiles/filedetails/?id=490199854


接下来我会以我在 steam 上玩游戏的经验,督促开发团队完善游戏体验。在核心战斗之外,完善收集成长等这些我个人比较喜欢的游戏系统。当然,围绕核心战斗玩法,开发人员有很大的热情设计各种古怪的 boss 战,这部分我倒不担心。按计划,我们为开发团队还留了半年的开发时间,陆续应该会有更多的视频展示吧。

据有通过绿光经验的朋友讲,快的话,可以在两周通过,慢的话可能会等两三个月,当然也有可能被拒。希望我们这个试水产品能有一个好的结果。当然更希望可以收回开发成本,让公司的决策团队能有更多信心投入更多资源来做更多好玩的游戏。

目前,这个项目已经从早期的两个人加到了 5 人,几乎是我们预算的极限了。如果能把开发成本控制在 200 万人民币以下,那么似乎还有点可能可以收回成本,或者小亏一点也能接受,算是探路交点学费吧。在这个项目之外,我们公司还有一大帮游戏迷们早就摩拳擦掌了。

ps. 不用担心,我们不打算卖情怀。到时候定价也不会太高的,一定比目前国产卖情怀的游戏便宜的多。项目一分钱不赚,这次我们公司也还算赔得起,只是可能比较难有下一款单机了。如果真心喜欢,到时候请买一套支持一下开发者;不喜欢就算了,不用为情怀掏腰包,帮忙吆喝一下也非常感激。

July 28, 2015

lua 分配器的一些想法及实践

从周末开始, 我一直在忙一个想法。我希望给 skynet 中的 lua 服务定制一个内存分配器。

倒不是为了提升性能。如果可以单独为每个 lua vm 配置一个内存分配器,自己调用 mmap 映射虚拟内存,就可以为独立的服务制作快照了。这样可以随时 fork 出子进程,只保留关心的 vm 的内存快照。主要可以有三个用途:

  1. 可以在快照上做序列化,并把结果返还父进程。通常做序列化有一定的时间代价,如果想定期保存的话,这个代码很可能导致服务暂停。

  2. 可以利用快照监控检查泄露。定期做快照相比较,就能找到累积的对象。我曾经做过这样的工具

  3. 可以在镜像上对快照做一些调试工作而不会影响主进程。


为什么为每个虚拟机单独配备分配器是必要的呢?

因为如果对 skynet 整个进程 fork 做出内存快照的话,当所有虚拟机共用一套内存池,很难单独把不感兴趣的内存 unmap 掉。而子进程的内存页是 COW 机制的,一旦子进程工作时间过长,会导致整体内存开销非常大。

因为 lua 本身就可以为虚拟机定制分配器,所以比较容易给出一个实现。事实上我花了半天时间就动手完成了,随后花的更多的精力是从已有项目的线上数据里采集真实的分配器工作流程的 log 用来对新写的分配器做测试。

让已有项目的分配器加上 log ,逐条记录每个 vm 分配器工作时的分配释放操作在临时文件中,再写一个脚本分析这些 log 就可以还原出内存分配器真实的工作流程。大约收集了几百个,从几千条到几千万条不等的 log 后,我就可以做测试了。

测试的过程完全还原线上程序从一开始到退出的每一个内存管理的操作,并在分配后填充入特定的字符串,并在释放的时候逐字节核对并清理。这样的流程跑下来,几乎所有的潜在 bug 都可以发现。(只要测试样本足够多,就至少能保证在现有项目中替换新的分配器不会出问题)


我定制在分配器放在了 github 上

从时间性能上,其实是很满意的。由于不需要考虑多线程问题,它几乎一定会比 jemalloc 这样的多线程安全的分配器要快的多。我用了一组 300 万条的真实数据对比过(由于数据比较大,没有上传到 github 上):

$ time ./testalloc skynet

real    0m0.139s
user    0m0.080s
sys     0m0.056s

$ time ./testalloc jemalloc

real    0m0.200s
user    0m0.156s
sys     0m0.040s

$ time ./testalloc malloc

real    0m0.217s
user    0m0.180s
sys     0m0.036s

可以看出 jemalloc 和 glibc 的 malloc 差别不是很大, 但定制的分配器要快的多。

我的分配策略主要分成三个,对于小于 256 字节的内存块,建立若干条 small object list 缓存复用。

对于 27 到 32K 间的内存块,每次分配 32K 的内存页,切割使用。并在回收的时候用一个链表实现的循环双向队列串起来。按回收的大小来绝对从队列的哪一头插入。分配的时候,在队列上最多移动固定数量的节点,找到最近匹配的合适节点使用。

对于大于 32K 的大内存块,则直接调用 mmap 分配。(实际在 lua vm 使用时,这种大内存块的需求非常稀少)


不过最终我对这份实现不太满意,主要是在某些测试样本中表现出了很大的碎片率。

碎片的产生是由算法产生的,因为分配器的策略是,对于小于 32K 的内存块,只会分割使用,而没有合并。如果长期运行,池中的内存会越来越碎,一旦后续有大内存请求,就会向外申请更多的内存。

我曾经想过定期对中等内存的内存页做合并整理。这个方案以前在为客户端写分配器时用过,感觉实用性不是特别高,所以这次也没有尝试。但我想过把中等内存块采用伙伴算法管理,昨天晚上完成了实现,还有少量 bug 需要调试,尚未提交到 github 。不过我对其最终的效果依旧保有怀疑。

先谈一下伙伴算法具体实现的一些问题:

如果使用伙伴算法,内存分配的单位就一定是某个最小单位长度的 2 整数幂倍。假设最小单位是 256 字节的话,能分配的大小就是 256, 512, 1024 ... 32K 这样。我查阅过采集来的数据,恰巧 lua vm 也经常申请 512, 1024, 2048 长度的内存(table 的数据区的长度)。这就有了第一个矛盾:

通常,我们需要为申请的内存多加几个字节的 cookie 保存一些分配器需要的额外信息。lua vm 在调用分配器时,会给出内存块长度,这样长度信息可以不用记录在内存块中,但对于伙伴算法,我们还需要有一个指针指向伙伴堆的管理器的位置。8 字节的 cookit 看起来是不可省的,这就导致了一种尴尬的境地:lua 通常申请 512 这种整的内存块,而加上 8 字节后,就需要用更大一级的块去满足需求。

固然我们可以把单位块的大小设成 520 这种,但按 lua 申请的翻倍策略,浪费也会越来越大。

也不是完全不能去除 cookie 的额外开销。比如,我们可以将整个 32k 的页的首地址对齐。这只需要在 mmap 时先申请 2 倍的空间 64K 。其中一定包含有一个 32K 对齐的地址,然后把不需要的部分 munmap 掉即可。且当一次对齐成功后,再申请时,通常也是对齐的。

对于 buddy 算法申请到的内存,我们只需要把地址对齐到 32K 上,就能找到当前页的头部。把每个页的第一块用于管理整个页就可以了。

一般的 lua vm 需要的 32K 页不会超过 1000 这个数量级(32 M 总开销),而伙伴算法很容易把整个页填满,活动页数量就更少了。所以在分配的时候查找的开销并不算太大。

第二个尴尬是,lua 有时候会以一些并不整的基数翻倍请求内存。这会使得上面的优化策略无效,依旧会有接近 50% 的内部碎片率。

似乎我们在使用 jemalloc 的时候也观察到过一些类似现象:在长期运行的服务器上,系统报告的内存使用情况和自己统计出来的申请内存数量相差一倍左右。

但是,如果有几千个分配器独立工作,分别使用完全独立的内存池,很可能恶化这种情况。因为 lua vm 在工作时,一旦把一块内存的需求翻倍,很可能短期就不会有后续相同的申请。如果很多 vm 共用同一个内存池,回收内存块的利用率就要高的多。


接下来几天,如果有别的进展,我再写点东西补充。

July 22, 2015

如何定义一个经典 Rogue Like 游戏

最近玩 Rogue Like 比较多。似乎好多游戏都被贴了 Rogue Like 的标签,在 steam 上,像盗贼遗产、以撒这些也被归到 Rogue Like 里去了,但我隐隐觉得它们还不应该被算到经典的 Rogue Like 里去。

如果从字面上理解,Rogue Like 似乎又特指界面像 Rogue 的游戏,也就是字符界面,海量的键盘快捷键控制,那么矮人要塞也算一标准的 Rogue Like 。可是 DF 的冒险模式之外的部分又似乎和印象中的 Rogue Like 不同。

另外,还有把一些轻量游戏改贴了 Rogue Lite 的标签。玩起来有点像 Rogue Like 细玩又有些差别。

今天在 google 上乱逛的时候,发现了这个 Berlin Interpretation ,觉得很涨知识,摘录如下:


作为 Rogue Like 的主要特性,通常有:

随机环境生成:游戏世界每次都是随机生成的,这样可以提高重复可玩性。物品、怪物等可以是固定的,但摆放的位置随机。同时可以有一部分固定的内容(谜题,特定的关卡,剧情等)。

永久死亡:第一次建角色一般完成不了游戏。死了之后从一级开始。保存游戏只供以后继续,不能靠读档来重来。所以需要随机环境这点来让重来的过程更好玩而不是折磨。

回合制:每个指令都对应一个动作。游戏对时间不敏感,你可以随便花多少时间来决定下一步做什么。

走格子:世界是由格子划分,怪物和玩家无论体积大小都占一个格子。

无模式:移动、战斗以及其它动作都在同一模式下(不切场景等)。游戏的任何时候都可以做任何动作。

复杂:系统够复杂,对于游戏里要做的事情,通常有不同的解决方案。有很多 物品/怪物 物品/物品的交互可能,而这些可能性都建立在统一的模式下。

资源管理:你同时能携带的资源有限,每次获得新资源时,都需要取舍,保留对你有用的东西。

砍杀:即使游戏里有很多内容,杀怪一定是 Rogue Like 类游戏中最重要的部分。游戏玩的是玩家挑战世界:不需要有 怪/怪 之间的复杂关系。

探索发现: 游戏过程需要很小心的探索地牢,发现没鉴定的物品的用法。需要每次新游戏都有全新的探索发现的体验。


Rogue Like 类游戏还有一些次要特性:

单一角色:游戏以单角色为中心,如果这个角色死了,游戏结束。

怪物和玩家一致:怪物和玩家一样也有装备、背包、物品等,数值公式等也和玩家使用同一套系统。

策略性挑战:每次有重大进展前,你都需要学习新的策略。这个过程要不断迭代,在游戏过程中学习游戏。比如,在早期游戏中学到的游戏知识是不够击败游戏后期的。对于新玩家来说,永久死亡使得游戏充满挑战。而游戏乐趣就在于提供不断的新的策略上的挑战。

ASCII 界面:传统的 Rogue Like 都会提供完全 ASCII 字符构成的界面。

地牢:游戏中有很多由房间和走廊构成的一层层地牢。

数值: 游戏中的角色是由一系列数字构成,并且这些数字都特意展示给玩家看。

July 14, 2015

被严重低评的好游戏 Rogue's Tale

虽然网络游戏假借游戏之名占据着主流之道,其实游戏玩家从来都只是个小众群体。好在靠着网络的力量,小众群体也能找到同好。steam 就是这么一个神奇的平台,他的贡献不仅在于改变了(中国)玩家的(盗版)消费习惯,更重要的是提供了一个良性的游戏评价平台,让好游戏可以被玩家认可并促使开发者开发出更好的作品。

我在 steam 上的一大乐趣就是翻阅引起我的兴趣的游戏的玩家评测。先从差评看起,了解为什么不被人推荐;再去阅读好评,理解游戏的设计点。在用户生态上,steam 的评测比 ios 的好了不只一个层次。

绝大多数情况下,steam 用户的眼光非常犀利。几乎不会有滥游戏被捧上天,也不会有好游戏埋没。至于某些专门迎合更小众群体的偏门游戏,只要制作的好,也不会被误解。因为非该类游戏的用户根本不会购买它,也就不会写评测了。所以,越小众的游戏,好评率越高。如果被差评,几乎都是制作上有问题不太值得一玩的。

曾经我几乎不会去玩好评率低于 80% 的游戏以避免浪费自己的精力,但随着深入了解 steam 社区,尤其是顺着我喜欢的一些评测文章去追述那些玩家的足迹,我发现了另一些被埋没的好游戏。

Rogue's Tale 就是这样一款被严重低评的好游戏。在写这篇 blog 的时候,它的总评测数量是 200 ,其中只有 129 篇好评,好评率 64% 。对于 steam 上发行的游戏来说,这个成绩几乎把游戏判了死刑。当我把这款我由衷喜爱的游戏推荐给朋友时,几乎第一反应都是:评价很低啊。

当我进行了 121 小时游戏时间后,对它的喜爱溢于言表。我决定为它写第二篇文章。前一篇是我在 steam 上的评测


首先,我并非传统 Rogue Like 的死衷。2008 年的时候,一个朋友给我安利 NetHack ,最终我是没有玩进去的。Tome4 、CDDA、DCSS、Dungeons of Dredmor 、Sword of the Stars: The Pit 等我都有玩,但时间均不算长。近年来让我停不下来的 Rogue Like 只有 Dungeon of Endless 了。可见,我还是一个典型的游戏玩家的:游戏需要上手简单、画面亲合、学习曲线平和、且需要有足够的规则深度。

Rogue's Tale 的好评中最打动我的一句话就是:

“The game is incredibly simple, but also incredibly hard. ”

在购买游戏决定在它上面投入精力前,我翻阅了所有差评。几乎所有的矛头都指向了游戏的随机性。无数玩家表示,我做对了一切,可还是死掉了。靠刷脸来玩游戏,这正也是我不喜欢设计。所以我在好评中寻找反对意见。

“the game isn't made for your chars to live forever, but to live long enough to have a shot at the KING”

是的、我不喜欢无限玩下去的游戏,靠一点点的累计数值成长,完成更高的目标。我希望游戏有一个明确的目标,完成后放在一边,在和朋友聊起的时候值得回味一番。角色突然死掉并不可怕,正如矮人要塞的 wiki 中所说:

"Losing is fun"


我大约花了 3 到 4 个小时熟悉游戏的基本规则。和我玩过的大部分 Rogue Like 相比,这个游戏的基础机制已经足够简单了。游戏内的文字引导恰到好处,教会了你所有的操作:探索、打怪、升级、拾取物品、天赋成长…… 这些其实已被各种游戏反复教育过了。

在网上贴过一些游戏截图给人看,大多数人的反应是:这不是暗黑破坏神吗?对于不玩 Rogue Like 的玩家,简单解释为一个只有 HardCore 模式的回合制 Diablo 似乎也满贴切的。虽然是回合制、可游戏反而更紧凑。我在新手期的第一个小时,平均 5 分钟就死掉重来一次,几乎没有多余繁复的操作,就是再来再来一盘。这个周期越短,死亡重来的挫折感也就越小,在失败中学习的乐趣也就越大。且回合制也带来了更大的策略深度,任何紧张的战斗间隙,你都可以停下来仔细思考,或者切换到浏览器搜索游戏的相关信息。

我在接触游戏的第一个晚上,翻阅了网络上不多的英文资料。包括作者在官方网站的文章,以及官方论坛和 steam 论坛上的大量关于游戏的帖子。因为不断的失败让我产生了浓厚的兴趣:对于新角色,似乎大家都是一样的角色起点,为什么有人可以玩得那么顺利呢?这绝不能单用人品好来解释吧。

Rogue's Tale 的作者是个天才。我试图找找有没有类似的游戏,但没有找到。这是一个独一无二的游戏。

整个游戏的内容不算太多(只是相对于同类 Rogue Like 来说),比如它的角色只有四个基本属性,角色到 20 级就满级了,自由支配的属性点才 7 点,且不准搭配过于畸形的属性配比(限制了最小属性值和最大属性值的差值)。这回答了我曾经对属性自由加点设计的疑惑 :无论角色升级过程或长或短,自由属性点分配的自由度其实是有限的,而天才的设计师可以用最小规则把这个自由度控制在设计范围内,又尽可能的提供多样性。

同样,Rogue's Tale 的角色,到满级只能在 24 个天赋点中选择 6 个来构建自己的特色角色。但这个搭配过程几乎是完全自由的。相互依赖的天赋最多只有一层。天赋系统设计的如此之好,以至于它可以完全放弃在游戏开始前让玩家选择职业。每个角色创建开始是完全一样的,没有偏向性,你完全按天赋搭配来发展角色的技能偏向。ps. 你可以在游戏开始时选择性别。男女角色在属性点分配规则上有细微的不同。我暂时还没有看到有另一个游戏提供了如此有效的自由度。

在法术、药水、装备、武器等方面,Rogue's Tale 体现了相似的特性:游戏中的这一切总类是有限的,都只有数十种。你可以在角色一级的时候就拾取到 20 级满级用的装备,也可以在初期就学到一切法术,但这并不破坏游戏的平衡性。没有所谓紫装、橙装、装备等级这些数值差异的装备区别,不靠这些来制造游戏进程需要的难度曲线。游戏里的 NPC 使用的装备和属性能力都完全符合和玩家一致的规则,唯一不同的是,操作玩家的是人脑而已。(但是,在拾取到某些特定的被祝福的戒指或项链时,依旧有获得极品的乐趣)

说了这么多,无非是想描述出这款游戏的天才设计之处:用极少量的元素、充分利用无限的搭配和单局游戏的随机性提供了无限多的可能。让每局游戏都有独特的体验。资深玩家在每局里都必须根据碰到的一切(由于随机性的存在、你会以不同的次序碰见游戏提供给你的元素)小心翼翼的升到满级。你固然可以心中预设一条路线,但各种不可控因素还是要求你充分理解游戏的方方面面来避免(在满级的过程中)中途意外死亡,同时也应了我们在玩魔兽世界时暴雪教育玩家的一句话:20 级才是开始。

我的 120 小时游戏经历仅仅到过 3 次 20 级。每次在 20 级都会玩很久,却最终不可避免的犯错误。惭愧的说,到现在连最终 boss KING 都还没敢见过。

刚开始游戏时,我觉得那些遗产好难解锁。遗产是跟随游戏账号的(是的,这是款需要联网才能进行的游戏。所有游戏过程都会去服务器校验,避免作弊。因为作弊的话会严重影响游戏乐趣),一旦在单局游戏里达成特定目标,就能在账号上永久解锁对应遗产。遗产可以增强后续的游戏角色初始状态。但就我的理解,所有遗产都是为了让你的角色升级到 20 级的过程更容易一点,升级过程不那么容易死掉,或是让你在角色级别比较低的时候,有更大能力做选择。对于满级角色,有没有遗产实力其实是一样的。

当熟悉了游戏之后,在发现解锁遗产并不是那么遥不可及后,每局游戏的目标低了好多。不去想杀掉最终 boss KING ,你可以按下 F3 查看那些没有解锁的遗产的条件,选择一个自己可以完成的为目的而活下去。我现在还记得把战士初始套装解锁后长舒了一口气,随后几分钟就因为松懈而让角色死亡,却没有一点沮丧之情。


写到最后,相信能看到这里的同学相信都是真正的游戏玩家。顺便夹带一点私货:

年初 提过我们要制作一款纯粹的(单机)游戏的计划。是的,现在在筹备登录 steam 的青睐之光了。需要提交的材料还没有准备好,争取这个月可以搞定吧。

这里是一小段非正式的游戏概念视频 ,还等提交到青睐之光后,steam 上的朋友多多支持。

July 09, 2015

n:m 的 vpn 隧道

前两天成功用 mptcp 搭建好出国上 github 的快速通道后 ,在想如何方便的无缝使用。如果不使用 vpn 的话,就无法用路由规则来把流量倒向隧道。当然修改内部 dns 的 github 域名解析是可以的,但终归不是很舒服。

SA 试了一下用 PeerVPN 搭 vpn tunnel ,工作倒是正常,但是 mptcp 跑在隧道上等于是白费了。PeerVPN 是用 UDP 通讯的,实际上只走了单条路径,如果再在上面跑 mptcp 等于是自欺欺人。

所以也曾经考虑过使用基于 TCP 连接的 VPN ,比如 openVPN 。在 SA 尝试做配置的时候,我突然有了个想法。既然 PeerVPN 是开源的,何不修改一下实现,让它支持多个 ip 间建立多条路径的通道呢。

读了一下代码发现,其实在 linux 下实现 vpn 是如此简单。linux 已经负责实现了 tun/tap 设备,可以帮你把 ip 层的流量(或以太网层)倒入一个虚拟设备。我们要做的仅仅是处理一下互联网上的通道转发而已。

google 了一下多路径的 vpn 方案,发现有这个想法的人太多了。比如 Multipath-VPN 就是,不过几百行 perl 代码 。PeerVPN 虽然也不复杂,但就我需要的应用来看,大部分功能都是不需要的。所以还不如自己实现一下,应该也就是几百行代码的量吧。

如果在 IP 层做流量转发,UDP 肯定是最合适的。因为 IP 包可以允许丢掉,可以乱序,这样必定最大程度的利用 tunnel 的有效负载。如果我们 tunnel 的两端都有不同网络的出口,那么可以并行的通道就有 N * M 条。使用连接去管理也复杂的多。而且网络出口也不一定是稳定的,对于办公室, ISP 给你换 IP 也是常事。tunnel 本身是不需要有连接的。

研究了两小时的 tun 开发手册,我便兴致盎然的写了一晚上,就有了这个:mptun

动态负载均衡的算法是我拍脑袋想的。一开始将每个出口 IP 都赋予一个均等的权值,而目的 IP 也如此。当有流量需要发出的时候,select 出可写的通路,然后按权值随机选择一个目的地和出发源地址,把从 tun 设备里读到的 IP 包打包成 UDP 转发出去。一旦从一条通路发出,就给源 IP 的权值加上一点。

而当从某个对端 IP 接收到数据包时,也给对应的目的地加上点权值。

这样,如果从某个位置收的包多,就有更大几率向其发包;而从一个出口发的多,也就更可能从它那里发出。

原本还计划给每条通路做一个计数,来统计丢包率来修正权值的。不过后来发现上面的策略已经可以满意了,就懒得再加。


最后我们实际测试了一下效率。我们办公室有 4 个不同运营商的出口,对端 VPS 只有一个。用了一个接近 40M 的 zip 文件,用 wget 测试速度。以直接拉取(只利用一个出口的 TCP 连接)为基准,发现使用 mptcp 下载,虽然也会建立 4 条连接(通过四条路径),但总的下载速度只提高了 2.6 倍左右;如果使用 mptun 做 tunnel 的话,几乎可以提高 4 倍,真的把 4 条出口的可以利用的带宽都跑满了。

当然对于小文件或做 ping 测试,丢包率可能略微上升、响应速度下降了一点。我的直觉解释是:tunnel 间建立了多条路径,IP 包的转发是随机在不同路径中进行的,依赖请求回应的应用,来回很可能走的不同路径,很大概率受最慢的路径的影响。而对于大量的流量,总体带宽却能提升不少。


目前没有测试对端也有多个入口的情况。我设想是这样的:租用日本和美国两个机房的 VPS 。由于没有墙的干扰,日本和美国间的通路是非常顺畅的。所以我们可以把日本 VPS 做 DNAT 将 UDP 对应端口映射到美国机房。这样,从办公室看来,对端机器就有两个 IP 且这两个 IP 在互联网上属于完全不同的两个网段。

当我们从办公室发包过去的时候,上游路由看起来我们的 IP 包发向了截然不同的地方,这样就可以互不影响,出国的流量就可以大大提高了。

目前暂时只是个想法,还没有做实验。

July 06, 2015

使用 MPTCP 增加对 github 的带宽

从去年开始,我们的工作项目逐步迁往 github 的私有仓库里。github 太好用了,自己搭建的 git 平台完全比不上。可是购买企业版本自己架服务器成本又太高(平均一个人一年要 250 刀),而我们项目并不多(只是人多)购买私有仓库绝对够用了,唯一的缺点就是 github 没有在国内开展业务,服务器都在境外,速度很慢。

之前我们无论是电信还是联通线路,从 github 上 clone 项目的速度一直没有超过 200KB/s ,而我们租用的国际线路本身带宽又很低(因为单价太高,所以只做部分翻墙用)。而且连接还不太稳定,有一定的丢包率。

考虑到我们办公室租有多条不同运营商的宽带,所以最近在考虑怎么把这些资源整合在一起使用。

带宽的瓶颈显然是在墙上,所以只要提高过墙的带宽就可以了。我在 linode 机器 上做了测试,从 linode 美国机房 clone github 仓库的速度轻易可以达到 8MB/s 。

一开始我的念头是自己写一个支持多条 TCP 连接协作的 tunnel 程序。后来转念一想,MPTCP 不就是干这个用的么?去年的时候玩过一阵 MPTCP ,这次和我们爱折腾的 SA 同学们一起再试试。

公司曾购买过一个备用翻墙的 linode vps ,不过是很早装的系统,内核不支持 mptcp ,所以需要先升级。

要做的准备工作还包括添加办公室网关上的的路由规则,在内网一个 IP 可以看到所有的出口。

所有准备工作完成后,我们测试了从办公室到 linode 机器的 TCP 连接,发现 MPTCP 已经起作用了。

然后只需要把 github 的 IP tunnel 到 linode 上就好了(github 服务器并不支持 MPTCP )。做测试的话,可以直接用 ssh -L ,不过日后可以选用类似 tcptunnel 这样的更廉价的方案。

我们办公室有 4 个对外出口,当我们用 MPTCP 在同一条 TCP 连接中同时使用上 4 条路径后。经过 linode 中转到 github 服务器的带宽果然极大的提升了。试了一下 clone skynet 的仓库,可以达到 500K ~ 1M /s 的速度。


剩下的工作是修改内部 DNS 让 github 的 IP 指向 tunnel 的内部 IP 。当然,日后可以考虑用 openvpn ,就可以通过修改办公室的路由规则直接(不修改 dns )绕道 linode 连上 github 了。从办公室到 linode 这一段极大的利用了所有的出口路径。ps. 据说 openvpn 的协议被墙监控,所以 openvpn 方案还需要实测才看得出效果。