2010年5月《程序员》杂志“架构师接龙”栏目中的问答
2010-05-21 09:48 by 老赵, 5468 visits上个月《程序员》杂志向我约稿,希望我可以参加5月份的“架构师接龙”栏目,我略为犹豫了一下便答应了。“架构师接龙”是一个问答形式的栏目,每期由一个人提问,并由另一个人回答。回答的一方便是下期的提问者。这次提问的架构师是新浪微博的技术经理杨卫华。他提出的问题包括语言选择与架构设计、NoSQL存储方案的取舍、微博类系统的架构等多个方面。杨卫华是国内技术社区一等一的高手,这使得我在回答问题时更有小心翼翼地班门弄斧之感。如果您对某些问题感兴趣,也不妨来一起讨论一下。
语言选择与架构设计
提问:很多架构师表示编程语言不重要,架构设计思想才重要,但是大部分团队都是非常依赖某种语言的,甚至很多项目负责人也存在对某种语言存在偏好而对另外一种语言反感的现象。你怎么看待编程语言选型问题?同时业界也存在另外一种现象,很多前沿技术研究者对一些新兴语言如Erlang, Go等表示出狂热,你对团队或项目中是否引入这些新的语言持什么观点?
回答:在我看来,编程语言的选择也是至关重要的。诚然,架构的设计思想可谓直接决定了系统本身的质量。与此相反,从理论上说,只要是图灵完备的语言,就不存在“能力”上的根本差异,任何工作都是可以实现的。但是,有一点也无法被忽视,那便是我们使用的语言,往往也会影响,甚至决定了我们的思维方式。
举个有些极端的例子,如果人们还在使用汇编语言进行开发,那么估计程序员的思维永远无法跳出“子过程”这个抽象级别,什么面向对象设计,函数式编程几乎无从谈起。人们在生产和学习过程种会引发一些需求,因而需要产生一些工具来辅助学习和生产,而“语言”便是此类工具之一。只有利用高级语言,人们才能有效地把真实世界抽象成计算机这些机器盒子能认识的东西。
如今,可用于构建项目可选的主流语言往往都会有多种,有时候的确会发现,好像不同的语言──例如Ruby和Python──从各方面来说并没有太大区别。这其实也是比较正常的,因为某些(甚至是大部分的)语言特性,并没有对我们的“思维”产生影响。
举例来说,某些喜欢Ruby的朋友认为Ruby语言的编程体验非常良好,好比它的数组操作可以直接做加法或减法:
array = ['aaa', 'bbb', 'ccc'] - ['ccc', 'ddd']
或者说,在Python里交换两个变量的值也只需要一行代码(大部分语言中可能需要借助中间变量):
a, b = b, a
但是,就我个人观点来说,这些语言特性,虽然它们的确可以让编程工作变的相对轻松一些,例如可以让我们少些一点代码,但终究没有改变,或是表现出另一种编程思维。这样的语法特性,一般来说都可以通过构建一些简单的辅助函数来做到类似程度的“简化开发”(如上面Ruby的例子),对于那些非“即写即抛”的程序来说,这些特性的优势并不明显。
而与此相比,Ruby的Mixin机制和Python的Decorator就不仅仅是“语法糖”,而是比较重要的语言特性了,因为它们可以带来或大大简化某些十分有用的编程模式。
不过对于语言的选择有时候还需要往更高处看。现今一些新出现,或者新流行起来语言,对于系统开发方面的影响则更为深远。举个例子,目前说起并发/并行程序设计,无法忽略的便是Erlang语言。这门语言提供了一种构建轻量级计算单元(在Erlang中被称为“进程”),使用发送消息(Message Passing)的方式进行相互间的通信。这种做法避免了共享状态(Shared State)方式下容易出现的各种问题,并且在其独特的虚拟机实现下可以得到很强大的并发能力。但是,Erlang的任务调度机制有个特点,那便是它会为每个“进程”分配相同的计算能力。这样,如果系统中有1000个进程,那么每个进程得到的计算能力便会是100个进程时的十分之一。这种调度方式对于某些类型的应用来说可能并不合适,因为它可能在并发压力增大的情况下,造成吞吐量的降低甚至完全停止服务(因为每个任务都超时了)。Erlang的这个特性往往会直接影响到系统的架构方式。
不过在某些场景下,我们也可以选用其他的语言。例如Scala,它同样提供了基于Actor模型的消息传递并发机制。但是它的调度方式与Erlang不同(事实上由于平台功能限制,它也无法实现Erlang的调度方式)。由于Scala的Actor模型构建与JVM之上,因此它只能准备一个线程池,让其中的线程不断地处理消息的传递及处理任务,而额外的任务则会在队列中等待。因此,Scala使用的并不是Erlang那样完全公平的调度方式,但是这样反而可以优先处理先出现的任务,保证稳定的吞吐量。
因此,Erlang和Scala这两种不同的调度机制,决定了它们适合不同的应用场景,或是系统架构的不同方式。我相信Facebook选用Erlang构建聊天平台,Twitter选用Scala构建消息中间件都是有这方面考虑的。
当然,调度方式更像是由平台决定,而不是语言决定的。不过在刚才的特定问题上我认为两者其实是统一的。因为Erlang既是门语言,也代表了一个平台。而Scala虽然是JVM平台上众多语言之一,但也只有它能够优雅的实现Actor模型的消息传送机制。我始终认为,一个语言特性只有真正“好用”,它才能被人们广为接受。例如,使用Java语言能实现Actor模型吗?能,但是它缺少Scala那样灵活的函数式语法,以及模式匹配等特性,因此无法构建出一个好用、易用的Actor框架,自然也就无人问津了。这其实也是“语言影响思维方式”的典型案例之一。
异步及并行是如今系统构建不可或缺的因素。如今的新语言大都在这方面下了很大功夫。除了Scala和Erlang以外,在微软.NET平台上的新语言F#引入的创新特性“计算表达式(Computation Expression)”,使用类似于Monad的机制大幅度简化了异步程序的开发难度。而JVM中的Clojure语言也引入了软件事务内存(STM,Software Transactional Memory)。我们几乎可以这么说,如今每种新出现的语言都有独特的“杀手级”特性,它们都是影响系统开发的重要因素,使用语言本身的支持可以显著降低系统的开发难度,增加可维护性和健壮性,这些并非是由架构改进可以轻松获得的效果。
如今“多语言”开发逐渐成为一种趋势,例如在Facebook的各个子系统分别使用了C、C++,Erlang,Java等多种语言/平台,然后使用PHP作为黏合剂连接起来。而Twitter也毫不例外地使用了Ruby,C,Scala和Java。现在的系统日趋复杂,几乎没有任意一种工具可以完全适合系统的全部开发,为系统不同的组成部分选择合适的语言,也是如今架构师需要面临的挑战。
与过去不同,现在即使确定构建系统所用的平台之后──如JVM,也会发现语言的选择余地会有很多,不同的语言的确也有不同的特性,可以带来一些特别的优势。例如,利用Ruby的动态特性,便可以方便地进行单元测试。而系统的生产部分代码,可能便可以选用Scala等静态编译型语言,以便借助更完整的静态检查工具来确保更加稳固的产品质量。
对于新语言的选取,不同风格的架构师会采取不同的策略。例如保守的架构师可能会根据语言所在社区是否活跃,语言相关资源是否丰富,相应的程序员是否容易招聘来考虑语言或平台的选择。这种做法是十分正常的。但是万事都讲究个平衡,在某些情况下“保守”和“抱残守缺”或“固步自封”之间仅一步之遥。
我个人的风格相对比较“激进”,十分乐于吸收和尝试一些新事物。我的建议是,每个技术团队都应该挑选出几个技术水平较高,经验较为丰富的成员,广泛的吸收新事物的发展,并在合适的时候向团队及生产环境做出提案,以改进系统的开发效率或者质量。由这些高级技术人员进行引导,往往可以较好的预估新技术对于产品的影响,即便出现了一些问题也可以设法自行解决。
据我了解,一些较为活跃的技术团队,尤其是一些Web 2.0产品的技术团队,在这方面都有比较好的实践。
NoSQL存储方案的选型
提问:最近很多公司有向NoSQL方向发展趋势,很多架构师也关心是否需要将关系数据库转向NoSQL,请问能给正在选型的架构师哪些建议?
回答:我的个人看法是,NoSQL本身是好东西,但是在使用的氛围方面稍有一些扭曲。可能是受到关系型存储方式的“压抑”太久,如今冒出一个NoSQL运动让人眼前一亮,不由得热血沸腾起来。
NoSQL的出现,原本不是为了完全取代关系型数据库,而是为了应对关系型数据库在性能和伸缩性方面的缺陷而提出的存储方式。NoSQL不应该是“No SQL”,更为妥当的方式应该是“Not Only SQL”。
放眼如今比较成功的NoSQL应用,似乎除了Google由于数据规模,资源沉淀等原因之外,其他系统大都是将NoSQL作为一种优化的手段在使用,而并非是作为系统的主要存储方式,它们主要使用的存储方式依然是MySQL等关系型数据库。而事实上,各系统也是在架构演变过程中,发现关系型数据库成为了系统优化的瓶颈,进而在一定程度上引入NoSQL存储方式以改善性能。
例如就在不久之前,SourceForge宣布将在系统中引入MongoDB,而Twitter也打算开始使用由Facebook创建的Cassandra。但是以SourceForge与Twitter目前基于关系型数据库所支撑起来的规模,也已经是无数系统难以企及的目标了。更何况,如Stack Overflow这样号称全世界最大的程序员网站,作为存储后端也只是使用了单台关系型数据库。
毕竟,关系型数据库的性能并非差到不可接受,NoSQL的优势也只有在达到一定规模时才能体现出来。而且,除了存储方式以外,系统中可以优化的地方还有很多。例如最传统的,缓存,一个实现较好的缓存机制可以减少95%以上的数据库访问,这对于系统性能的影响也是相当巨大的。
在目前,使用NoSQL存储方式的另一个不便之处便是工具的缺失。我在项目中也使用了MongoDB,一个十分明显的体会便是,对于MongoDB的操作比关系型数据库要麻烦不少。例如在访问关系型数据库时可以利用现成的映射工具,经过多年发展此类工具也已经变得非常灵活、高效,能够应对绝大部分的使用场景。而在使用MongoDB时,我似乎又有了回到当年裸写JDBC的感觉了,甚至对于某些平台来说,连一个成熟的驱动(例如有连接池的支持)都需要亲自动手开发一个。对于一个有经验的开发人员来说,便写一些“够用”的代码自然不是难事,不过这的确也是一件会影响投入产出比的事情。
此外,NoSQL虽然性能高,但这也是通过在一定程度上牺牲数据的完整性或一致性的保证换来的,传统的关系型数据库却在这方面投入了很大的精力,例如事务机制,虽然会降低性能,但是却保证了数据的一致性。但是,如今的NoSQL存储几乎都没有提供类似的机制(毕竟有个无法回避的CAP规律),这样多个相关的操作一旦中断(例如出现了异常),则很容易造成数据“此长彼短”的现象。而且,如今的NoSQL产品出于性能考虑,几乎无一例外会带有一定程度的缓存机制,不会将新建或更新的数据直接写入磁盘。因此,如果没有一个集群环境,在遇到突发状况时则很可能带来数据丢失的情况。对此,如MongoDB明确指出,它对于单机的持久性并不十分重视,它的设计人员以此换来更为重要的参数:性能。这意味着,一旦使用NoSQL作为主要存储方式,则往往会需要同步跟进一些周边措施,例如可能要在保证数据的最终一致性方面投入较多的精力。
当我们确定要选择NoSQL存储方式的时候,则必须根据自己的业务特征选取特别的NoSQL产品。目前NoSQL主要分为四大类:BigTable,Key-Value,文档型,及图数据库。它们有各自的性能优势及适用范围。例如Key-Value存储方式支持的查询方式非常有限,但是由于结构简单,它的性能和伸缩性可谓傲视群雄。而文档型数据库,如MongoDB,它所支持的查询方式非常灵活,并内置Map Reduce机制,可以直接输入JavaScript脚本进行一些特殊的数据处理及汇总。而如Neo4j这样的图数据库,由于直接支持“节点”、“(有向)关系”等概念,则对于一些关系型数据库、文档型数据库难以应对或建模的查询或遍历方式(如最短路径计算),就有了非常直接、自然且高效的支持。
总之,架构师选择的不是SQL或NoSQL本身,而应该只是“最合适”的东西。
关注的方向和领域选择
提问:很多架构师都喜欢学习Google, Facebook等大型系统的经验,但另外不少架构师则认为绝大多数网站都不会成长成一个“大型网站”。绝大多数工程师都没有能力建立和维护一个类似GFS的系统。对绝大多数网站而言,把时间耗在所谓“大型网站”的架构上没有意义,你怎样看待这种说法,架构师应该如何选择关注的方向和领域?
回答:我在这方面的看法是,虽然Google,Facebook等大型系统的规模对于绝大多数人来说可能是永远无法接触到的,但是它们的经验及措施可能会给我们带来一些其他方面的体会。
例如,Map Reduce原本是函数式编程中再普通不过的概念和手段,但是Google将它和GFS等其他基础设施一结合,便成为了一个无比强大的分布式计算技术。但是,Map Reduce它本身还是十分简单的东西(Google Map Reduce实现的复杂点主要还是在于GFS),它也并非Google专有的东西,我们受到这样的启发也可以将其用到别处。例如在MongoDB和CouchDB中都内置了MapReduce支持,在去年的QCon Beijing大会中,FreeWheel公司在它们的广告平台中,也使用了自己的方式实现了Map Reduce计算机制。
因此,即便是无法成为真正的巨人,也可以关注巨人成长过程中所吸取的经验教训,从中也可以得到一些启发。即使是当作一个有趣的故事去了解也好,即使是为了打开眼界也好。有时,我们需要的可能只是一个不经意的提示。
微博类产品的架构难点
提问:目前国内很多互联网门户都在做微博产品,你觉得微博技术架构的主要难点在哪些方面?
回答:从复杂度上面来说,微博产品的业务是相对比较简单的,我认为它在技术架构上有两大要素:消息传递与缓存。
微博产品从产品性质上来说几乎完全就是一个消息分发平台,因此一个良好的消息传递机制是至关重要的。当一个用户发出消息之后,它可以被许多人观察到。对于一个名人来说,被数十万用户所追随是一件非常普通的事情。那么此时,如果期望所有的追随者都即时地看到消息这几乎是件不可能的事情,因此在实现时我们往往需要构建一个消息队列,将消息快速地派送至队列中等待处理,最终将这条消息“陆续地”显示在每个追随者的时间轴上。这里势必会产生延迟,但对于业务质量来说并非不可接受。但显然这个延迟也不可以太长,在Twitter上这个平均延迟是500毫秒,从绝对数值上看并不算太短,但也已够用。Twitter在这方面的处理方式是利用Scala的Actor模型及Apache Mina编写了一个分布式的消息传输框架Kestrel,它具有快速、轻量(包括注释才不到2000行代码)、持久、稳定等特性,但不具备事务性,也不保证消息的顺序处理。因此可以这么说,Kestrel是一个Twitter根据自身需求“定制”出的消息传输机制。
另外一个要点便是每个大型系统都不可或缺的缓存机制。有人说缓存就好比万能膏药,哪儿不舒服就再哪儿擦点便能见效。这话有一定道理。如Twitter便设计了相对复杂的多级缓存机制,几乎对于每个IO密集的地方都进行了缓存。例如记录ID序列的向量缓存(Vector Cache),纪录每条消息等具体内容的行缓存(Row Cache)。此外,由于它的API访问量很大,仅仅是从消息内容转化成API的输出形式(可能只是一些字符串连接操作)也会消耗较多代价,因此Twitter还为消息的API输出形式设计了片断缓存(Fragment Cache)。最后自然还有对某些热门页面的页面缓存(Page Cache)。除了页面缓存之外,其他缓存的命中率都在95%以上,可见缓存机制对于Twitter系统的重要程度。值得注意的是,向量缓存及行缓存都是Write Through的,这意味着基本上所有的新数据都在缓存中存在一份拷贝。正如Twitter的Evan Weaver在QCon London 2009会议上讲的那样:Everything runs from memory in Web 2.0。
最后,对于微博类应用来说,可能会因为某个突发事件造成访问量暴增的现象,如何抵抗住消息轰炸也是一个重要的课题。例如Twitter使用了云计算的方式来应对此类问题,在需要的时候它便会租用更多的计算资源。不过增加服务器只是纯硬件的投入,而架构设计能否顺利且充分地利用起新增的设备也是一个值得关注的地方。从这个角度看,一个高效的分布式消息传递机制在这里会扮演重要的角色。如果有了合适的消息机制,首先能够将消息负载较为容易地平衡至多台服务器上,其次即使是在压力增大的情况下,响应时间虽然会按线性增长,但是系统的吞吐量还是可以保持在正常的水平。
给未来架构师的建议
提问:很多工作2-3年的软件工程师谈到职业规划都是希望往架构师方向发展,请问能给这些正在成长的工程师哪些建议?如何才能成为一个优秀的架构师?
回答:其实我也不知如何给出一些有效而具体的建议。我认为架构师不是一个职位或是职责,而更像是一种思维方式。其实只要打开眼界,不断吸收和关注技术及业务的发展,待积累到一个合适的时候便可以对系统架构提出自己的思路及建议的时候,那你就是一个架构师了。
其实每个程序员都可以是架构师。
刚好昨天买了那书,看到了。哈哈。