t1

架构的从0到1与从1到10:演进与创新

但上线了几个客户以后,Hadoop系统开始出现性能问题。最先发现的是Log的Parser竟然用了Reflection,内存也出现了空间不足的征兆。调整了一段时间以后,Diane找到了Hadoop方面的专家来帮忙调优。专家开门见山就问:“你们的Cluster有几百台?”我们看了看机房里三台廉价服务器,对话就没能有效地继续下去。当时报表系统的业务需求相对简单,用Hadoop有点大材小用,何况当时Hadoop的版本是0.15,稳定性差。于是仅一个周末的时间,我们用Python做了个原型直接将结果统计出来,大概有100行代码。我们的Log格式起初是文本的,而业务比较复杂,结构化的格式会更加适用。于是我们扩充了重构的范围,让这个改变更大一些,用Protocol Buffers记Log,同时用C++/Python写数据统计。这个C++/Python的程序基本上类似于简化的Hadoop,在固定的节点上做预处理和预统计(类似Map),然后在一个中央节点汇总到最终的结果(类似Reduce)。这个过程比之前提到的广告服务器设计要困难一些,因为需要换掉一个在线系统。写代码的工作相对简单,在两三个星期之内完成几千行代码。而这个很简单的架构撑到了现在,直到我们换成基于Hadoop和Presto的新数据平台。这个简单架构的优点和缺点都很明显——简单可操控,但也带来了很多功能和灵活性方面的限制。

在我们忙于报表系统的同时,预测系统也上线了。

第一个版本的预测系统基于Erlang,毕竟Mnesia、高并发和Process Supervisor模型看起来很漂亮。虽然函数式编程有些不一样,但是代码写得也很快。而上线以后发现Mnesia性能存在问题,更重要的是Erlang标准库OTP某些代码的质量实在令人担忧。系统崩溃几次之后,同时系统变得复杂使Erlang代码读起来有些困难了,同时我们也希望采用一个采样模拟的方案来提高准确度,需要重用广告服务器的代码来测试模拟的请求,最终我们决定用C++重写一个版本。这个重写的版本参照了Erlang版本的结构,比较复杂,维护起来依然有些困难。于是我们在2011年又推倒重来了一次,抛开历史负担,写出了一个更简单的版本,整个重写过程用去了一个多月时间。这个版本的架构一直维持到今天。总体来说后端几个系统的大重构还算顺利,原因主要在于重构的比较早,较好地控制了代码数量,人员相对稳定,以实现速战速决。

与此同时,客户端的类库也经历了一些波折。首先我们尝试用Haxe来同时支持Flash的AS2和AS3。紧接着随着iPhone的崛起,又做了iOS的类库,接着是Android的类库。同时有客户要用Silverlight的集成,我们又做了Silverlight的库。随着技术的发展,Flash开始走下坡路,用户普遍切换到HTML5,从而JavaScript的类库应运而生。不仅如此,业界开始制定标准,从VAST的各个版本到VMAP的各个版本,还有VPAID的支持。加之各种设备的出现和普及,比如Roku和Sling Box,每个细分领域都想要集成。如果Android的生态系统是碎片化的,那么这个生态系统可以说是颗粒化了。支持这么多的平台基本是不可能的,投入产出比就更低了。可想而知,如果战线铺得太宽,代码质量下降得也比较明显。

综上,我们采取了以下策略:第一,标准化。鼓励客户使用标准协议;第二,服务端化。把一些功能转移到服务器端来实现,简化客户端的逻辑,避免在每个平台都要实现一套类似的逻辑,举例来说工程师设计了一个在客户端做HLS Proxy的方案,来支持向HLS视频流里插播广告的功能,我们把这个逻辑搬到了服务器端;第三,尽量尽快终止不常用的集成,例如AS2和Silverlight;最后是更加激进地简化系统,比如我们最初设计了一套复杂的Android自动升级方案,客户端先导入一个Loader,然后由Loader导入最新的库版本,结果很多Bug都出现在这个Loader上,网络延迟故障、安全和各种时序等导致了很多不必要的问题,因此我们决定把这个功能取消,让用户直接链接库。

与此同时,基于Ruby on Rails的UI系统也在快速发展。代码数量甚至超过了其他几个系统的总和,而且Rails版本一直都未升级。2010年左右我们遇到了第一个挑战:需要升级到当时比较新的Rails 3版本。除了大量的代码改动,还包括库的变动和升级。这项工作耗时数月,但总体上对代码质量的改善却十分有限。之后我们甚至举行了一个简化代码的比赛,哪个团队每个版本删除的代码行数最多,就能得到额外的奖励。结果这个活动也无疾而终。随着大型代码库增长而来的,是产品质量的下降和团队成员的不稳定。在2012年前后,尝试模块化整个UI系统,形成几个小系统,并且把公用部分抽取出来做UI Foundation,进展都不太顺利。结果是原来一个有问题的系统,反而变成了多个有问题的系统。从中亦获得了不少经验教训:最重要的问题是没有尽早对代码的质量进行监控和管理;其次是业务变化快、人员不稳定;Rails框架本身存在问题,以及制定计划的时间线铺得过长等等。当然这几个因素也是互相影响的。

总之,FreeWheel初期发展面临的挑战是各个创业公司所共有的,即各种不确定性,包括快速变化的业界图景、业务需求、技术方案以及团队组织。在此背景下,技术架构应相应迅速地进行调整。我们积累的主要经验在于,满足需求的情况下尽量简化设计去实现,并且尽快响应变化,同时为了避免技术债务的累积,要控制好代码总量。有一段时间我们只强调一个评判标准并力求使其深入人心,即代码行数——一方面要实现功能需求,另一方面要压缩代码行数。结果也有些小插曲,比如某一次一个工程师为了避免返工,把Python代码全堆到了一行上。