4. 自主研发的系统可在软件和硬件多个层次不断的优化。
历史总是惊人的巧合,在我们准备研发文件存储系统的时候,Google 走在了前面,2007 年他们公布了 GFS( Google File System )的设计论文,这给我们带来了很多借鉴的思路。随后我们开发出了适合淘宝使用的图片存储系统TFS(Taobao File System)。3年之后,我们发现历史的巧合比我们想象中还要神奇,几乎跟我们同时,中国的另外一家互联网公司也开发了他们的文件存储系统,甚至取的名 字都一样 —— TFS,太神奇了!(猜猜是哪家?)
2007 年 6 月,TFS 正式上线运营。在生产环境中应用的集群规模达到了 200 台 PC Server(146G*6 SAS 15K Raid5),文件数量达到上亿级别;系统部署存储容量:140TB;实际使用存储容量: 50TB;单台支持随机IOPS200+,流量 3MBps。
要讲 TFS 的系统架构,首先要描述清楚业务需求,淘宝对图片存储的需求大概可以描述如下:
文件比较小;并发量高;读操作远大于写操作;访问随机;没有文件修改的操作;要求存储成本低;能容灾能备份。应对这种需求,显然要用分布式存储系统;由 于文件大小比较统一,可以采用专有文件系统;并发量高,读写随机性强,需要更少的 IO 操作;考虑到成本和备份,需要用廉价的存储设备;考虑到容灾,需要能平滑扩容。
参照 GFS 并做了适度的优化之后,TFS 1.0 版的架构图如下:
从上面架构图上看:集群由一对 Name Server 和多台 Data Serve r构成,Name Server 的两台服务器互为双机,就是集群文件系统中管理节点的概念。
在这个架构中:
• 每个 Data Server 运行在一台普通的 Linux 主机上
• 以 block 文件的形式存放数据文件(一般64M一个block )
• block 存多份保证数据安全
• 利用 ext3 文件系统存放数据文件
• 磁盘 raid5 做数据冗余
• 文件名内置元数据信息,用户自己保存 TFS 文件名与实际文件的对照关系 – 使得元数据量特别小。
淘宝 TFS 文件系统在核心设计上最大的取巧的地方就在,传统的集群系统里面元数据只有 1 份,通常由管理节点来管理,因而很容易成为瓶颈。而对于淘宝网的用户来说,图片文件究竟用什么名字来保存实际上用户并不关心,因此TFS 在设计规划上考虑在图片的保存文件名上暗藏了一些元数据信息,例如图片的大小、时间、访问频次等等信息,包括所在的逻辑块号。而在元数据上,实际上保存的 信息很少,因此元数据结构非常简单。仅仅只需要一个 fileID,能够准确定位文件在什么地方。
由于大量的文件信息都隐藏在文件名中,整个系统完全抛弃了传统的目录树结构,因为目录树开销最大。拿掉后,整个集群的高可扩展性极大提高。实际上,这一 设计理念和目前业界的“对象存储”较为类似,淘宝网 TFS 文件系统已经更新到 1.3 版本,在生产系统的性能已经得到验证,且不断得到了完善和优化,淘宝网目前在对象存储领域的研究已经走在前列。
在 TFS 上线之前,淘宝网每个商品只允许上传一张图片,大小限定在 120K 之内,在商品详情里面的图片必须使用外站的服务。那时侯发布一件商品确实非常麻烦,笔者曾经想卖一台二手电脑,先把照片上传到 Google 相册,在发布到淘宝网之后发现 Google 相册被墙了,我的图片别人看不到,当时郁闷的不行。TFS 上线后,商品展示图片开放到 5 张,商品描述里面的图片也可以使用淘宝的图片服务,到现在为止,淘宝网给每个用户提供了 1G 的图片空间,这下大家都满足了。技术和业务就是这么互相用力的推动着,业务满足不了的时候,技术必须创新,技术创新之后,业务有了更大的发展空间。
1.3 版本的架构见阿里味(阿里巴巴内网)⋯⋯
六、淘宝技术发展(分布式时代:服务化)
在系统发展的过程中,架构师的眼光至关重要,作为程序员,把功能实现即可,但作为架构师,要考虑系统的扩展性、重用性,这种敏锐的感觉,有人说是一种代 码洁癖。淘宝早期有几个架构师具备了这种感觉。一指开发的 Webx 是一个扩展性很强的框架,行癫在这个框架上插入了数据分库路由的模块、session 框架等等。在做淘宝后台系统的时候,同样需要这几个模块,行癫指导我把这些模块单独打成了 jar 包。另外在做淘宝机票、彩票系统的时候,页面端也有很多东西需要复用,最直观的是页头和页脚,一开始我们每个系统里面复制了一份过去,但奇妙的是,那段时 间页脚要经常修改,例如把“雅虎中国”改成“中国雅虎”,过一段时间又加了一个“口碑网”,再过一段时间变成了“雅虎口碑”,最后又变成了“中国雅虎”, 每个系统都改一遍,折腾啊。后来我就把这部分 velocity 模版单独拿出来了,做成了公用的模块。
上面这些都是比较小的复用模块,到 2006 年我们做了一个商品类目属性的改造,在类目里面引入属性的概念。项目的代号叫做“泰山”,如同它的名字,这是一个举足轻重的项目,这个改变是一个划时代的 创新。在这之前的三年时间内,商品的分类都是按照树状的一级一级的节点来分的,随着商品数量的增长,类目也变得越来越深,越来越复杂,这带给买家的就是查 找一件商品要逐级类目点开,找商品之前要懂商品的分类。而淘宝运营部门管理类目的小二也发现一个很严重的问题 —— 例如男装里面有T恤、T恤下面有耐克、耐克有纯棉的,女装里面也有T恤、T恤下面还是有耐克、耐克下面依然有纯棉的,那是先分男女装再分款式再分品牌再分 材质呢?还是先分品牌再分款式再分材质再分男女呢?晕倒了。这时候,一位大侠出来了 —— 一灯,他说品牌、款式、材质这种东东可以叫做“属性”,属性是类似 tag 的一个概念,与类目相比更加离散,更加灵活,这样也缩减了类目的深度。这个思想的提出,一举解决了分类的难题!从系统的角度来看,我们建立了“属性”这样 一个数据结构,由于除了类目的子节点有属性,父节点也有可能有属性,于是类目属性合起来也是一个结构化的数据对象。这个做出来之后我们把它独立出来作为一 个服务,叫做 catserver(category server)。跟类目属性密切关联的商品搜索功能,独立出来,叫做 hesper(金星),catserver 和 hesper 供淘宝的前后台系统调用。
现在淘宝的商品类目属性已经是地球上最大的了,几乎没有什么类目的商品在淘宝上找不到(除了违禁的),但最初类目属性改造完之后,我们很缺属性数据,尤 其是数码类的最缺。那从哪里弄这些数据呢亲?我们跟“中关村在线”合作,拿到了很多数据,那个时候,很多商品属性信息的后边标注着:“来自中关村在线”。 有了类目属性,给运营的工作带来很大的便利,我们知道淘宝的运营主要就是类目的运营,什么季节推什么商品,都要在类目属性上面做调整,让买家更容易找到。 例如夏天我要用户在女装一级类目下就标出来材质是不是蕾丝的、是不是纯棉的,冬天却要把羽绒衣调到女装一级类目下,流行什么就要把什么商品往更高级的类目 调整。这样类目和属性要经常调整,随之而来的问题就显现了 —— 调整到哪个类目,那类商品的卖家就要编辑一次自己的商品,随着商品量的增长,卖家的工作量越来越大,然后我们就发现卖家受不了啦。到了 2008 年,我们研究了超市里面前后台商品的分类,发现超市前台商品可以随季节和关联来调整摆放场景(例如著名的啤酒和尿布的关联),后台仓库里面要按照自然类目 来存储,二者密切关联却又相互分开。然后我们就把前后台类目分开了,这样卖家发布商品选择的是自然类目和属性,淘宝前台展示的是根据运营需要而摆放的商品 的类目和属性。改造后的类目属性服务取名叫做 forest(森林,跟类目属性有点神似。catserver 还在,提供卖家授权、品牌服务、关键词等相关的服务)。类目属性的服务化,是淘宝在系统服务化方面做的第一个探索。
虽然个别架构师具备了代码洁癖,但淘宝前台系统的业务量和代码量还是爆炸式的增长了起来。业务方总在后面催,开发人员不够了就继续招人,招来的人根本看 不懂原来的业务,只好摸索着在“合适的地方”加一些“合适的代码”,看看运行起来像那么回事,就发布上线了。在这样的恶性循环中,系统越来越臃肿,业务的 耦合性越来越高,开发的效率越来越低。借用当时比较流行的一句话“写一段代码,编译一下能通过,半个小时就过去了;编译一下没通过,半天就过去了。”在这 种情况下,系统出错的概率也逐步增长,常常是你改了商品相关的某些代码,发现交易出问题了,甚至你改了论坛上的某些代码,旺旺出问题了。这让开发人员苦不 堪言,而业务方还认为这帮人干活越来越慢了。
大概是在 2007 年底的时候,研发部空降了一位从硅谷来的高管,空闻大师。空闻是一位温厚的长者,他告诉我们一切要以稳定为中心,所有影响系统稳定的因素都要解决掉。例如 每做一个日常修改,都必须整个系统回归测试一遍;多个日常修改如果放在一个版本里面,要是一个功能没有测试通过,整个系统都不能发布。我们把这个叫做“火 车模型”,任何一个乘客没有上车,都不许发车。这样做的最直接后果就是火车一直晚点,新功能上线更慢了,我们能明显的感觉到业务方的不满,空闻的压力肯定 非常大。当时我都不理解这种一刀切的做法,为了稳定牺牲了发展的速度,这跟某 Party 的“稳定压倒一切”有什么分别?
但是到现在回过头来看看,其实我们并没有理解背后的思路。正是在这种要求下,我们不得不开始改变一些东西,例如把回归测试日常化,每天晚上都跑一遍整个 系统的回归。还有就是在这种要求下,我们不得不对这个超级复杂的系统做肢解和重构,其中复用性最高的一个模块 —— 用户信息模块开始拆分出来了,我们叫它 UIC(user information center)。在 UIC 里面,它只处理最基础的用户信息操作,例如getUserById、getUserByName等等。
在另外一个方面,还有两个新兴的业务,也对系统基础功能的拆分提出了要求。在那个时候,我们做了淘宝旅行(trip.taobao.com)和淘宝彩票 (caipiao.taobao.com)两个新业务,这两个新业务在商品的展示和交易的流程上都跟主站的业务不一样,机票是按照航班的信息展示的,彩票 是按照双色球、数字和足球的赛程来展示的。但用到的会员的功能和交易的功能是跟主站差不多的,当时做的时候就很纠结,在主站里面做的话,会有一大半跟主站 无关的东西,重新做一个的话,会有很多重复建设。最终我们决定不再给主站添乱了,就另起炉灶做了两个新的业务系统。从查询商品、购买商品、评价反馈、查看 订单这一整个流程都重新写了一套出来。现在在“我的淘宝”里面查看交易记录的时候,还能发现“已买到的宝贝”里面把机票和彩票另外列出来了,他们没有加入 到普通的订单里面去。在当时如果已经把会员、交易、商品、评价这些模块拆分出来,就不用什么都重做一遍了。
到 2008 年初,整个主站系统(有了机票、彩票系统之后,把原来的系统叫做主站)的容量已经到了瓶颈,商品数在一亿以上,PV 在 2.5 亿以上,会员数超过了五千万。这个时候 Oracle 的连接池数量都不够用了,数据库的容量到了极限,上层系统再增加机器也无法继续扩容了,我们只有把底层的基础服务继续拆分,从底层开始扩容,上层才能扩 展,这才能容纳以后三五年的增长。
于是那一年我们专门启动了一个更大的项目,把交易这个核心业务模块也拆分出来了。原来的淘宝交易除了跟商品管理耦合在一起,也在支付宝和淘宝之间跳来跳 去,跟支付宝耦合在一起,系统复杂,用户体验也很不好。我们把交易的底层业务拆出来叫交易中心TC(trade center),所谓底层业务是例如创建订单、减库存、修改订单状态等原子型的操作;交易的上层业务叫交易管理TM(trade manager),例如拍下一件普通商品要对订单、库存、物流进行操作,拍下虚拟商品不需要对物流进行操作,这些在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 框架。再者,还有一个软件工程方面的问题,这么多层的一套系统,怎么去测试它?