一门没有变量的语言
2007 年,我还在北电。
有一天,一位同事跟我聊起一门叫 Erlang 的语言。
Erlang 是爱立信为通信系统开发的一门语言。它的名字来自丹麦数学家 Agner Erlang;通信工程中的话务量单位 Erlang,也以他的名字命名。
这门语言最让我意外的,是它“没有变量”。
更让我疑惑的是,Erlang 必须运行在它自己的虚拟机上。
通信系统讲究实时性、稳定性和高并发。一门需要依赖虚拟机执行的语言,真的适合做电信级系统吗?
我没有深入研究。那时候,我对它只是觉得新奇,还没有产生真正学下去的兴趣。
没想到几年以后,我重新遇到了 Erlang。
这一次,它不再是茶余饭后的谈资,而是成了我创业生死局里最重要的武器。
页游之都的“公共资产”
2012 年,我和前同事老 K 合伙创业,开始做网页游戏。
那几年,广州被称为“页游之都”。
这个称号听上去很有气势,仿佛走在广州街头,随便踢倒一个垃圾桶,里面都能滚出几个游戏制作人。
实际上也差不多。当时广州有几家头部页游公司,后来不少核心员工离职创业,成立了大大小小的工作室。
这些团队出来创业时,手上往往都带着一些过去积累的“家当”。有人带着运营经验,有人带着美术资源,还有人直接带着一套游戏核心代码。
于是,一套代码从一个团队流向另一个团队,再经过几轮转手、修改和二次开发,逐渐成了广州页游圈里一套广泛流传的框架。
我们创业时,也接触到这样一套游戏框架。
它的后端,正是用 Erlang 开发的。
这是我第一次真正正面遭遇这门语言。它不再存在于技术文章或实验项目里,而是结结实实地躺在我们的服务器里,承载着我们全部的创业身家。
这时候,语言好不好已经不再是学术问题。它关系到游戏能不能做出来,我们的公司能不能活过第一天。
“草台班子”也能赚钱?
我第一次看到那套后端框架时,内心经历了两个极其撕裂的阶段。
第一个阶段是:
“这写的什么垃圾代码。”
第二个阶段是:
“这都能赚到钱?!”
这两个阶段过去之后,我顿时对创业的未来充满了信心。
一个人最容易产生自信的时候,往往不是发现自己有多厉害,而是发现一些在自己看来能力平平的人,也已经赚到了大钱。这就是所谓的“草台班子理论”吧。
那套框架最明显的问题,是滥用了 Erlang 的轻量级进程。
Erlang 的进程非常轻量,一个系统里可以同时存在大量这样的进程。我估计之前的程序员看到这个特性以后,产生了一种朴素而狂热的理解:既然进程开销这么小,那就多开几个!
于是,一个玩家身上被拆出了无数个进程。角色一个进程,任务一个进程,背包可能又是一个进程,其他业务再继续往上叠。
看上去模块分得很细,架构也很“Erlang”。
但问题在于,一个玩家自己的大部分事件,本来就需要串行处理。玩家不可能同时完成两次互相冲突的操作,很多状态修改天然具有极强的时序性,并没有为了并发而拆分进程的必要。
原来的设计,某种程度上把“可以并发”误解成了“必须并发”。最后,代码不仅没有变得优雅,反而增加了大量进程之间的状态协调和消息交互,变成了一个极其臃肿的迷宫。
看着这乱成一团的代码,我当时只是觉得好笑。但我怎么也没想到,正是这套被我嫌弃的“垃圾框架”,在三个月后,差点直接把我们的创业公司送进火葬场。
《Erlang 程序设计》
代码虽然写得不好,但用 Erlang 做游戏后端,客观上却是一个相当大胆、也相当合适的选择。
Erlang 本来是为通信系统而设计的。通信系统需要同时处理大量连接、消息和状态变化,整个系统天然要求面向事件、异步运行,同时具备很强的健壮性和故障隔离能力。这和游戏服务器面对的场景其实非常相似。
不同玩家同时在线,不断产生登录、战斗、交易、聊天等事件。服务器既要处理高并发,也要保证某个玩家或者某项业务出错时,不至于影响整个游戏。这种面向事件、异步消息和状态隔离的架构,对我来说并不陌生。
真正需要我重塑的,是函数式编程的思维,以及变量一旦绑定就绝不可变更的铁律。
也正是在这个阶段,我研读了 Erlang 主要设计者 Joe Armstrong 写的《Erlang 程序设计》,开始真正理解这门语言背后的设计思路。
命令式编程习惯不断修改变量,而在 Erlang 里,逻辑需要通过函数调用和状态传递来完成。这种思维方式刚开始极其痛苦,但也确实断绝了变量被随意修改和状态共享带来的隐患。
另外,Erlang 提供的监督机制和不需要停服的热更新能力,简直是为网页游戏量身定制的武器。
所以我一直认为,在当时广州页游圈广为流传的几套后端框架里,Erlang 这一套,比另外两种常见方案——Lua/C++,以及 Java——更适合中小型页游团队。至少,它很适合当时的我们。
Elegant 是绊脚石
虽然我对那套代码有一万个不满意,但最终,理智让我克制住了大幅改造它的冲动。我甚至特意把 QQ 签名改成了一句话:
“Elegant 是绊脚石。”
这句话当然不是真的反对优雅。程序员谁不喜欢代码层次分明、结构清晰?
但创业以后,我慢慢发现,很多技术上的“优雅”,一旦脱离了时间、成本和业务目标,就可能变成技术负责人的一种自我满足。
你觉得架构还不够漂亮,产品却在等着上线。你觉得技术债必须一次清完,竞争对手已经开始买量了。你觉得代码需要彻底重构,老板——也就是你自己——看了一眼银行账户,觉得原来的代码其实“又不是不能跑”。
我克制住了强迫症,只是增加了一些基础服务和开发工具,提高整个团队的开发效率。
现在看来,这种克制可能是当时最正确的决定之一。创业公司的第一目标,不是证明技术负责人的审美有多高级,而是先把产品做出来。
公司得先活下来,代码才可以以后再优化。
大概三个月后,我们完成了游戏的第一个版本。经过几轮测试,当年 11 月,游戏开启了第一次不删档付费测试。
对于玩家来说,游戏真正开始了;对于我们来说,意味着不再有任何容错空间。
同一天的开服与关服
游戏上线之初还算顺利,在线人数稳定在一千人左右。
就在大家稍微松了一口气时,运营突然反馈了一个致命问题:有个玩家账号,正在产出海量的“铜钱”。
铜钱是游戏里的基础货币。这个产出速度已经远远超过了正常玩家,游戏内的经济生态眼看就要崩盘。
其他玩家也注意到了这个“神仙账号”。不过,他们并不认为这是漏洞,反而更愿意相信这是官方安排的“托”。
这个误会对我们产生了一种奇特的保护作用——至少在真相暴露之前,玩家只是怀疑我们不太厚道,还没有发现我们可能不太专业。
但留给我们的时间已经不多了。这是不删档付费测试,玩家已经开始充值,所有数据都不能简单回滚。如果问题解决不了,游戏开服的第一天,很可能也是正式关服的那一天。

不要连生产环境
我们首先检查日志和业务数据,但一时没有找到漏洞到底出在哪里。
我等不及了,决定直接连上生产环境的调试控制台。
老 K 转过头,脸色惨白地阻止我:“不要连!会导致在线玩家掉线!”
但他话还没有说完,我的手指已经敲下了回车。
老 K 马上看了一眼在线人数,原本稳定的一千人,瞬间拦腰斩半。
办公室里的空气仿佛凝固了,空调的嗡嗡声无限放大,现场气氛一度非常紧张,如果这时候问题还找不到,我可能不仅要修游戏,还要先修复一下合伙人对我的信任。
我咬着牙顶着压力,顺着进程一路排查运行上下文。两分钟后,漏洞终于暴露出来。
这是一个极其低级却致命的漏洞:玩家通过客户端协议,向服务器传入了一个负数。原本应该扣除资源的逻辑,在负数参与运算之后,“减去一个负数”变成了“加上一个正数”,扣费变成了送钱。
漏洞本身并不复杂,但它意味着玩家已经开始主动篡改客户端请求。一旦走到这一步,类似的问题很可能不止一个。
极其惊险的“热插拔”救场
找到问题之后,真正的生死考验来了。如果是在传统大厂,这时候需要提测、打包、发公告、停服维护,但当时每一分钟都在流血。
这时候,Erlang 的威力真正显现了出来。
我迅速修改了代码,补上参数校验,然后利用 Erlang 的热更新机制,直接把补丁加载进了正在运行的生产环境。
没有停服,没有重启,没有发临时维护公告。
新的代码被加载的瞬间,异常产出戛然而止。那个正在疯狂制造铜钱的账号,瞬间失去了它的无限印钞机。
随后,我又在前后端通信协议上加了一层加密。虽然不能从根本上让协议从此坚不可摧,但在当时,它至少大幅提高了客户端篡改的门槛,为我们继续完善服务器端校验争取了宝贵的时间。
游戏没有停服,不删档付费测试得以继续。我们靠着 Erlang 这种近乎“现场开颅”的惊险操作,把公司从悬崖边拉了回来。
非常时候,需要非常方法
我们团队当时常常半开玩笑、半认真地自称是“电信级(Carrier Grade)团队”。
我们都来自大型通信设备企业,经历过最严格的研发流程,习惯了评审、测试和规范发布。那时候的我们,多少带着一些外企技术人的傲慢,甚至有些看不起国内同行在客户现场直接改代码的“野路子”。
但真正创业以后,轮到一千名玩家在线,充值、口碑和公司的生死都压在一个漏洞上时,傲慢被现实砸得粉碎。
那一天,我直接连上生产环境导致一半玩家掉线。从标准操作来看,这显然是一次严重的教学事故——我先制造了一个事故,才解决了另一个事故。
但如果再来一次,在当时的信息和条件下,我大概还是会做同样的选择。因为如果不能马上止血,面临风险的就不是掉线的一半玩家,而是整个游戏的生命。
规范和流程当然重要,没有它们,团队迟早会为混乱付出代价。
但流程的意义,是帮助我们控制风险、解决问题,而不是在危机真正发生时,让我们躲在后面当成免责的挡箭牌。
大型团队可以依靠完整的体系控制风险,创业团队却没有那么多缓冲,也没有多少重新来过的机会。
后来我才明白,我们过去有些看不起的“野路子”,并不一定代表技术能力不足。有时候,那只是另一种残酷环境下,倒逼出来的独特生存方式。
非常时候,需要非常方法。
而真正困难的,是在打破规则的瞬间,你清楚地知道代价是什么,并且有勇气去承担后果。
技术之外
那一次,我们靠技术和运气解决了问题,让游戏继续运行了下去。
也是从那以后,我慢慢发现,技术做得再好,离一款游戏真正成功,中间还隔着产品、运营、渠道、市场,以及许多远比代码复杂得多的东西。
而我对这些未知领域的探索,也从那个惊心动魄的不删档测试,真正拉开了序幕。
