第二部分 淘宝技术发展——Java时代 脱胎换骨
我的师父黄裳曾经说过“好的架构图充满美感”。一个架构好不好,从审美的角度就能看出来。后来我看了很多系统的架构,发现这个言论基本成立。反观淘宝以前两个版本的架构,如
下图所示,你看哪个比较美?
显然,第一个比较好看,第二个显得头重脚轻,这也注定了它不是一个稳定的版本,只存活了不到半年的时间。2004年初, SQL Relay的问题解决不了,数据库必须要用Oracle,那么从哪里动刀呢?只有换开发语言了。换什么语言好?用Java。Java是当时 最成熟的网站开发语言,它有比较良好的企业开发框架,被世界上主流的大规模网站普遍采用。另外,有Java开发经验的人才也比 较多,后续维护成本会比较低。
到2004年上半年,淘宝网已经运行了一年的时间,这一年积累了大量的用户,也快速开发了很多功能,当时这个网站已经很庞大了,而且新的需求还在源源不断地增加。把一个庞大的网站的开发语言换掉,无异于脱胎换骨,在换的过程中还不能拖慢业务的发展,这无异于边换边跑,对时间和技术能力的要求都非常高。做这样的手术,需要请第一流的专家来主刀。现在再考一下大家:亲,如果你在这个创业团队中,请什么样的人来做这件
事?我们的答案是请Sun公司的人。没错,就是创造Java语言的那家公司,世界上没有比他们更懂Java的了。除此之外,还有一个不 为人知的原因,我刚才说到Java被世界上主流的大规模网站普遍采 用,其中有一个网站就是eBay,那时侯eBay的系统刚刚从C++ 改到Java,而且就是请Sun的工程师给改造成Java架构的,这下你懂 了吧?他们不仅更懂Java,而且更懂eBay。
Sun公司的这帮工程师的确很强大,在笔者2004年年底来淘宝的时候,他们还在,我有幸与他们共事了几个月。现在摆在他们面前的问题是用什么办法把一个庞大的网站从PHP语言迁移到
Java?而且要求在迁移的过程中,不停止服务,原来系统的bugfix 和功能改进不受影响。亲,你要是架构师,你怎么做?有人的答案是写一个翻译器,如同把中文翻译成英文一样,自动翻译。我只能说你这个想法太超前了,“too young, too simple, sometimes naive”。当时没有,现在也没有人能做到。他们的大致方案是给业务分模块,一个模块一个模块地渐进式替换。如用户模块, 老的member.taobao.com继续维护,不添加新功能,新功能在新的模块上开发,跟老的模块共用一个数据库,开发完毕之后放到不同的应用集群上,另开一个域名member1.taobao.com,同时再替换老的功能,替换一个,就把老的模块上的功能关闭一个,逐渐把用户引导到member1.taobao.com,等所有的功能都替换完之后,关闭member.taobao.com上。从设计上来看,这个member1的
二级域名应该是一个过渡状态,但我们把member域名的代码下线以后,发现很难把member1切换回member,因为有些地方把链接写死了,于是后来很长时间里我们都是在用member1.taobao.
com这样奇怪的域名。一年后,有另外一家互联网公司开始做电子商务了,我们发现他们的域名也叫member1.xx.com、auction1. xx.com,复制得毫无保留,我们只能会心一笑。
说了开发模式,再说说用到的Java MVC框架,当时的struts1.x 是用得比较多的框架,但是用过webwork和struts2的人可能知道, struts1.x在多人协作方面有很多致命的弱点,由于没有一个轻量框架作为基础,因此,很难扩展,这样架构师对于基础功能和全局功能的控制就很难做到。而阿里巴巴的18个创始人之中,有个架构师周悦虹,他在Jakarta Turbine的基础上做了很多扩展,打造了一个阿里巴巴自己用的MVC框架WebX ( http://www.openwebx">http://www.openwebx. org/docs/Webx3_Guide_Book.html ),这个框架易于扩展,方便组件化开发,它的页面模板支持JSP和Velocity等,持久层支持
ibatis和hibernate等,控制层可以用EJB和Spring(Spring是后来才有的)。项目组选择了这个强大的框架。另外,当时Sun在全世界大力推广他们的EJB,虽然淘宝的架构师认为这个东西用不到,但他们还是极力坚持。在经历了很多次的技术讨论、争论甚至争吵之后,这个系统的架构就变成了下图的形式。
MVC框架是阿里的WebX,控制层用了EJB,持久层是ibatis。另外,为了缓解数据库的压力,商品查询和店铺查询放在搜索引擎中。这个架构图是不是好看了一点了?
Sun的这帮工程师开发完淘宝的网站之后,用同样的架构又做了一个很牛的网站,叫“支付宝”。(上一篇说过支付宝最初是淘宝上的“安全交易”功能,这个功能后来独立出来,成立了一个网站,也成立了一个公司,就是现在的支付宝。把支付宝从淘宝分出去的人,就是Sun公司的这几个人。)下图是支付宝的第一次员工大会。
上面的架构中,引入了搜索引擎iSearch(前文说过,iSearch 其实是在LAMP系统运行一段时间之后被多隆引进的,换为Oracle 之后只是替换一下数据源)。其实这个搜索引擎的原理很简单, 就是把数据库里的数据dump(倾倒)成结构化的文本文件后,放在硬盘上,提供Web应用以约定的参数和语法来查询这些数据。这看起来不难,难的是数以亿计的信息,怎么做到快速更新呢? 这好比你做了一个网站,在百度上很快就能搜到,你一定很满意了。但如果你发布一件商品,在淘宝上过1个小时还搜不到,你肯定要郁闷了。另一个难点是如何保证非常高的容量和并发量?再往后面就要考虑断句和语义分析的问题,以及推荐算法等更加智能的问题。这些内容先不详细介绍,因为搜索引擎的技术已经足以写好几本书了。
其实在任何时候,开发语言本身都不是系统的瓶颈,业务带来的压力更多的存在于数据和存储方面。前面也说到,MySQL撑不住之后换为Oracle,Oracle的存储一开始在本机上,后来在NAS 上,NAS撑不住了用EMC的SAN存储,再后来,Oracle的RAC撑不住了,数据的存储方面就不得不考虑使用小型机。在2004年夏天,DBA七公、测试工程师郭芙和架构师行癫,踏上了去北京测试小型机的道路。他们带着小型机回来的时候,我们像欢迎领袖一样欢迎他们,因为那是我们最值钱的设备,价格表上的数字吓死人。小型机买回来之后,我们争相合影,然后Oracle就运行在了小型机上,存储方面,从EMC低端CX存储到Sun oem hds高端存储,再到EMC dmx高端存储,一级一级地往上跳。
到2004年底,淘宝网已经有4百多万种商品了,日均4千多万个PV,注册会员达400万个,全网成交额达10亿元。
到现在为止,我们已经用上了IBM的小型机、Oracle的数据库、EMC的存储,这些东西都是很贵的,那些年可以说是花钱如流水。有人说过“钱能解决的问题,就不是问题”,但随着淘宝网的发展,在不久以后,钱已经解决不了我们的问题了。花钱买豪华的配置,也许能支持1亿个PV的网站,但淘宝网的发展实在是太快了,到了10亿个PV怎么办?到了百亿怎么办?在几年以后,我们不得不创造技术,解决这些只有世界顶尖的网站才会遇到的问题。后来我们在开源软件的基础上进行自主研发,一步一
步地把IOE(IBM小型机、Oracle、EMC存储)这几个“神器” 都去掉了。这些神器就如同《西游记》中那些神仙的兵器,他们身边的妖怪们拿到这些兵器能把猴子打得落荒而逃。但最牛的神仙是不依赖这些神器的,他们挥一挥衣袖、翻一下手掌就威力无比了。
坚若磐石
已经有读者在迫不及待地问怎么去掉了IOE?别急,在去掉IOE之前还有很长的路要走(在后面讲到TDDL的时候,会提到去IOE的一些事情)。行癫等人买回小型机之后,我们用上了
Oracle。然后七公带着一帮DBA做优化SQL和存储方面的工作,行癫带着几个架构师研究数据库的扩展性。Oracle本身是一个封闭的系统,用Oracle怎么做扩展呢?用现在一个时髦的说法就是做“分库分表”。
我们知道,一台Oracle的处理能力是有上限的,它的连接池有数量限制,查询速度与容量成反比。简单地说,在数据量上亿、查询量上亿的时候,就到它的极限了。要突破这种极限,最简单的方式就是多用几个Oracle数据库。但一个封闭的系统做扩展, 不像分布式系统那样直接加机器就可以了。我们的做法是把用户的信息按照ID来存放到两个数据库中(DB1和DB2),把商品的信息和卖家信息放在两个对应的数据库中,把商品类目等通用信息放在第三个库中(DBcommon)。这么做的目的是除了增加了数据库的容量之外,还有一个就是做容灾,即万一一个数据库挂了,整个网站上还有一半的商品可以买。
数据库这么分后,应用程序就会出现麻烦,如果我是卖家, 查看我的商品没有问题,我们都在一个库里。但如果我是一个买家,买的商品有DB1的,也有DB2的,要查看“我已买到的宝贝”的时候,应用程序怎么办?必须到两个数据库中分别查询对应的商品。要按时间排序怎么办?两个库中“我已买到的宝贝” 全部查出来在应用程序中做合并。另外,分页怎么处理?关键字查询怎么处理?专业点的说法就是数据的Join没法做了。这些工作交给程序员来做也许会更麻烦,于是行癫出手了,他写了一个
数据库路由的框架DBRoute,统一处理了数据的合并、排序、分页等操作,让程序员像使用一个数据库一样操作多个数据库里的数据,这个框架在淘宝的Oracle时代一直在使用。但是后来随着业务的发展,这种分库的第二个目的——“容灾”的效果没有达到。像评价、投诉、举报、收藏、我的淘宝等很多地方,都必须同时连接DB1和DB2,哪个库挂了都会导致整个网站挂掉。
上一篇说过,采用EJB其实是和Sun的工程师妥协的结果,在他们离开之后,EJB也逐渐被冷落了下来。在2005年和2006年的时候,Spring大放异彩,于是在控制层用Spring替换掉了EJB,给整个系统精简了很多代码。
上一篇还说过,为了减少数据库的压力,提高搜索的效率, 我们引入了搜索引擎。随着数据量的继续增长,到了2005年,商品数有1663万个,PV有8931万个,注册会员有1390万个,这给数据存储带来的压力依然很大,数据量大,速度就慢。亲,除了搜索引擎、分库分表,还有什么办法能提升系统的性能?一定还有招数可以用,这就是缓存和CDN(内容分发网络)。
你可以想象,9000万次的访问量,有多少是在商品详情页面?访问这个页面的时候,数据全都是只读的(全部从数据库中读出来,不写入数据库),在那个时候,我们的架构师多隆大神做了一个基于 Berkeley DB 的缓存系统,把很多不太变动的只读信息放了进去。其实最初这个缓存系统还比较弱,我们并不敢把所有能缓存的信息都往里面放,一开始先把卖家的信息放里面, 然后把商品属性放里面,再把店铺信息放里面,但是像商品详情这类字段太大的放进去受不了。说到商品详情,这个字段比较恐怖,有人统计过,淘宝商品详情打印出来平均有5米长,在系统里其实放在哪里都不招人待见。笔者清楚地记得,我来淘宝之后担任项目经理做的第一个项目就是把商品详情从商品表中移出来。它最早与商品的价格、运费等信息放在一个表中,拖慢了整张表的查询速度,而很多时候查询商品信息是不需要查看详情的。于是在2005年的时候,我把商品详情放在数据库的另外一张表中, 再往后,这个大字段被从数据库中请了出来,先是放入了缓存系统,到现在是放进了文件系统TFS中。
到现在为止,整个商品详情的页面都在缓存里面了,眼尖的读者可能会发现现在的商品详情不全是“只读”的信息了,这个页面上有个信息叫“浏览量”(这个信息是2006年加上去的), 这个数字每刷新一次,页面就要“写入”存储一次,这种高频度实时更新的数据能用缓存吗?通常来说,这种是必须放进数据库的,但是悲剧的是,我们在2006年开发这个功能的时候,把浏览量写入数据库,发布上线1个小时后,数据库就挂掉了,每天几亿次的写入,数据库承受不了。那怎么办?亲,……先不回答你, 后面讲到缓存Tair的时候再说。(下图不是广告,请把注意力从左边移到右边中间,看看浏览量这个数据在哪里。)
CDN这个工作相对比较独立,跟别的系统一样,一开始我们采用的也是商用系统。后来随着流量的增加,商用的系统已经撑不住了,LVS的创始人章文嵩博士带人搭建了淘宝自己的CDN网络。在本文的引言中,我说过淘宝的CDN系统支撑了800Gbps以
上的流量,作为对比,我们可以看一下国内专业做CDN的上市公司ChinaCache的介绍——“ChinaCache是中国第一的专业CDN服务提供商,向客户提供全方位网络内容快速分布解决方案。作为首家获信产部许可的CDN服务提供商,目前ChinaCache在全国50 多个大中城市拥有近300个节点,全网处理能力超过500Gbps,其
CDN网络覆盖中国电信、中国网通、中国移动、中国联通、中国铁通和中国教育科研网等各大运营商。”——淘宝一家的流量比他们的加起来还要多,这样你可以看出淘宝在CDN上的实力,这在全世界都是数一数二的(其实我们一开始用的商用CDN就是ChinaCache,它们支撑了很长时间)。另外,因为CDN需要大量 的服务器,要消耗很多能源(消耗多少?在前两年我们算过一笔账,淘宝上产生一个交易,消耗的电量足以煮熟4个鸡蛋)。这两年,章文嵩的团队又在研究低功耗的服务器,在绿色计算领域也做了很多开创性的工作,我们定制的基于英特尔凌动处理器的低功耗服务器已经部署到了CDN机房,降低了很大的能耗。这方面的内容可以看后面笔者对章文嵩博士的专访。
回想起刚用缓存那段时间,笔者还是个菜鸟,有一个经典的错误常常犯,就是更新数据库的内容时,忘记通知缓存系统,结果在测试的时候就发现我改过的数据在页面上没有变化。后来做了一些页面上的代码,修改CSS和JS的时候,用户本地缓存的信息没有更新,页面上也会乱掉,在论坛上被人说的时候,我告诉他用Ctrl+F5组合键(清除本地缓存刷新页面),然后赶紧修改脚本
文件的名称,重新发布页面。
我们对数据分库、放弃EJB、引入Spring、加入缓存、加入CDN等工作,看起来没有章法可循,其实都是围绕着提高容量、提高性能、节约成本来做的,由于这些是不算大的版本变迁,我们姑且叫它2.1版,这个版本从构图上看有三只脚,是不是稳定了很多?
在这个稳定的版本下,淘宝网的业务有了突飞猛进的发展, 2005年5月,微软的MSN门户大张旗鼓地进入中国,淘宝网成为它的购物频道。2005年中,盛大进军机顶盒业务,其电视购物的功能也是淘宝网开发的。虽然因为水土不服或者政策的原因,这
两个业务现在都看不到了,但他们曾经是中国互联网行业的大事件。那位和微软谈合作的人是@胖胡斐,他花起钱来也是大手笔的,我们就管他叫“二少爷”,他现在是蘑菇街的COO。
另外,老马也从来都不缺少娱乐精神,他看到湖南卫视的超女如此成功,也想借鉴一下这种模式。就在2005年底,淘宝网和湖南卫视合作推出了一档节目,叫做“超级Buyer秀”。这是一个定位于“超级会网购的人”的一个选秀节目,以百万年薪为诱饵,让大家分享自己的网购经历,网友投票选出最终胜者。这个从海选到表演,历时一年多,广告做得铺天盖地。虽然节目最终没有超女那样火爆,这也让“淘宝网就是网购”的形象通过湖南卫视更加深入人心。
到2006年,淘宝网已经有了1.5亿个的日均PV,商品数达5千多万个,注册用户3千多万个,全网成交额达169亿元。
创造技术
TFS
回顾一下上面几个版本,1.0版的PHP系统运行了将近一年的时间(2003年5月—2004年1月),服务器由一台发展到多台;
后来数据库撑不住了,将MySQL换成了Oracle,引入了搜索引擎
(2004年1月—2004年5月,叫1.1版本);然后不到半年的时间又把开发语言换成了Java(2004年2月—2005年3月,叫2.0版本), 数据服务逐步采用了IOE;随着数据量和访问量的增长,我们进行数据分库、加入缓存、使用CDN(2004年10月—2007年1月, 叫2.1版本)。这几个版本中间有些时间上的重合,因为很多架构的演化并没有明显的时间点,它是逐步进化而来的。
在描述2.1版本的时候,我写的标题是《坚若磐石》,这个“坚若磐石”是因为这个版本终于稳定下来了,在这个版本的系统上,淘宝网运行了两年多的时间。这期间有很多优秀的人才加入,也开发了很多优秀的产品,例如,商品的类目属性、支付宝认证系统、招财进宝项目、淘宝旅行、淘宝彩票、淘宝论坛等, 甚至在团购网站风起云涌之前,淘宝网在2006年就推出了“团购”的功能。
在这些产品和功能的最底层,其实还是商品管理和交易管理这两大功能。这两大功能在2.1版本中都有很大的变化。商品管理起初是要求卖家选择7天到期还是14天到期,到期之后自动下架, 必须重新发布才能上架,上架之后就变成了新的商品信息(ID变过了)。另外,如果商品在这个期间成交了,之后再有新货,必须发布一个新的商品信息。这么做有几个原因,一是参照拍卖商品的时间设置,要在某日期前结束挂牌;二是搜索引擎不知道同
样的商品哪个排在前面,那就把挂牌时间长的排前面(这样就必须在某个时间把老的商品下架,否则它会一直排在前面);第三是成交信息和商品ID关联,这个商品如果多次编辑还是同一个ID 的话,成交记录中的商品信息会不断改变;还有一个不为人知的原因是我们的存储有限,不能让所有的商品老存放在主库中。这种处理方式简单粗暴,但还算是公平。不过这样会导致很多需求都无法满足,例如,卖出一件商品之后就无法更改价格,否则前面已经成交的那个价格都变了,而且同样的商品,上一次销售后的很多好评都无法在下一个商品上体现出来;再如,我买过的商品结束后只看到交易的信息,不知道卖家是否还会卖。基于这些需求,我们在2006年下半年把商品和交易拆开,一个商家的一种商品有一个唯一的ID,上下架都是同一个商品。那么如果卖家修改价格和库存等信息,已成交的信息怎么处理?那就在买家每交易一次的时候,都记录下商品的快照信息,有多少次交易就有多少个快照。这样买卖双方比较爽了,但这给系统带来了什么?存储的成本大幅度上升了!
存储的成本高到什么程度呢?数据库方面用了IOE,一套下来就是千万级别的,那几套下来就是——好多钱啊。另外,淘宝网还有很多文件需要存储,最主要的就是图片、商品描述、交易快照,一个商品要包含几张图片和一长串的描述信息,而每一张图片都要生成几张规格不同的缩略图。在2010年,淘宝网的后端系统上保存着286亿个图片文件。图片在交易系统中非常重要,大
家常说“一张好图胜千言”、“无图无真相”,淘宝网的商品照片,尤其是热门商品图片的访问流量是非常大的。在淘宝网整体流量中,图片的访问流量要占到90%以上,而且这些图片平均大小为17.45KB,小于8KB的图片占整体图片数量的61%,占整体系统容量的11%。这么多的图片数据、这么大的访问流量,给淘宝网的系统带来了巨大的挑战。对于大多数系统来说,最头疼的就是大规模的小文件存储与读取,因为磁头需要频繁寻道和换道, 因此,在读取上容易带来较长的延时。在大量高并发访问量的情况下,简直就是系统的噩梦。我们该怎么办?
同样的套路,在某个规模以下采用现有的商业解决方案,达到某种规模之后,商业的解决方案无法满足,只有自己创造解决方案了。对于淘宝的图片存储来说,转折点在2007年。这之前,一直采用商用存储系统,应用NetApp公司的文件存储系统。随着淘宝网的图片文件数量以每年3倍的速度增长,淘宝网后端NetApp公司的存储系统也从低端到高端不断迁移,直至2006年, 即使是NetApp公司最高端的产品也不能满足淘宝网存储的要求。从2006年开始,我们决定自己开发一套针对海量小文件存储的文件系统,用于解决自身图片存储的难题。这标志着淘宝网从使用技术到了创造技术的阶段。
2007年之前的图片存储架构如下图所示。
在一次架构师大会上,章文嵩博士总结了几点商用存储系统的局限和不足。
第一,商用存储系统没有对小文件存储和读取的环境进行有针对性的优化;第二,文件数量大,网络存储设备无法支撑;第三,整个系统所连接的服务器越来越多,网络连接数已经达到网络存储设备的极限;第四,商用存储系统扩容成本高,10TB的存储容量需要几百万元,而且存在单点故障,容灾和安全性无法得到很好的保证。
谈到在商用系统和自主研发之间的经济效益方面的对比,章文嵩博士列举了以下几点经验。
第一,商用软件很难满足大规模系统的应用需求,无论是存储、CDN还是负载均衡,在厂商实验室端,都很难实现如此大的数据规模测试。
第二,在研发过程中,将开源和自主开发相结合,会有更好的可控性,若系统出了问题,完全可以从底层解决问题,系统扩展性也更高。
第三, 在一定规模效应的基础上,研发的投入都是值得的。下图演示的是一个自主研发和购买商用系统的投入产出比,实际上,图中交叉点的左边,购买商用系统都是更加实际和经济性更好的选择,只有在规模超过交叉点的情况下,自主研发才能收到较好的经济效果。实际上,规模化达到如此程度的公司并不多, 不过淘宝网已经远远超过了交叉点。
第四,自主研发的系统可在软件和硬件的多个层次之间不断
优化。
历史总是惊人的巧合,在我们准备研发文件存储系统的时候,Google走在了前面,2007年,他们公布了GFS(Google File System)的设计论文,这给我们带来了很多借鉴的思路。随后我们开发出了适合淘宝使用的图片存储系统(TaoBao File System, TFS)。3年之后,我们发现历史的巧合比我们想象的还要神奇, 几乎跟我们同时,中国的另外一家互联网公司也开发了他们的文件存储系统,甚至取的名字都一样——TFS,太神奇了!(猜猜是哪家)
2007年6月,TFS正式上线运营。在生产环境中应用的集群规模达到了200台PC Server(146GB×6 SAS 15KB Raid5),文件数量达到上亿级别;系统部署存储容量为140TB;实际使用存储容量为50TB;单台支持随机IOPS 200+,流量为3MB/s。
说到TFS的系统架构,首先要描述清楚业务需求,淘宝对图片存储的需求大概可以描述如下:
文件比较小;并发量高;读操作远大于写操作;访问随机; 没有文件修改的操作;要求存储成本低;能容灾,能备份。显然,应对这种需求时要用分布式存储系统;由于文件大小比较统一,可以采用专有文件系统;由于并发量高,读写随机性强,需要更少的I/O操作;考虑到成本和备份,需要用廉价的存储设备; 考虑到容灾,需要能平滑扩容。
参照GFS并做了大量的优化之后,TFS 1.0版的架构图如下。
从上面的架构图可看出:集群由一对Name Server和多台Data Server构成,Name Server的两台服务器互为双机,这就是集群文件系统中管理节点的概念。
在这个系统中:
y 每个Data Server运行在一台普通的Linux主机上;
y 以Block文件的形式存放数据文件(一个Block的大小一般为64MB);
y Block存储多份是为了保证数据安全;
y 利用ext3文件系统存放数据文件;
y 磁盘raid5做数据冗余;
y 文件名内置元数据信息,用户自己保存TFS文件名与实际文件的对照关系,这使得元数据量特别小。
淘宝TFS文件系统在核心设计上最大的取巧在于,传统的集群系统中元数据只有1份,通常由管理节点来管理,因而很容易成为瓶颈。而对于淘宝网的用户来说,图片文件究竟用什么名字来保存他们并不关心,因此,TFS在设计规划上考虑在图片的保存文件名上暗藏了 一些元数据信息,例如,图片的大小、时间、访问频次等信息,包括所在的逻辑块号。而在实际的元数据上,保存的信息很少,因此,元数据结构非常简单。仅仅只需要一个FileID 就能够准确定位文件在什么地方。由于大量的文件信息都隐藏在文件名中,整个系统完全抛弃了传统的目录树结构,因为目录树开销最大。拿掉后,整个集群的高可扩展性可极大地提高。实际上,这一设计理念和目前业界的“对象存储”较类似。
在TFS上线之前,淘宝网每个商品只允许上传一张图片,大小限定在120KB之内,在商品详情中的图片必须使用外站的服务。那时侯发布一件商品确实非常麻烦,笔者曾经想卖一台二手电脑,我先把照片上传到Google相册,在发布到淘宝网之后发现Google相册被墙(即被屏撇,无法访问),我的图片别人看不
到,当时很郁闷。在TFS上线后,商品展示图片开放到5张,商品描述里面的图片也可以使用淘宝的图片服务,到现在为止,淘宝网给每个用户提供了1GB的图片空间。技术和业务就是这么互相借力推动着的,业务满足不了的时候,技术必须创新,技术创新之后,业务有了更大的发展空间。
TFS发布之后,又经历了多个版本的修改,到1.3版的时候已经比较成熟了。2009年6月,TFS 1.3版本上线,集群规模大大扩展,部署到淘宝的图片生产系统上,整个系统已经从原有200台
PC服务器扩增至440台PC服务器(300B×12 SAS 15KB RPM)+
30台PC服务器(600B×12 SAS 15KB RPM )。支持文件数量也扩容至百亿级别;系统部署存储容量为1800TB;当前实际存储容量为995TB;单台DataServer支持随机IOPS900+,流量为15MB+;
目前NameServer运行的物理内存是217MB(服务器使用千兆网卡)。
TFS 1.3版本逻辑结构图如下图所示。
在TFS 1.3版本中,工程师们重点改善了心跳和同步的性能, 最新版本的心跳和同步在几秒钟之内就可完成切换,同时进行了一些新的优化,包括元数据存储在内存中、清理磁盘空间等。
性能上也做了优化,包括如下内容。
y 采用ext4文件系统,并且预分配文件,减少ext3等文件系统数据碎片带来的性能损耗;
y 单进程管理单块磁盘的方式,摒除RAID5机制;
y 带有HA机制的中央控制节点,在安全稳定和性能复杂度之间取得平衡;
y 缩减元数据大小,将更多的元数据加载入内存,提升访问速度;
y 跨机架和IDC的负载均衡及冗余安全策略;
y 完全平滑扩容。
对于整个图片服务来说,仅有TFS还是不够的,整个图片服务机器的拓扑结构如下图所示。
整个图片存储系统就像一个庞大的服务器,有处理单元、缓存单元和存储单元。前面已经介绍过后台的TFS集群文件存储系
统,在TFS前端,还部署着200多台图片文件服务器,用Apache实现,用于生成缩略图的运算。
值得一提的是,根据淘宝网的缩略图生成规则,缩略图都是实时生成的。这样做的好处有两点:一是为了避免后端图片服务器上存储的图片数量过多,大大节约后台存储空间的需求,我们计算过,采用实时生成缩略图的模式比提前全部生成好缩略图的模式节约90%的存储空间。也就是说,存储空间只需要后一种模式的10%。二是,缩略图可根据需要实时生成,这样更加灵活。
图片文件服务器的前端则是一级缓存和二级缓存,前面还有全局负载均衡的设置,用于解决图片的访问热点问题。图片的访问热点一定存在,重要的是,让图片尽量在缓存中命中。目前淘宝网在各个运营商的中心点设有二级缓存,整体系统中心店设有一级缓存,加上全局负载均衡,传递到后端TFS的流量就已经非常均衡和分散了,对前端的响应性能也大大提高。根据淘宝的缓存策略,大部分图片都尽量在缓存中命中,如果缓存中无法命中,则会在本地服务器上查找是否存有原图,并根据原图生成缩略图,如果都没有命中,则会考虑去后台TFS集群文件存储系统上调取。因此,最终反馈到TFS集群文件存储系统上的流量已经被大大优化了。
淘宝网将图片处理与缓存编写成基于Nginx的模块,我们认为,Nginx是目前性能最高的HTTP服务器(用户空间),代码清
晰,模块化非常好。淘宝网使用GraphicsMagick进行图片处理, 采用了面向小对象的缓存文件系统,前端有LVS+Haproxy将原图和其所有的缩略图请求都调度到同一台Image Server(图片服务器)。
在文件定位上,内存用Hash算法做索引,最多一次读盘。另外会有很多相同的图片重复上传上来,去除重复文件也是采用Hash算法来做的。写盘方式则采用Append方式写,并采用了淘汰策略FIFO,主要考虑降低硬盘的写操作,没有必要进一步提高
Cache命中率,因为ImageServer和TFS位于同一个数据中心,读盘效率还是非常高的。
目前淘宝网的TFS已经开源(见code.taobao.org),业界的同仁可以一起使用和完善这个系统。
Tair
TFS的开发让淘宝的图片功能得到了充分的发挥。同TFS一样,很多技术都是在产品的推动下得到发展的。在介绍下面的技术之前,有必要说说前些年我们做过的几个产品。
先说一个比较悲剧的——“团购”,这个团购可不是现在满大街挂的那种Groupon类型的模式,在Groupon产生之前,在2006 年,淘宝的产品经理一灯就提出了“团购”这种产品。一灯最初的设想是让买家在社区发起团购,“团长”找到足够的人之后,
去跟卖家砍价,这类似于现在蘑菇街的“自由团”。但由于种种原因,在开发的时候,对产品的功能做了裁剪,与最初的设想比起来偏离了一点,变成了让卖家设置团购价,在买家达到指定的数量之后,以团购价成交。这个功能看起来是结合了淘宝“一口价”和“荷兰拍”的另一种交易模式,但最终没有支撑下去,这种交易方式最大的弱点就是让买家看到了卖家的底牌,即便达不到团购的数量,他们也往团购的价格上砍。当时为了提高流量, 淘宝网开辟了团购专区,实诚的卖家在达不到团购数量的时候, 被砍价砍亏了,狡猾的卖家干脆提高原价,利用这个专区做促销。在接下来的两年里,这个产品沦落成了促销工具(话说现在满大街的团购,其实也就是促销)。这个产品让研发人员对“产品”这个概念有了深刻的认识。
再说一个更加悲剧的——“我的淘宝”。“我的淘宝”是给会员管理自己的商品、交易、收货地址、评价、投诉的地方,这个地方必须在登录之后才能看到,所以风格与外观完全不一样, 很长时间都没有优化过,样子丑,用户操作也不方便,如果一个人有很多商品,上下架需要一个一个地操作,非常麻烦(想想那些卖书的)。这时候一个重要人物承志(现在的蘑菇街CEO) 登场了,他给我们演示了最牛的前端交互技术,就是Gmail上那种AJAX的交互方式,可以拖动,可以用鼠标右键,也可以用组合键,操作完毕还不刷新页面,管理商品如有神助,帅呆了。我是这个项目的项目经理,一灯是产品经理,我们再拉上万剑和一
伙工程师就开始行动了。我们热火朝天地干了三个月,快要完成的时候,老马突然出现在我身后,看我操作了一遍新版“我的淘宝”之后,问我这是不是客户端软件,我说是网页,他抓狂了, 说这跟客户端软件一样,链接下面的下画线都没有,上下架用文件夹表示,他都不知道怎么操作,卖家肯定也不会玩。
页面如下图所示:看看这神乎其技的翻页条、精致的文件夹结构、人性化的多选框,还有一个类似Excel的冻结窗口的功能, 这有多么性感啊!
老马果然是神一样的人物,他说的应验了,淘宝历史上第一个群体性事件爆发了,试用完新版本的“我的淘宝”之后,很多卖家愤怒了,说不会玩儿。一灯就和承志一起商量怎么把页面改得像网页一点,改了半个月,愤怒依然没有平息。我很无奈地看着这两个人在那里坚持,然后跟老板们商量怎么办。后来我们到论坛上让大家投票要不要使用新版“我的淘宝”,投票结果是一半以上的人反对。于是这十来个人做了3个月的系统被杀掉了。这让我非常沮丧,但最痛苦的还不是这个,我们下线之后,另外一拨卖家不满了,说这么好的功能怎么没有了?这个产品带给我们的是新技术(AJAX、prototype框架)的尝试,以及新技术对用户操作习惯的改变,一定要慎之又慎。另外,还有一点没有总结好的教训,就是应对群体事件的时候,我们手足无措,在后来“招财进宝”和淘宝商城出现群体性事件的时候,我发现悲剧在重演。
说到“招财进宝”,这个是最悲剧的产品。在2006年“五一”的时候,一个划时代的项目启动了。财神说要用最好的项目阵容,我被选中了,这一下让我觉得我能划分到最好的员工之类,在“我的淘宝”这个产品中严重受伤的心又痊愈了。这是一个商品P4P的系统,就是按成交付费。我们认为已经有很多卖家有钱了,但淘宝上这么多的商品,他们很难被找到,卖家愿意花钱让商品排在前面。我们允许卖家购买广告位,把他的商品按一定算法给出排名(类似于百度的竞价排名,但不仅仅看他出了多少钱,还要看信用、成交量、被收藏数量等,这个算法弄得巨复杂)。这是一个多么牛的盈利模式啊!
这个系统进行得很顺利,但发布的时候,更大的群体性事件出来了,买家们质疑:你们不是承诺三年不收费吗?收广告费不是收费吗?后来我们的竞争对手又推波助澜,公关公司和圈子里各路大侠上蹿下跳,甚至同行推出了“一键搬家”的功能来收纳我们的会员。一时间,舆论哗然,各种矛头都指了过来。为了收场,我们又一次在论坛中让用户投票决定产品是否下线,同“我的淘宝”一样,以悲剧收场。也如同“我的淘宝”一样,下线后,一拨尝到甜头的卖家说,这么好的功能怎么没有了?(直到
Yahoo中国合并之后,开发了淘宝直通车,才以类似的产品形态满足了这部分需求。)
虽然“招财进宝”失败了,但这个项目中对技术的探索更加深入,其中用到了用户行为追踪、AJAX等,而且有一个技术的细
节非常经典,淘宝商品详情页面每天的流量有几个亿,里面的内容都是放在缓存里的,做“招财进宝”的时候,我们要给卖家显示他的商品被浏览的次数(见下图),这个数字必须实时更新, 而用缓存一般都是异步更新的,所以,一开始根本没考虑把这个数据放入缓存里。我们的商品表里增加了这样一个字段,每增加一个PV,该字段就要更新一次。发布一个小时后,数据库就挂掉了。数据库撑不住怎么办?一般的缓存策略是不支持实时更新的,这时候多隆大神想了个办法,在Pache上面写了一个模块,这个数字根本不经过下层的WebApp容器(只经过Apache)就写入一个集中式的缓存区了,这个缓存区的数据再异步更新到数据库。这就是我前面提到的,整个商品详情的页面都在缓存中了,把缓存用到了极致。
接下来,我们就说说缓存的技术吧。
淘宝在很早就开始使用缓存技术了,在2004年的时候,我们使用一个叫做ESI(Edge Side Includes)的缓存(Cache)。在决定采用ESI之前,多隆试用了Java的很多Cache,但都比较重,后来用了Oracle Web Cache,也经常挂掉,Oracle Web Cache也支持
ESI,多隆由此发现了ESI这个好东东。ESI是一种数据缓冲/缓存服务器,它提供将Web网页的部分(这里指页面的片段)进行缓冲/缓存的技术及服务。以往的数据缓冲服务器和信息传送服务以“页”为单位制作,复制到数据缓冲服务器中,这用于处理静态页面很有效,但在面对动态内容的时候,就很难得到高效率。在ESI中是部分的缓冲网页,使用基于XML的标记语言,指定想要缓冲的页面部分。由此,页面内分为动态地变更部分和静态的不变更部分,只将静态的部分有效地发送到服务器中。淘宝网的数据虽然大部分都是动态产生的,但页面中的静态片段也有很多, 例如,页面的头、尾,商品详情页面的卖家信息等(如下图右侧),这些最早都是从ESI缓存中读取的。
ESI解决了页面端静态片段的缓存,聪明的读者可能马上就想到了,在后端的那些数据能不能使用缓存?显然也是可以的,而
且是必需的。例如,一个大卖家的商品和店铺,一天的浏览量可能是几百万个,一个小卖家的可能只有几个,那么这个大卖家的用户信息要是每次都从数据库中读取,显然不划算,要是把这个信息放在内存中,每次都从内存里取,性能要好很多。这种应用场景就是memcached这种Key-Value缓存的用武之地。只可惜,在 淘宝急需要memcached的时候,它还没有崭露头角(它于2003年6 月出现,但近几年才火爆起来,当时没发现它)。我们的架构师多隆大神再一次出手写了一个缓存系统,叫TBstore,这是一个分布式的基于Berkeley DB的缓存系统,推出之后,在阿里巴巴集团内部使用非常广泛,特别是对于淘宝,TBstore上应用了ESI(就是上面说过的那个ESI)、Checkcode(验证码)、Description(前文说过的商品详情)、Story(心情故事,商品信息里面的一个大字段,长度仅次于商品详情)、用户信息等内容。
TBstore的分布式算法实现:根据保存的Key(关键字),对
key进行Hash算法,取得Hash值,再对Hash值与总Cache服务器数据取模。然后根据取模后的值,找到服务器列表中下标为此值的Cache服务器。由Java Client API封装实现,应用无须关心。
TBstore有一个优点,这也是它的弱点,它的存储是基于Berkeley DB的,而Berkeley DB在数据量超过内存的时候,就要往磁盘上写数据了,所以,它是可以做持久化存储的。但是一旦往磁盘写入数据,作为缓存的性能就大幅下降。
这时又有一个项目,推动了淘宝在缓存方面的技术提升。在
2007年,我们把淘宝的用户信息独立出来,形成一个中心系统UIC(User Information Center),因为淘宝所有的功能都要依赖于用户信息,所以这个模块必须单独拿出来,否则以后的系统无法扩展。把UIC拿出来以后,应用系统访问UIC,UIC访问数据库取得用户信息,粗算一下,每天要取几十亿条的用户信息,若直接查询数据库,数据库肯定会崩溃,这里必须要用缓存。于是多隆专门为UIC写了一个缓存系统,取名叫做TDBM。TDBM抛弃了Berkeley DB的持久功能,数据全部存放在内存中。到2009 年,多隆又参考了memcached的内存结构,改进了TDBM的集群分布方式,在内存利用率和吞吐量方面又做了大幅提升,推出了TDBM 2.0系统。
下图是一个关键应用的实时监控信息,第一行是UIC的缓存命中率,可以看到有99.2%之高。换句话说,也就是给数据库减少了99.2%的压力。
由于TDBM、TBstore的数据接口和用途都很相似,开发团队把二者合并,推出了淘宝自创的Key-Value缓存系统——Tair
(TaoBao Pair的意思,Pair即Key-Value数据对)。Tair包括缓存和持久化两种存储功能。Tair作为一个分布式系统,由一个中心控制节点和一系列的服务节点组成,我们称中心控制节点为Config Server,服务节点是Data Server。Config Server 负责管理所有的Data Server,维护Data Server的状态信息。Data Server 对外提供各种数据服务,并以心跳的形式将自身的状况汇报给Config Server。Config Server是控制点,而且是单点,目前采用一主一备的形式来保证其可靠性。所有的Data Server 地位都是等价的。Tair 的架构图如下图所示。
系统部署结构如下图所示。
目前,Tair支撑了淘宝几乎所有系统的缓存信息。Tair已开源,地址为code.taobao.org。
在创造了TFS和Tair之后,整个系统的架构如下图所示。
在这个时候,研发部对搜索引擎iSearch也进行了一次升级, 之前的搜索引擎是把数据分到多台机器上,但是每份数据只有一份,现在是每份数据变成多份,整个系统从一个单行的部署变成了矩阵,能够支撑更大的访问量,并且做到很高的可用性。
到2007年,淘宝网日均PV达到2.5亿个,商品数超过1亿个, 注册会员数达5千多万个,全网成交额达433亿元。
分布式时代
服务化
在系统发展的过程中,架构师的眼光至关重要,作为程序员,只要把功能实现即可,但作为架构师,要考虑系统的扩展性、重用性,对于这种敏锐的感觉,有人说是一种“代码洁癖”。淘宝早期有几个架构师具备了这种感觉,周悦虹开发的Webx是一个扩展性很强的框架,行癫在这个框架上插入了数据分库路由的模块、Session框架等。在做淘宝后台系统的时候,同样需要这几个模块,行癫指导我把这些模块单独打成JAR包。另外,在做淘宝机票、彩票系统的时候,页面端也有很多东西需要复用,最直观的是页眉和页脚,一开始,我们的每个系统中都复制了一份,但奇妙的是,那段时间页脚要经常修改,例如,把“雅虎中国”改成“中国雅虎”,过一段时间又加了一个“口碑网”,再过一段时间变成了“雅虎口碑”,最后又变成了“中国雅虎”。后来我就把这部分Velocity模板独立出来做成了公用的模块。
上面说的都是比较小的复用模块,到2006年,我们做了一个
商品类目属性的改造,在类目中引入了属性的概念。项目的代号叫做“泰山”,如同它的名字一样,这是一个举足轻重的项目,这个改变是一个划时代的创新。在这之前的三年时间内,商品的分类都是按照树状一级一级的节点来分的,随着商品数量的增长,类目也变得越来越深,且越来越复杂,这样,买家如果要查找一件商品,就要逐级打开类目,找商品之前要弄清商品的分类。而淘宝运营部门管理类目的小二也发现了一个很严重的问题,例如,男装里有T恤、T恤下面有耐克、耐克有纯棉的,女装里也有T恤、T恤下面还是有耐克、耐克下面依然有纯棉的,那是先分男女装,再分款式、品牌和材质呢,还是先分品牌,再分款式、材质和男女装呢?弄得很乱。这时候,一位大侠出来了—— 一灯,他说品牌、款式、材质等都可以叫做“属性”,属性是类似Tag(标签)的一个概念,与类目相比更加离散、灵活,这样也缩减了类目的深度。这个思想的提出一举解决了分类的难题! 从系统的角度来看,我们建立了“属性”这样一个数据结构,由于除了类目的子节点有属性外,父节点也可能有属性,于是类目属性合起来也是一个结构化的数据对象。这个做出来之后,我们把它独立出来作为一个服务,叫做Catserver(Category Server)。跟类目属性密切关联的商品搜索功能独立出来,叫做Hesper(金星)。Catserver和Hesper供淘宝的前后台系统调用。
现在淘宝的商品类目属性已经是全球最大的,几乎没有什么类目的商品在淘宝上找不到(除了违禁的),但最初的类目属性
改造完之后,我们很缺乏属性数据,尤其是数码类。从哪里弄这些数据呢?我们跟“中关村在线”合作,拿到了很多数据,那个时候,很多商品属性信息的后边标注着:“来自中关村在线”。有了类目属性,给运营工作带来了很大的便利,我们知道淘宝的运营主要就是类目的运营,什么季节推什么商品,都要在类目属性上做调整,让买家更容易找到。例如,夏天让用户在女装一级类目下标出材质是不是蕾丝的、是不是纯棉的,冬天却要把羽绒衣调到女装一级类目下,什么流行,就要把什么商品往更高级的类目调整。这样类目和属性要经常调整,随之而来的问题就出现了——调整到哪个类目,所属商品的卖家就要编辑一次自己的商品,随着商品量的增长,卖家的工作量越来越大,他们肯定受不了。到了2008年,我们研究了超市里前后台商品的分类,发现超市前台商品可以随季节和关联来调整摆放场景(例如著名的啤酒和尿布的关联),后台仓库里要按照自然类目来存储,二者密切关联,却又相互分开这样卖家发布商品选择的是自然类目和属性,淘宝前台展示的是根据运营需要摆放商品的类目和属性。改造后的类目属性服务取名为Forest(森林,与类目属性有点神似。Catserver还用于提供卖家授权、品牌服务、关键词等相关的服务)。类目属性的服务化是淘宝在系统服务化方面做的第一个探索。
虽然个别架构师具备了“代码洁癖”,但淘宝前台系统的业
务量和代码量还是呈爆炸式的增长。业务方总在后面催,开发人员不够就继续招人,招来的人根本看不懂原来的业务,只好摸索着在“合适的地方”加一些“合适的代码”,看看运行起来像那么回事后,就发布上线。在这样的恶性循环中,系统越来越臃肿,业务的耦合性越来越高,开发的效率越来越低。借用当时比较流行的一句话“你写一段代码,编译一下能通过,半个小时就过去了;编译一下没通过,半天就过去了。”在这种情况下,系统出错的概率也逐步增长,常常是你改了商品相关的某些代码, 发现交易出问题了,甚至你改了论坛上的某些代码,旺旺出问题了。这让开发人员苦不堪言,而业务方还认为开发人员办事不力。
大概是在2007年年底的时候,研发部空降了一位从硅谷来的高管——空闻大师。空闻是一位温厚的长者,他告诉我们一切要以稳定为中心,所有影响系统稳定的因素都要解决掉。例如,每做一个日常修改,都必须对整个系统回归测试一遍;多个日常修改如果放在一个版本中,要是一个功能没有测试通过,整个系统都不能发布。我们把这个叫做“火车模型”,即任何一个乘客没有上车,都不许发车。这样做最直接的后果就是火车一直晚点, 新功能上线更慢了,我们能明显感觉到业务方的不满,空闻的压力肯定非常大。
现在回过头来看看,其实我们并没有理解背后的思路。正是
在这种要求下,我们不得不开始改变一些东西,例如,把回归测试日常化,每天晚上都跑一遍整个系统的回归。另外,在这种要求下,我们不得不对这个超级复杂的系统做肢解和重构,其中复用性最高的一个模块——用户信息模块开始拆分出来,我们叫它UIC(User Information Center)。在UIC中,它只处理最基础的用户信息操作,例如,getUserById、getUserByName等。
在另一方面,还有两个新兴的业务对系统基础功能的拆分也提出了要求。在那个时候,我们做了淘宝旅行(com">trip.taobao.com) 和淘宝彩票(caipiao.taobao.com)两个新业务,这两个新业务在商品的展示和交易的流程上都跟主站的业务不一样,机票是按照航班的信息展示的,彩票是按照双色球、数字和足球的赛程来展示的。但用到的会员功能和交易功能是与主站差不多的,当时做起来就很纠结,因为如果在主站中做,会有一大半跟主站无关的东西,如果重新做一个,会有很多重复建设。最终我们决定不再给主站添乱了,就另起炉灶做了两个新的业务系统,从查询商品、购买商品、评价反馈、查看订单这一整个流程都重新写了一套。现在在“我的淘宝”中查看交易记录的时候,还能发现“已买到的宝贝”中把机票和彩票另外列出来了,他们没有加入到普通的订单中。在当时,如果已经把会员、交易、商品、评价这些模块拆分出来,就不用什么都重做一遍了。
到2008年初,整个主站系统(有了机票、彩票系统之后,把原来的系统叫做主站)的容量已经到了瓶颈,商品数在1亿个以上,PV在2.5亿个以上,会员数超过了5000万个。这时Oracle的连接池数量都不够用了,数据库的容量到了极限,即使上层系统加机器也无法继续扩容,我们只有把底层的基础服务继续拆分,从底层开始扩容,上层才能扩展,这才能容纳以后三五年的增长。
于是我们专门启动了一个更大的项目,即把交易这个核心业务模块拆分出来。原来的淘宝交易除了跟商品管理耦合在一起, 还在支付宝和淘宝之间转换,跟支付宝耦合在一起,这会导致系统很复杂,用户体验也很不好。我们把交易的底层业务拆分出
来,叫交易中心(Trade Center,TC),所谓底层业务,就如创建订单、减库存、修改订单状态等原子型的操作;交易的上层业务叫交易管理(Trade Manager,TM),例如,拍下一件普通商品要对订单、库存、物流进行操作,拍下虚拟商品不需要对物流进行操作,这些在TM中完成。这个项目取了一个很没有创意的名字——“千岛湖”,开发人员取这个名字的目的是想在开发完毕之后,去千岛湖玩一圈,后来他们如愿以偿了。这个时候还有一个淘宝商城的项目在做,之前拆分出来的那些基础服务给商城的快速构建提供了良好的基础。
类目属性、用户中心、交易中心,随着这些模块的逐步拆分和服务化改造,我们在系统架构方面也积累了不少经验。到2008 年年底就做了一个更大的项目,把淘宝所有的业务都模块化,这是继2004年从LAMP架构到Java架构之后的第二次脱胎换骨。我们对这个项目取了一个很霸气的名字——“五彩石”(女娲炼石补天用的石头)。这个系统重构的工作非常惊险,有人称为“给一架高速飞行的飞机换发动机”。“五彩石”项目发布之后,相关工程师去海南三亚玩了几天。
他们把淘宝的系统拆分成了如下架构。
其中,UIC和Forest在上文已说过,TC、IC、SC分别是交易中心(Trade Center)、商品中心(Item Center)、店铺中心
(Shop Center),这些中心级别的服务只提供原子级的业务逻辑,如根据ID查找商品、创建交易、减少库存等操作。再往上一层是业务系统TM(Trade Manager,交易业务)、IM(Item Manager,商品业务)、SM(Shop Manager,后来改名叫SS,即Shop System,店铺业务)、Detail(商品详情)。
拆分之后,系统之间的交互关系变得非常复杂,示意图如下所示。
系统这么拆分的好处显而易见,拆分之后的每个系统可以单独部署,业务简单,方便扩容;有大量可重用的模块便于开发新的业务;能够做到专人专事,让技术人员更加专注于某一个领域。这样要解决的问题也很明显,分拆之后,系统之间还是必须要打交道的,越往底层的系统,调用它的客户越多,这就要求底层的系统必须具有超大规模的容量和非常高的可用性。另外,拆分之后的系统如何通信?这里需要两种中间件系统,一种是实时调用的中间件(淘宝的HSF,高性能服务框架),一种是异步消息通知的中间件(淘宝的Notify)。另外,一个需要解决的问题是用户在A系统登录后,到B系统的时候,用户的登录信息怎么保存?这又涉及一个Session框架。再者,还有一个软件工程方面的问题,这么多层的一套系统,怎么去测试它?
中间件
HSF
互联网系统的发展看似非常专业,其实在生活中也存在类似的“系统”,正如一位哲学家说“太阳底下无新事”。我们可以从生活中的一个小例子来看网站系统的发展,这个例子是HSF的作者毕玄写的。
一家小超市,一个收银员,同时还兼着干点其他的事情,例如,打扫卫生、摆货。
来买东西的人多起来了,排队很长,顾客受不了,于是增加了一个收银台,雇了一个收银员。
忙的时候收银员根本没时间去打扫卫生,超市内有点脏,于是雇了一个专门打扫卫生的。
随着顾客不断增加,超市也经过好几次装修,由以前的一层变成了两层,这个时候所做的事情就是不断增加收银台、收银员和打扫卫生的人。
在超市运转的过程中,老板发现一个现象,有些收银台排很长的队,有些收银台排的人不多,了解后知道是因为收银台太多了,顾客根本看不到现在各个收银台的状况。对于这个现象,一种简单的方法就是继续加收银台。但一方面,超市没地方可加收
银台了,另一方面,作为老板,当然不需要雇太多的人,于是开始研究怎样让顾客了解到收银台的状况,简单地加了一个摄像头和一个大屏幕,在大屏幕上显示目前收银台的状况,这样基本解决了这个问题。
排队长度差不多后,又出现了一个现象,就是有些收银台速度明显比其他的慢,原因是排在这些收银台的顾客买的东西特别多,于是又想了一招,就是设立专门的10件以下的通道,这样买东西比较少的顾客就不用排太长的队了,这一招施展后,顾客的满意度明显提升,销售额也好了不少,后来就继续用这招应对团购状况、VIP 状况。
在解决了上面的一些烦心事后,老板关注到了一个存在已久的现象,就是白天收银台很闲,晚上则很忙,于是从节省成本上考虑,决定实行部分员工只在晚上上班的机制,白天则关闭一些收银台,顾客仍然可以通过大屏幕看到哪些收银台是关闭的,避免走到没人的收银台去,实行这招后,成本大大降低了。
这个生活中的例子及其解决的方法,其实和互联网网站发展过程中的一些技术是非常类似的,只是在技术层面用其他名词来表达了而已,例如,有集群、分工、负载均衡、根据QoS分配资源等。
y 集群:所有的收银员提供的都是收银功能,无论顾客到哪一个收银员面前,都可完成付款,可以认为所有的收银员就构成了一个集群,都希望能做到顾客增加的时候只需增加收银员就行。在现实生活中有场地的限制,而在互联网应用中,能否集群化还受限于应用在水平伸缩上的支撑程度,而集群的规模通常会受限于调度、数据库、机房等。
y 分工:收银员和打扫卫生的人分开,这种分工容易解决, 而这种分工在互联网中是一项重要而复杂的技术,没有现实生活中这么简单,涉及的主要有按功能和数据库的不同拆分系统等,如何拆分以及拆分后如何交互是需要面临的两个挑战。因此,会有高性能通信框架、SOA平台、消息中间件、分布式数据层等基础产品的诞生。
y 负载均衡:让每个收银台排队差不多长,设立小件通道、团购通道、VIP通道等,这些可以认为都是集群带来的负载均衡的问题,从技术层面上说,实现起来自然比生活中复杂很多。
y 根据QoS分配资源:部分员工仅在晚上上班的机制要在现实生活中做到不难,而对互联网应用而言,就是一件复杂而且极具挑战的事。
参照生活中的例子来说,在面对用户增长的情况下,想出这
些招应该不难,不过要掌握以上四点涉及的技术就相当复杂了, 而且互联网中涉及的其他很多技术还没在这个例子中展现出来, 例如缓存、CDN等优化手段;运转状况监测、功能降级、资源劣化、流控等可用性手段,自建机房、硬件组装等成本控制手段。因此,构建一个互联网网站确实是不容易的,技术含量十足,当然,经营一家超市也不简单。
从超市的运维可以抽象出系统设计的一些思路,服务拆分之后,如何取得我需要的服务?在“电视机”上,把每个集群能提供的服务显示出来。你不需要关心哪个人为你服务,当你有需要的时候,请先看头顶的电视机,它告诉你哪个服务在哪个区域。当你直接去这个区域的时候,系统会给你找到一个最快速的服务通道。
这就是HSF的设计思想,服务的提供者启动时通过HSF框架向ConfigServer(类似超市的电视机)注册服务信息(接口、版本、超时时间、序列化方式等),这样ConfigServer上面就定义了所有可供调用的服务(同一个服务也可能有不同的版本);服
务调用者启动的时候向ConfigServer注册对哪些服务感兴趣(接口、版本),当服务提供者的信息变化时,ConfigServer向相应的感兴趣的服务调用者推送新的服务信息列表;调用者在调用时则根据服务信息的列表直接访问相应的服务提供者,而无须经过
ConfigServer。我们注意到ConfigServer并不会把服务提供者的IP地址推送给服务的调用者,HSF框架会根据负载状况来选择具体的服务器,返回结果给调用者,这不仅统一了服务调用的方式,也实现了“软负载均衡”。平时ConfigServer通过和服务提供者的心跳来感应服务提供者的存活状态。
在HSF的支持下,服务集群对调用者来说是“统一”的,服务之间是“隔离”的,这保证了服务的扩展性和应用的统一性。再加上HSF本身能提供的“软负载均衡”,服务层对应用层来说就是一片“私有云”了。
HSF框架以SAR包的方式部署到Jboss、Jetty或Tomcat下,在应用启动的时候,HSF(High-Speed Service Framework,在开发团队内部有一些人称HSF为“好舒服”)服务随之启动。HSF旨在为淘宝的应用提供一个分布式的服务框架,HSF从分布式应用层面以及统一的发布/调用方式层面为大家提供支持,从而可以很容易地开发分布式的应用以及提供或使用公用功能模块,而不用考虑分布式领域中的各种细节技术,例如,远程通讯、性能损耗、调用的透明化、同步/异步调用方式的实现等问题。
从上图HSF的标志来看,它的速度是很快的。HSF是一个分布式的标准Service方式的RPC(Remote Procedure Call Protocol, 远程过程调用协议)框架,Service的定义基于OSGI的方式,通讯层采用TCP/IP协议。关于分布式的服务框架的理论基础,HSF 的作者毕玄写了一篇博文(http://www.blogjava.net/BlueDavy/">http://www.blogjava.net/BlueDavy/ archive/2008/01/24/177533.html),有关基于OSGI的分布式服务框架,也有一系列的博文(http://www.blogjava.net/BlueDavy/ archive/2008/01/14/175054.html)。
从下面这个HSF监控系统的截图中可以更直观地看到一些信
息,在两个集群中有两个服务器(其实有更多的,没有全部截图下来)都提供com.taobao.item.service.SpuGroupService 这一服务,版本号都是1.0.0,这个服务在ConfigServer上的注册信息中包含超时时间、序列化方式。在后面那条信息中可看到,在展开的这个集群中服务有835台机器已订阅,这些订阅者有淘宝的服务器(cart是购物车功能的服务器),也有hitao(淘花网)的服务器。
HSF系统目前每天承担了300亿次以上的服务调用。
一些读者可能会有一个疑问:既然淘宝的服务化是渐进式的,那么在HSF出现之前,系统之间的调用采用什么方式呢?
这个有点“五花八门”,例如,对于类目的调用方式是: Forest打包成一个JAR包,在应用启动的时候装载到内存中,仅这一个JAR包所占用的内存就有800MB之多(因为淘宝的类目数据太庞大了),对于当时一般只有2GB内存的开发机来说,加载完类目信息后,机器运行速度就非常慢。对于用户信息(UIC)
来说,一开始的调用方式是用Hessian接口。还有一些系统是通过WebService、Socket甚至是HTTP请求来相互调用的。每种调用方 式都涉及各种超时、信息的加解/密、参数的定义等问题,由此可见,在没有HSF之前,系统之间的调用是错综复杂的。而随着系统拆分得越来越多,必须由一个统一的中间层来处理这种问题, HSF正是在这种背景下诞生的。
Notify
HSF解决了服务调用的问题,我们再提出一个很早就说过的问题:用户在银行的网关付钱后,银行需要通知到支付宝,但银行的系统不一定能发出通知;如果通知发出了,不一定能通知到;如果通知到了,不一定不重复通知一遍。这个状况在支付宝持续了很长时间,非常痛苦。支付宝从淘宝剥离出来的时候,淘宝和支付宝之间的通信也面临同样的问题,那是2005年的事情, 支付宝的架构师鲁肃提出用MQ(Message Queue)的方式来解决这个问题,我负责淘宝这边读取消息的模块。但我们发现消息数量上来之后,常常造成拥堵,消息的顺序也会出错,在系统挂掉的时候,消息也会丢掉,这样非常不保险。然后鲁肃提出做一个系统框架上的解决方案,把要发出的通知存放到数据库中,如果实时发送失败,再用一个时间程序来周期性地发送这些通知,系统记录下消息的中间状态和时间戳,这样保证消息一定能发出, 也一定能通知到,且通知带有时间顺序,这些通知甚至可以实现事务性的操作。
在“千岛湖”项目和“五彩石”项目之后,淘宝自家的系统也拆成了很多个,他们之间也需要类似的通知。例如,拍下一件商品,在交易管理系统中完成时,它需要通知商品管理系统减少库存,通知旺旺服务系统发送旺旺提醒,通知物流系统上门取货,通知SNS系统分享订单,通知公安局的系统这是骗子……用户的一次请求,在底层系统可能产生10次的消息通知。这一大堆的通知信息是异步调用的(如果同步,系统耦合在一起就达不到拆分的目的),这些消息通知需要一个强大的系统提供支持,从消息的数量级上看,比支付宝和淘宝之间的消息量又上了一个层次,于是按照类似的思路,一个更加强大的消息中间件系统就诞生了,它的名字叫做Notify。Notify是一个分布式的消息中间件系统,支持消息的订阅、发送和消费,其架构图如下所示。
NotifyServer在ConfigServer上面注册消息服务,消息的客户端通过ConfigServer订阅消息服务。某个客户端调用NotifyServer发送一条消息,NotifyServer负责把消息发送到所有订阅这个消息的客户端(这个过程参照HSF一节,原理是一样的)。为了保证消息一定能发出,且对方也一定能收到,消息数据本身就需要记录下来,这些信息存放在数据库中(可以是各种数据库)。由于消息具有中间状态(已发送、未发送等),应用系统通过Notify可以实现分布式事物——BASE(基本可用(Basically Available)、软状态 (Soft State)、最终一致(Eventually Consistent))。NotifyServer可以水平扩展,NotifyClient也可以水平扩展,数据库也可以水平扩展,从理论上讲,这个消息系统的吞吐量是没有上限的,现在Notify系统每天承载了淘宝10亿次以上的消息通知。
下图展示了创建一笔交易之后,TC(交易中心)向Notify发送一条消息,后续Notify所完成的一系列消息通知。
TDDL
有了HSF和Notify的支持,在应用级别中,整个淘宝网的系统可以拆分了,还有一个制约系统规模的更重要的因素,就是数据库,也必须拆分。
在第二部分中讲过,淘宝很早就对数据进行过分库的处理, 上层系统连接多个数据库,中间有一个叫做DBRoute的路由来对数据进行统一访问。DBRoute对数据进行多库的操作、数据的整合,让上层系统像操作一个数据库一样操作多个库。但是随着数据量的增长,对于库表的分法有了更高的要求,例如,你的商品数据到了百亿级别的时候,任何一个库都无法存放了,于是分成
2个、4个、8个、16个、32个……直到1024个、2048个。好,分成这么多,数据能够存放了,那怎么查询它?这时候,数据查询的中间件就要能够承担这个重任了,它对上层来说,必须像查询一
个数据库一样来查询数据,还要像查询一个数据库一样快(每条查询在几毫秒内完成),TDDL就承担了这样一个工作。
另外,加上数据的备份、复制、主备切换等功能,这一套系统都在TDDL中完成。在外面有些系统也用DAL(数据访问层) 这个概念来命名这个中间件。
TDDL实现了下面三个主要的特性:
y 数据访问路由——将针对数据的读写请求发送到最合适的地方;
y 数据的多向非对称复制——一次写入,多点读取;
y 数据存储的自由扩展——不再受限于单台机器的容量瓶颈与速度瓶颈,平滑迁移。
下图展示了TDDL所处的位置。
下图展示了一个简单的分库分表数据查询策略。
下面是TDDL的主要开发者之一沈询讲述的“TDDL的前世今
生”——数据层的发展历程。
CommonDAO的时代
数据切分并不算是一个很新的概念,当商品库切分为两个时,就已经出现了名字叫做xingdian(笑,那时候行癫已经不写代码了,但从代码的版本信息可以看到作者)的人写的Common DAO。
CommonDAO的思路非常简单实用,因为淘宝主要在使用ibatis作为访问数据库的DAO层,所以,CommonDAO的作用就是 对ibatis层做了一个很浅的封装,允许你通过商品字串ID的第一个
字符来访问两台数据库中的一台。
比如,如果字符串ID的第一个字符是0~7,那么走到数据库1 去,如果是8~f,则走到数据库2去。同时,也允许用户直接给定数据库的名字来访问数据库。
这应该是最早的数据层原型。
TDDL 1.0时代
后来,大家逐渐发现,如果按照业务的发展规模和速度,那么使用高端存储和小型机的Oracle存储的成本将难以控制,于是降低成本就成了必然。
如何能够在不影响业务正常发展的前提下,从一定程度上解决成本的问题呢?
“对一部分数据库使用MySQL”,DBA们的决策是这样,于是,分布式数据层的重担就落到了华黎的头上。
别看现在数据水平切分似乎已经成了基础知识。在2007年、2008年,如何设计它还真是让我们伤透了脑筋。
当时的我们,只知道eBay有一个数据层,却不知道如何设计和实现?
于是邀请了当时所有的业务负责人来畅想数据层的样子……
得到了以下需求:
y 对外统一一切数据访问;
y 支持缓存、文件存储系统;
y 能够在Oracle和MySQL之间自由切换;
y 支持搜索引擎。
然后,我们自己的问题与现在大家所问的问题也是完全一样的。
如何实现分布式Join(连接)?——在跨节点以后,简单的
Join会变成M×N台机器的合并,这个代价比原来的基于数据库的 单机Join大太多了。
如何实现高速多维度查询?——就像SNS中的消息系统,A发给B一个消息,那么A要看到的是我发给所有人的消息,而B要看到的是所有人发给我的消息。这种多维度查询,如何能够做到高效快捷呢?
如何实现分布式事务?——原始单机数据库中存在着大量的事务操作,在分布式以后,分布式事务的代价远远大于单机事务,那么这个矛盾也变得非常明显。
华黎带着我和念冰,坐在那里讨论了一个半月,还是没想出
来……于是决定先动起手来。名字是我起的——Taobao Distributed Data layer(TDDL,后来有人对它取了个外号 :“头都大了”
⊙﹏⊙b)
学习开源的Amoeba Proxy。
找到的目标应用是“收藏夹”,首先要做的两个关键的特性是:分库分表和异构数据库的数据复制。
开始本来希望和B2B的团队合作,因为我们觉得独立的Proxy 没有太大必要。而SQL解析器因为有淘宝特殊的需求,所以也需要重写。
可惜,最后因为B2B的人搬到滨江去了,交流十分不畅,所以最后只是做了拿来主义,没有对开源的Amoeba和当时的Cobar 有所贡献。
回到淘宝,因为有东西可以借鉴,我们在一个多月的时间内就完成了TDDL 1.0版本的工作。上线过程中虽然出了点小问题, 不过总体来说是比较成功的。
TDDL 2.0时代
随着使用TDDL的业务越来越多,对业务方来说,DBA 对于使用MySQL以及数据切分也积累了比较多的经验,于是决定开始动核心应用了。
“评价”是第一个重要的应用,评价最重要的问题还是在于双向查询、评价、被评价。于是我们的异构数据源增量复制就派上了用场。
然后是“商品”,我们在商品上投入了近半年的时间,失败很多,也成长得最快。
y 容量规划做得不到位,机器到位后因压力过大,直接死掉,于是产生了数据库容量线上压力模拟测试。
y 历史遗留问题,商品几乎是所有的业务都会使用的资源, 所以接口设计比较复杂。很多接口的调用在新架构上很难以低成本的方式实现。而推动业务改动,则需要大量的时间和成本。
y 数据层代码被业务代码侵染,看起来似乎应该是数据层的代码,但实际上又只有商品在使用。这种问题让数据层的依赖变得更加庞大,边缘代码变得更多,冲突更明显。
TDDL 3.0~TDDL 4.0 时 代
在商品之后,似乎所有的应用都可以使用类似的方式来解决业务增长上量的问题。但正当我们志得意满的时候,却被“交易”撞了一个满怀。
我一直很感谢交易线的所有同仁,他们是淘宝草根精神的典
型代表——功能可以做得不那么“漂亮”,但必须减少中间环节,真正做到了实用、干净、简洁。我们在向他们介绍产品的时候,他们对我们的实现细节提出了非常多的质疑,他们认为整个流程中只有规则、主备切换对他们是有意义的,而解析、合并则是他们所不需要的功能。
“不需要的功能为什么要放到流程里?增加的复杂度会导致更多的问题”。在当时,我感到很痛苦,因为我无法回答他们这些质疑之声。
不过,也正是因为这些质疑,让我有了一个契机,重新审视自己所创造出来的产品。
我问自己:它能够给业务带来什么益处? 对此,我的回答是:
y 规则引擎/切分规则可以用配置的方式帮助业务隔离具体的数据库地址与用户的业务逻辑;
y 单机主备切换;
y 数据源简化和管理。
于是,我们就产生了TDDL 3.0版本。其主要的作用就是将代码做了逻辑切分,将单机主备切换和数据源管理独立了出来。这
样,可以针对不同的业务需求,给予不同的逻辑分层。让每一个业务都有适合自己的使用数据库的方式。
同时,我们开始做工具,Rtools/JADE 作为数据库运维平台的组件被提了出来。在它的帮助下,我们发现能够极大地提升用户在使用单机数据源和多机数据源时的效率。用户只需要在客户端给定两个属性,就可以立刻开始使用。结果是用户反馈比以前好了很多。
这也坚定了我们开发工具的决心。
工具平台时代
在尝到工具平台的甜头以后,我们在工具组件上走得更远了。首先被提出的是“愚公”数据迁移平台。该平台能够在多种
异构的数据库中进行数据的平滑移动,对业务影响很小,并且也允许业务插入自己的业务逻辑。
这个东西主要能够帮助业务进行数据库自动扩容,自动缩容,单机、多机数据迁移,在Oracle到MySQL数据迁移等场景中都发挥了重要的作用。
然后,又以内部开源的方式提出了“精卫”数据增量复制平台。这个平台基于数据库的通用数据分发组件,基于开源的
Tungsten进行了大量Bug Fix和结构调优。在数据的一对多分发以及异步通知给DW和搜索等场景中都发挥了重要的作用。
TDDL的现在
粗略统计下来,TDDL已经走过了4年的时间,满足了近700个业务应用的使用需求。其中有交易商品评价用户等核心数据,也有不那么有名的中小型应用。量变产生质变,如何能够更好地帮助这些业务以更低的成本更快地完成业务需求,将成为数据层未来最重要的挑战。
Session框架
介绍Session框架之前,有必要先了解一下Session。Session在网络应用中称为“会话”,借助它可提供客户端与服务系统之间必要的交互。因为HTTP协议本身是无状态的,所以经常需要通过Session来解决服务端和浏览器的保持状态的解决方案。用户向服务器发送第一个请求时,服务器为其建立一个Session,并为此Session创建一个标识,用户随后的所有请求都应包括这个标识号。服务器会校对这个标识号以判断请求属于哪个Session。会话保持有效,默认状况下,直到浏览器关闭,会话才结束。
Session中存储的内容包括用户信息:昵称、用户ID、登录状态等。
当网站服务器只有一台的时候,用Session来解决用户识别是很简单的,但是当网站是一个集群的时候,同一用户的两次请求可能被分配到两台不同的服务器上处理。怎样保证两次请求中存取的Session值一致呢?还有一个问题:网站规模扩大时,对于一个具有上亿个访问用户的系统来说,当大部分用户的Session信息都存储在服务端时,要在服务端检索出用户的信息效率就非常低了,Session管理器不管用什么数据结构和算法都要耗费大量内存和CPU时间。如何解决服务端Session信息的管理?
解决集群Session共享的问题,通常有以下两种办法。
y 硬件负载,将用户请求分发到特定的服务器。
y Session复制,就是将用户的Session复制到集群内所有的服务器。
这两种方法的弊端也很明显:
y 成本较高。
y 性能差。当访问量增大的时候,带宽增大,而且随着机器数量的增加,网络负担成指数级上升,不具备高度可扩展性,性能随着服务器数量的增加急剧下降,而且容易引起广播风暴。
这种情况下,Tbsession框架闪亮登场了。Tbsession框架致力于解决以下几个问题。
y Session的客户端存储,将Session信息存储到客户端浏览器Cookie中。
y 实现服务端存储,减少Cookie使用,增强用户信息的安全性,避免浏览器对Cookie数量和大小的限制。
y Session配置统一管理起来,集中管理服务端Session和客户端Cookie的使用情况,对Cookie的使用做有效的监管。
y 支持动态更新,Session的配置动态更新。
简单地说,就是要么用客户端Cookie来解决问题,要不用服务端的集中缓存区(Tair)的Session来解决登录问题。Tair已在前文介绍过,Session对它的使用就不再描述了。
为什么这里还要提到用Cookie这种比较“落伍”的方式呢? 其实是因为在淘宝3.0版本以前,我们一直都用Cookie来识别用户,Cookie是放在客户端的,每一次HTTP请求都要提交到服务端,在访问量比较小的时候,采用Cookie避免了Session复制、硬件负载等高成本的情况。但随着用户访问规模的提高,我们可以折算一下,一个Cookie大概是2KB的数据,也就是说,一次请求要提交到服务器的数据是网页请求数据,再加上2KB的Cookie, 当有上亿个请求的时候,Cookie所带来的流量已经非常可观了, 而且网络流量的成本也越来越高。于是在3.0版本中,我们采用了集中式的缓存区的Session方式。
到此为止,应用服务切分了(TM、IM)、核心服务切分了
(TC、IC)、基础服务切分了(UIC、Forest)、数据存储切分了(DB、TFS、Tair),通过高性能服务框架(HSF)、分布式数据层(TDDL)、消息中间件(Notify)和Session框架支持了这些切分。一个美好的时代到来了,高度稳定、可扩展、低成本、快速迭代、产品化管理,淘宝的3.0系统走上了历史的舞台。
在这个分布式系统的支持下,更多的业务迅速开发出来了, 因为任何一个业务都基于淘宝的商品、交易、会员、评价等基础体系,而这些基础体系就像“云”一样存在,现在可以随处调用了。Hitao、淘花网、良无限、天猫、一淘、聚划算、各种SNS、各种移动客户端等如雨后春笋般地成长起来了。目前,淘宝已经
变成了一个生态体系,包含C2C、B2C、导购、团购、社区等各种电子商务相关的业务。
既然说是一种“生态体系”,那就不能把所有的业务把控在自己的手中,在开发3.0版本的过程中,我们就有一个团队把淘宝“开放”出去了,我们把自己的数据、自己的应用通过接口的方式让更多的开发者调用,他们可以开发出形形色色的产品,例如,你可以开发出心形的淘宝店铺、菱形的店铺,再放到淘宝上供商家购买。淘宝再多的员工,其创造力也是有限的,而开放出去之后,让无限的人都可以参与到这个生态体系的建设中来,这个生态体系才是完整的。下面就是开放平台的架构师放翁所记述的“开放平台这几年”注1。
开放平台
2006年年底:阿里巴巴提出了Workatalibaba的战略,二十多个人就被拉到湖畔花园马云的公寓里开始一个叫阿里软件的公司创业。当时对于Work at alibaba有一个朦朦胧胧的感觉,就是要为中小企业提供一个工作平台,但是工作平台又需要是一个开放的平台,因为卖家的需求是长尾的,当时火热的Salesforce给了阿里人
cenwenchu79中找到详细的内容,同时本文主要介绍开放平台技术发展历程, 产品和业务内容不涵盖在此,因此,受众群体主要是技术人员。——作者注
一些启示,那就是做一个支持二次开发的工作平台,半开放式地满足各种卖家的长尾管理需求。此时,软件市场上就开始培养起最早的一批TP(淘宝开放合作伙伴)。迄今为止,很多非常成功的TP就是从那个时候开始进入淘宝卖家市场的。
但经过一年的平台建设,发现开发者非常难利用平台做二次开发,只有阿里软件公司内部的团队构建了三个不同的CRM 软件。这时候淘宝来了一个业界的技术牛人王文彬(花名:菲青),这位淘宝新晋的首席架构师找到阿里软件的平台架构团队,谈到了当时业界还非常新颖的一种技术平台——开放平台。由于阿里软件已经在做类似的开放工作,希望能够以合作的方式来试水开放平台。当时双方都是一种尝试的态度,因此,最后敲定投入一个人花两周时间,看是否能够做出原型,如果可以,就继续做,如果出不了原型,就此结束。两周时间里,负责阿里软件的架构师放翁参看着美国雅虎的开放模式就搞出了开放平台的第一个雏形,没想到就这样开启了5年的开放之路。后面会根据时间轴来说一下开放平台的产品和技术的变革,每一年会发生很多事情,但是调出的一点一滴是当年最有感触的。
2007年:萌芽。SOA盛行的年代,内部架构服务化成为开放的第一步,内部服务不做好隔离,开放就意味着风险不可控。支付宝今天的服务框架SOFA(类ESB)、淘宝的HSF(OSGI)、阿里软件的ASF(SCA)都是那个年代的产物,但服务化带来的痛却是一样的,不论是OSGI还是SCA之类的服务框架,本身的服务
化规约设计都类似,但难题也都摆在每个架构师和开发者面前: 服务单元Bundle的粒度控制,服务之间依赖管理,性能与规范的冲突,调试与隔离的平衡。这些都使得一线开发者和平台框架实现者出现非常多的矛盾,而这个过程能活下来的框架最后都是摒弃了很多企业级的设计思路,因为SOA架构从企业级产品演变而来,而服务化后的内部平台要面对的开放平台天生就是互联网的产物。
2008年:雏形。2008年年底,平台开放淘宝服务30个,每天调用量2000次,这一年开放平台的开发者面向的客户主要是阿里巴巴上的中小企业和淘宝C店卖家。开放平台建设初期要解决的就是三个问题:
y 服务路由。(外部可以获取内部信息)
y 服务接口标准化。(统一方式的获得各种标准化信息)
y 授权。(外部合法的获取内部信息)
服务路由其实就是写一个高效的HttpAgent,服务接口标准化就是对象文本化(JSON,XML)。今天在各大开放平台广为使用的OAuth协议,当前处于0.6版本,没有任何实际的互联网开放平台使用,直到Google于2008年年底慢慢地对外推广开放的时候,OAuth被封装到Google的Open SDK中,才使得很多中小型互联网公司使用这种看似复杂的两阶段授权交互模式。淘宝初期采
用的是自有协议,因为OAuth2以前的逻辑较复杂且使用不方便, 直到2011年才开始支持OAuth2,同时做了部分的安全增强。授权解决了开放最大的一个问题:用户安全的对应用访问其数据受信。用户从此不用赤裸裸地将用户名和密码交给一个应用软件, 应用也可以在允许的范围内(操作、数据、授权时长)充分利用用户授权来玩转创意。
有了上面的三板斧(路由、数据规范和授权),开放平台正式开门迎客了,没有对外做任何的推广,日均调用数据就猛增到了1000次,此时两个互联网的新兴技术Memcached和Hadoop 开始在开放平台中尝试。今天看来,这两个技术已经被大规模地使用,Memcached无疑是最好的选择,但当时号称分布式缓存的Memcached其实是集中式缓存的一种,真正的分布式缓存还都在纠结于一致性和效率的问题(第2、3阶段提交)。此时需要有一种方式能够保证效率(可扩展)和稳定性,于是我们封装了
Memcached客户端,提升当时BIO的Java客户端的性能,同时引入了客户端负载均衡和容灾的设计,这种设计已经被应用在现在很多大型分布式系统中。另一方面,每天上千万的访问也让技术和产品对访问的行为有很强的分析需求,此时,Hadoop在雅虎的充分利用引起了我们的重视(当时的雅虎技术创新一直都是业界的领头人),通过仅有的两台机器和一堆技术文档,我们摸索着搭建了公司内部的第一个Hadoop集群,而所写的Hadoop入门实践也成为当时Hadoop入门的基础文档,对于每天2000次调用量的日志分析需求来说,Hadoop用得游刃有余,但随着业务的不断发展,Hadoop离线分析所带来的问题也凸显出来,MR程序面对灵活多变的分析需求,显得不易维护且效率低下(数据反复读取分析),于是我们也开始思考怎样改进这个新玩意儿。
2009年:产品化。到2009年年底,平台开放淘宝服务100多个,每天调用量为4000次,这一年开放平台的开发者面对的主要
是淘宝C店卖家,卖家工具成为服务市场的主流。这一年也是变化的一年,阿里软件年中的分拆使得开放平台的归属有些微妙, 一种情况是留在阿里云,作为集团的基础设施,另一种情况就是跟着主要的业务需求方淘宝走,最后我们还是说服了博士, 结束了阿里软件的老平台,淘宝正式开始自己的开放之路。来到淘宝后,业务开放迅猛增长,从30个API猛增到了100个API, 没有对外做任何业务推广,平台调用量到了年底翻番。此时技术上的挑战又聚焦到了性能上,一次API Call的业务消耗平均在
30~40ms,开放平台当时的平台处理消耗平均在10ms左右。我们做了数据打点和分析,发现最大的消耗在于互联网数据的接收, 同时大量的图片数据上行,更是加大了平台处理时间。另外,从访问日志分析中可以看到很多无效的请求也占用了非常多的处理时间,这也意味着无效请求和有效请求一样在消耗着有限的容器线程资源。于是我们开始尝试自己封装字节流解析模块,按需解析上行数据,一来可以提升数据分析的性能(并行业务和数据增量分析操作),二来可以用最小代价处理异常请求(当发现不满足业务规范时,则立刻丢弃后续所有的数据),这块实现被叫做LazyParser,主要的实现重点就是最小化数据缓存来进行并行业务和数据解析操作,上线后效果不错,整个系统平均处理时间从10ms降低到了4ms。(包含了异常处理的优化和解析性能的提升)
另一方面,Hadoop的MR问题也日益突出,一大堆MR的
Class(类)维护成本高、性能问题也随之出现。此时我们开始尝试抽象分析业务场景,想到的是是否能够通过配置就可以完成各种统计分析需求。要用配置替代代码,其实就看是否可以穷举代码所实现的各种统计需求。当回顾SQL的理念时,发现其实所有的统计在切割成为KV作为输入/输出时,所涵盖的需求无非是
Max、Min、Average、Sum、Count、Distinct(这个是2012年实现 的,用了bloomfilter和AtomicLong),再复杂一些无非就是上述几个操作结果的数学表达式运算。因此,KV输入和KV输出的离散统计配置需求已经抽象出来了,接着就是把统计完的一组组KV 根据K来做Groupby(分组),就生成了传统意义上的报表(K,
v1,v2…)。从此以后,每天的统计需求都通过配置来改变,再也没有一大堆MR代码,同时一次数据输入就可以完成所有分析的处理,性能上得到了极大的提高。
虽然Hadoop每日分析抽象出模型配置解决了性能和易用性的问题,但是对于即时分析却不太适合,当时出于监控的需求,希望能够一个小时就可以对数据做一次增量的分析,用于监控服务整体的调用情况,保证对异常问题的即时排查。由于一天4000次的调用量还不算很大,因此,当时就直接考虑采用MySQL分库分表的方式,然后定时做SQL的查询,结果发现效果不错。当然, 这个过程又产生了一个小组件,要直到4000次的日志数据写磁盘和DB双份必然会带来不少的I/O消耗,同时这个系统并不是账务系统,丢掉一点日志也没关系。因此,就采取了异步批量数据外
写的设计(多线程守护各自的一块Buffer页,定时外刷或者满页外刷),这样在双写的情况下,单机的Load也没有超过0.7。
但快到年底的时候,发生了一件事情让我们头痛不已,同时也成了开放平台的一个“隐形炸弹”。一天晚上,突然发生平台大规模拒绝服务的告警,观察整个集群发现,业务处理时间从平均的30~40ms,上升到了1s,仔细观察发现,某一个业务的响应时间大幅攀升,从原来20ms的响应时间飙升到了1s以上,此时由于HTTP请求的同步性,导致前端服务路由网关的集群线程都释放得非常慢,阻塞处理这个业务的请求,而其他正常的业务
(淘宝开放平台背后的服务由不同的团队维护,处理时间从1ms 到200ms都有)也无法被访问,因此,才有了开始的全线告警的产生。后来发现是这个业务团队的一次发布中忽略了数据库索引建立导致服务耗时增加,但这个问题开始时不时地拜访开放平台,开放平台稳定性受制于任何一个业务方,这是不可接受的。对于这个问题,起先考虑集群拆分,即将重要业务和不重要业务拆分,但考虑到实施成本(不同服务的利用率差异很大)和业务隔离是否彻底(重点业务也会相互影响),最终放弃了这个想法。当时又想到了软负载切割Haproxy和LVS,一个是七层的网络软负载切割,一个是四层的负载切割,由于涉及业务,于是考虑用七层的软负载切割,尝试一台Haproxy挂7台虚拟机,然后运行期可动态调整,配置在出现问题的时候可人工干预切割流量。就这样,我们有了告警以后可以手动切割的半人工方式干预措施。
但我们依然晚上睡不踏实……(期间考虑过Web请求异步化和
Servlet3的模式来规避同步HTTP带来的平台阻塞,但当时唯一支持Servlet3的Jetty和Tomcat做压力测试,效果都很不稳定)
2010年:平台化。到2010年年底,平台开放淘宝服务300多个,每天调用量为8亿次,这一年淘宝正式开始对外宣传开放, 淘宝开放年赢在淘宝,目前很多年收入上千万的TP在这个时候成了先锋(2010年以前的可以叫做先烈),产品层面上,这一年除了卖家工具的继续发展,SNS热潮的兴起带动了淘江湖的买家应用,游戏应用的淘金者蜂蛹而入,开放的服务也继续保持300%的增速,覆盖面从卖家类延伸到了买家类,从简单的API提供,到了淘宝网站支持深度集成应用到店铺和社区。
在8亿次访问量的情况下,再用MySQL做流式分析已经不可能了,分析时间要求也从一个小时提升到了20分钟,此时经过快一年半的Hadoop使用和学习,再加上对分布式系统的了解,正式开始写第一版的流式分析系统,MR的抽象依旧保留,而底层的数据计算分析改用其他方式,这个“其他方式”和Hadoop的差异在于:
y 分析任务数据来源于远端服务器日志(主要通过Pull,而非Push)。
y 任务分配和调度采用被动分配( 有点类似于Volunteer
Computing的模式),Mater轻量的管理任务,Slave加入即
可要求执行任务,对任务执行的情况不监控,只简单通过超时来重置任务状态。
y 任务统一由Master来做最后的Reduce,Slave可以支持做
Shuffle来减少数据传输量和Master的合并压力,Master负责统一输出结果到本地。
总的来说,就是数据来源变了,数据不通过磁盘文件来做节点计算交互(只在内存使用一次就丢掉了),简化任务调度,简化数据归并。这样第一版本的流式分析出来了,当然,后面这些设计遇到的挑战让这个项目不断在演进,演进的各种优化几年后发现都在Hadoop或者Hive之类的设计中有类似的做法。这个系统三台虚拟机撑住了8亿次的日志即时分析,MySQL日志分析就此结束。
这一年另一个重大改变就是更多的人对开放的价值有所认同,淘宝从一个部门的开放走到了淘宝公司的开放,什么叫做部门开放?就是在10年以前大部分的API开放都是开放平台这个团队来做封装维护,30个API还可以支撑,100个API已经让一个专业的小团队应接不暇(当然不得不承认,迄今为止,淘宝最有全局业务知识的还属这个团队的成员),300多个API这种势头基本上就无法由一个团队来做了,业务变更带来的接口不稳定经常被投诉。因此,我们启动了服务轻量化的“长征项目”,逐渐通过工
具和平台将服务接入变成自动化的方式,将原来开放一个服务需要点对点,手把手花一周时间实施完成的过程,通过自动化服务发布平台,一个人一天时间就可以发布一个服务,并且服务的文档中,多语言版本SDK都自动生成。这样就具备了服务轻量化的基础,然后将各个新开放的业务采用这种模式接入,而老业务逐渐归还给各个业务方去维护。这样,服务的“稳定性”(业务方面)得到了非常大的提升,用户对于服务的满意度也得到了极大的提高。
但这个担子放下了,那个担子又挑上了,在上面谈到后台应用不稳定导致平台整体不稳定的问题在轻量化以后出现的频率和次数更多了,因为发布和维护都落到了后台部门,此时对于各个系统的把控就更弱了,KPI中的稳定性指标基本就没法定了。唯一能够彻底解决问题的办法就是HTTP服务异步化+事件驱动+ 虚拟隔离线程池。2010年年中对Jetty7做了一次压测,发现Continuations的效果已经可以上正式的环境了,于是开始在Jetty7 的基础上做HTTP服务异步化+事件驱动的封装,同时也实现了一个虚拟隔离线程池做配合。具体设计细节这里就不再多说,参看Blog,简单描述原理如下:
y 将前端容器线程和业务处理隔离。(类似NIO和BIO的设计差异)
y 业务处理如果依赖于外部系统,则采用事件驱动的方式来
减少线程等待,同时提高线程占用资源的利用率。(从这点上说,理想和现实还是有很多细节差异的,在实现的时候必须根据依赖系统消耗时间占总时间的比例看是否需要事件驱动,事件驱动带来的切换消耗是比较大的)
y 通过一个大的线程池虚拟设置不同的业务可消耗的最大资源数,来充分共享资源在异常情况下限制业务占用过多的资源(任务处理开始排队,而非无度地占用资源)。
这个组件上线以后,没过几天就发生了一个典型的案例,一个业务在下午2点开始响应时间从10ms上升到了40ms,然后继续上升到200ms,当时给这个业务模拟设置最大的线程资源数是20个, 就发现那时候由于RT时间提升,线程资源释放得慢,20个慢慢地被消耗完了,此时这个业务的队列开始从0到100,再到200……
(当然,防止内存过多地被占用,会丢弃超过队列长度的业务处理),而其他业务还是正常地使用着资源,平台平稳,到了下午4 点多,业务方收到告警修复以后,RT时间下降到了10ms,队列中的请求数量开始减少,最后队列清空,线程资源占用下降到正常水平。
从此以后,震子开心地和我说:开放平台稳定性的KPI可以随便大胆地写几个9了。
2011年:市场化。到2011年年底,平台开放淘宝服务758个, 每天调用量19亿次,这一年SNS热潮消退,游戏逐渐淡出,卖家
市场依旧生意火爆,营销工具崭露头角,成为开发者新宠,淘宝客成为开放新宠(这一年返利网和团购一样火,只是前者收钱, 后者烧钱)。
就在开放平台前景一片大好的时候,出现了一个让开放转变和收缩的导火索,一家做营销工具的公司“团购宝”每天凌晨都会通过接口同步客户设置的一些优惠商品信息到淘宝网, 结果那天凌晨,微博上突然有很多人说某些店都是一块钱的便宜货,要知道这种事情在微博盛行的时代,传播速度之快,影响之大,当即很多卖家商品都被1块钱拍下。最后发现是线下的商品价格不知道怎么全被修改成1块钱,然后凌晨一同步, 就导致出现了上面的一幕。从那时候开始,开放平台的KPI中增加了一个重中之重的功能:安全,包括后面的很多技术产品都围绕安全展开。此时第一个被波及提升能力的系统就是流式分析集群,20分钟一轮的数据分析要求压缩到3分钟,同时数据量已经从每天8亿条增长到了每天19亿条,花了两个月时间断断续续地优化集群结构设计和单机处理能力,对于其中经历的内容,有一天我翻Hadoop的优化过程时看到了相似的场景, 具体就不在这里赘述,详细内容可参考Blog。简单地说,包括四个方面:充分利用多核能力用计算换内存;磁盘换内存,用并行设计来保证整体业务时间消耗不变甚至减少;Slave Shuffle 来减少Mater的合并压力;数据压缩减少数据传输消耗和内存占用。
另一方面,由于2010年对于Jetty7的充分理解和封装,此时看到了又一个新技术的契机,2010年去美国参加Javaone,当时看到有老外用Jetty7的特性来实现Comet功能,Comet原先主要用于CS 结构的应用搬到互联网上,因为不能用TCP的长连接,所以不得不用HTTP的长连接来替代原来的模式,同时国外开放平台也关注很多新型的API设计,其中就有Twitter的Streaming API,这种通过HTTP长连接方式推送消息到外部ISV(独立软件开发商)的模式引起了我们的注意。因此,我们决定将Jetty7上的封装近一步升级,支持Comet长连接方式,后端通过事件驱动的模式主动推送内部消息给外部,避免外部轮询业务接口。这个设计最重要的一点就是如何用最有效且最少的线程来守护多个长连接,支持到后端事件驱动的数据下行,如果给每一个长连接支持一个数据推送守护线程,即时性自然最高,但代价就是消耗众多空置连接的守护线程(详细内容见Blog)。这种模式刚出来的时候,从上到下都是质疑声,觉得太不符合常规做法,常规做法就是pull, 认为开发人员无法接受,稳定性一定不靠谱。经过2011年的“双十一”,当天几个“尝鲜”的开发者用一台PC就支持几百万笔订单的高速处理,就让很多人明白了,技术要敢想,代码要敢写, 细节要敢专,没什么不可能。也就从这以后,多样化服务TQL、Schedule API、ATS从开放平台的土壤上都长了出来,为更多的场景和更多的终端提供了各种解决方案和创新实现。
2012年:垂直化。这一年到现在,平台开放淘宝服务900多
个,每天调用量为25亿次,这一年淘宝客由于公司方向转变热潮消退,无线乘势而起,新业务(机彩票、酒店、理财等)、P4P、数据类服务都开始运营API,开放平台开发者的客户群体也从C店卖家增加到了B的品牌商和渠道商等。
这是一个业务多变的一年,这也是淘宝内部对开放平台认可的新阶段。第一个阶段是放任不管,任由开放平台部门开放和封装。第二阶段是由业务方负责支持开放业务,但开放后的结果概不了解,也无所谓了解。第三阶段就是业务主动要开放,开放后开始运营服务,培养ISV市场,带动业务的正向发展。
这一年由于业务量的增长以及分析需求到用户纬度,因此, 在2011年年底启动了流式分析集群重构升级的项目,将新的分析集群项目命名为Beatles,希望它能够象甲壳虫一样,小虫吃树叶,再多都能吃下。2011年底到2012年初,用了近两个半月的时间做了一次完整的重构,将那么多年的补丁经验和老代码重新设计和实现,并且将Mater根据业务可垂直切分,最终解决Master归并压力的问题,当然期间的技术优化点也不少,因为我们的目标从3分钟压缩到了1分钟,而我们的数据量翻番,统计纬度细化到了用户纬度。(意味着结果也会很大,如果不靠文件做中转,如何来实现需要更多的分拆和协同设计)
这一年起了两个比较创新的项目:JS SDK和无线SDK(IOS, 安卓),这两个SDK的出现在一定程度上由业务和安全两方面决
定。首先,2011年年底启动了社区电子商务化的项目,也就是现在所说的轻电商(XTao)项目,将更多的网站和淘宝衔接起来,此时网站间的融合就要求更轻便和简易,最成功的案例就是Facebook,于是2012年年初的时候,拿这FackBook的JS SDK一阵看,就开始动手写了,期间很高兴拉了UED入伙,这才使得这个JS SDK变得更加靠谱,更加专业。同时有了JS SDK,买家的服务安全性有所保证,因为原先的REST调用在授权以后是无法知道是用户发起的还是服务器发起的,而JS SDK从一定程度上还要校验Cookie的有效性,可以部分保证用户的在场和知情。而下半年的无线SDK,就是苦读一个月的各种文档,然后就开始动手玩儿了,由于对Java语言、动态语言、脚本语言都有比较多的使用,因 此,Objective-C语言上手并不是那么困难,同时没有涉及过多的MVC的内容,做SDK基础层的东西还是比较得心应手的,就这样
IOS的无线SDK版本就生成了,此时在开放平台的技术团队内部正在执行一个叫做Hack project的活动,其中一个自主项目就是安卓的SDK,因此,一个月后,安卓的SDK顺利诞生了。这两个无线SDK所担负的职责就是把控无线安全问题,不仅是淘宝,业界其实很多公司都还没理解无线开放的风险到底有多大,OAuth2基本就无法保证无线的用户安全,因此,如何在SDK和服务端融入更高级别的安全设计,成了无线SDK诞生的第一个重要需求。
另一方面,开放平台安全体系的构建成为2012年的重点,从两个角度对安全做了全方位的控制。
第一,用户。用户授权更细化了授权操作范围(细粒度到了数据范畴),授权时长。所有的信息可监控、归档、快速定位, 我们内部叫做Top Ocean,简单说来就是对所有的访问日志做归档,归档的载体是块状文件,归档时对块状文件的所有记录按照需求建立索引,然后保留索引,上传本地文件到远端分布式文件系统备份。实时的监控服务调用和应用访问,授权异动。
第二,第三方应用。采用监控集群对所有ISV的服务器做安全扫描,对普通的Web安全漏洞做扫描,对应用的可用性和响应时间做监控。同时,正式启动“聚石塔”项目,提供弹性计算和存储能力及可靠的安全网络环境给ISV,帮助ISV提供自身应用的安全性。
至此为止,5年左右的技术历程已部分展示在了大家的面前, 这些只是5年中比较有代表性的一部分,同时技术的发展也只是开放平台的一部分,前5年是技术变革带动开放平台发展,而接下去的5年将会是业务变革和理解带动开放平台的阶段,对业务的理解直接决定了开放平台的价值所在。前面轻描淡写地介绍了5年来不同开放业务的兴衰,其实这背后却有更多耐人寻味的故事,而
5年后的今天,淘宝的格局为:集市(C2C)、天猫(B2C)、一淘(电商搜索返利入口)、无线、新业务、O2O(本地生活)、团购平台(聚划算),这些平台的价值是什么?如何找到自身定位?如何借助外力发展?如何面对流量入口的兴起、传统互联网企业的电商化、电商平台的竞争?这些才是开放平台2012年及下一个5年的精彩所在。