1. 首先假想:在构建系统以前,一些基础服务已经完备,不必再重头再去一一构建。这些基础服务包括:
日志服务:保存各种日志异步消息总线服务:用于系统间的解耦,进行异步消息通讯。
监控服务:监控各种运营数据,便于发现问题。
配置服务:提供一个容灾的集中的中心配置点,所有的相关配置都可以从配置中心里获取,简化运维,典型的比如开源的zoo_keeper
日志计算服务:map-reduce, 典型的如hadoop,用于离线的各种数据分析。
分布式key-value存储:由唯一的一个key快速定位到value,提供get/set/remove三个基本操作。实现为分布式系统,可自动扩容。
分布式文件系统:可以存储海量的小文件,诸如图片服务等。
CDN服务:做内容分发,把内容复制到离用户最近的访问点上去。
短URL服务:把一个长URL转成极短的URL。
以上服务都是逻辑上极度简单的,便于理解。但实现上有很不简单,比如分布式、比如容灾等,假想他们都是稳定而又可靠的。
2. 第二个部分是微博的存储:
先看微博本身的特点,最长140个字,假设使用UTF-8编码,则每条微博最多140*3=420字节。因此1kb的存储空间可以存储两条微博,加上微博本身的一些元数据,以每条微博512字节来进行存储吧。已经定义了每条微博固定占512字节,则微博可以顺序存储,把所有微博放在一个连续的大文件里,根据偏移量就可以随机定位到任意一条微博。单个文件肯定是不行的,因此一个机器上可以放多个文件,多个机器分别存储不同内容,再按群集部署,一定的群集放在某个机房内,再进一步扩展,不同的城市再建立多个机房……
城市,机房,群集,机器,文件,偏移量……以上的信息都用一个数字来编号的话,每条微博最终都能得到一条唯一的ID。 不过,城市和机房等因素一般是用来做异地容灾的,与ID无关。
再者,由于微博的完全开放的,因此就算其他人猜出任意一条微博的ID,也不存在隐私方面的问题。就算用同样的存储结构存储私信等内容,仍可以在微博的额外的92个字节的meta区域加上权限和类型等信息,然后接入服务器读取微博的内容后,判断当前的读取者无权获得此条微博,再返回错误就行。
决定了微博的存储结构和存储方式,下一步是确定微博的数据如何分布到多个文件,多个机器,甚至多个群集上。
从群集上看,按用户进行分区是好办法。假设当前有1000万用户,则可以每个群集服务100万用户,一共十个群集即可。用户量增加后,再按群集进行扩展。
微博的特性也决定了,微博是按时间顺序来进行读取的。因此第二个分区的维度是时间:简单的把当前发表的微博顺序存储在大文件中,然后返回微博的ID即可。一个文件写满后写另一个,一台机器写满后再写另一台……这样也带来了缺点:群集中存储最近的微博内容的机器总是IO最高的,其他的则比较闲。因此也可以在写微博的时候,前端随即分发给所有群集内的服务器,每个服务器内部进行切换文件。当所有机器都达到一定量级后,在群集内增加机器,把比较老的文件迁移到新的机器上,然后写分发从原来的N台机器变为N+M台。
这样的存储不仅仅存储微博:转播,评论,私信,短URL等内容都可以以这样的方式保存下来。
存储量的问题:TWITTER现在的发表量每天3.4亿条(参考:《 用户达1.4亿 每天Tweets发布量达3.4亿条 》, http://web2.iresearch.cn/87/20120324/167421.shtml ),按照这样的存储方式,则每天增加的存储空间为162Gb。sina微博每天发表1亿条微博: http://www.bjnews.com.cn/finance/2012/05/16/199252.html,存储空间占用为47.7G。考虑异地容灾,存储两份或者三份的话,压力也不算太大。
关于存储的另一个考虑是:如果不固定分配512字节,按照紧凑存储的话,会更省空间。假设已经统计到平均每个用户的微博长度为200字节,加上头信息和预留存储位,可能最多300字节就能存储一条。因此,这里可以折中一下,把存储单元按128字节划分为一个块,一条微博最多占用4个块,也可以只占用一个块。假设平均的微博长度仅占用两个块,则上面计算出的3.4亿条微博每天的存储量减半,达到81.1G。
3. 存储之上:近期热点数据,CACHE,传输,聚合拉取等问题
有了存储后,不可能用户读取微博的时候,都从磁盘去读取,一定是要有CACHE的。并且,微博的访问特点与时间相关性很大,比如,最近一两天的微博是最热的;最近一两个星期的内容,还有人会去看看;超过一个月以前的内容,可能几乎没人去访问了。
根据这个特点,我们构建一个三级的存储:所有微博的最终落地是磁盘,容量大,成本低;最近一个月的微博,采用SSD存储,性价比适中;最近一周内的微博,全部存储在内存中。
假设微博新增量有twitter那么大,那需要的容量为:(按最大存储容量计算,按照平均长度计算的话,容量减半)
内存:162G*7 = 1134G
SSD: 162*30 = 4860G
磁盘:162*365 = 57.7T/每年
内存,SSD,磁盘,其存储的格式可以都保持一致,仍可以以唯一的微博ID索引到。一条最新的微博会同时存在与内存,SSD和磁盘中;内存和SSD中的内容,相关的服务进程会定期对超时的数据进行淘汰。存储层的接口服务器来决定把请求转发到内存,SSD还是磁盘。
另一个可能是,在SSD或磁盘中的某条微博突然死灰复燃,又火起来了。在架构上,可以在SSD存储和磁盘存储之上再加自己的CACHE层,把访问得多的数据CACHE住,避免偶发性的热点数据的情况。
从这样的设计也可以看出,总体上采用了有损服务的策略:仅保证了近期的微博有较好的读取速度,晚一点的就会越来越慢。
再一个问题是传输:每条微博最多512字节,你想到了什么?小数据量的高性能传输,是使用UDP通讯的极佳场合:
1. 从性能上而言,网络上的IO次数,TCP的极限是2万次/s,而UDP的极限是10万次/s;
2. 一条微博完全可以放在一个UDP包内,甚至放多条也是可以的;
3. 丢包的问题:极端情况下,承载一条微博内容的UDP包丢了,会怎么样?那就让它丢吧,让用户在某个时间点少看一条微博又不会死,更不会怀孕……没什么大不了!
4. 在系统内部的复杂网络环境里,可以做到点对点通讯。或许你认为点对点通讯没什么大不了的,我来举个例子:假设使用TCP协议,服务器A请求服务器B,服务器B再请求服务器C,服务器C响应给服务器B,服务器B再响应给服务器A,类似代理一样,司空见惯,没什么异常的;但是,如果使用了UDP,则可以有这样的效果:服务器A请求服务器B,并在包体中带上自己的IP和端口,服务器B转发请求给服务器C,服务器C响应的时候,根据包体内的地址,直接响应给服务器A。P2P通讯的好处就是每个代理服务器负责转发就好,不必等待请求的返回,代理本身的吞吐量提高了,且缩短了回路的路径长度,效率更高。
下一个问题是微博主页的聚合拉取。经过前面的介绍,我们了解到如下信息:
1. 微博有唯一ID,可以快速定位到确定的一条微博;
2. 近期的微博都在内存中,访问极快;
3. 机器间的微博使用UDP传输,性能极其快……
有了上面的铺垫,下面看起来不可能实现的功能也就得以实现了:每个人收听了N个人,N个人发了M条微博,则进入微博主页的时候,需要拉取N*M条数据,然后按时间排序,显示给用户。
计算方法很简单,但数据量非常大,要做到快速显示出结果,只有一个方法:并行计算。
之前有提到,服务器群集按用户来划分,某部分用户存储在某个群集中。首先假设每个用户收听的人,还有每个人发表微博的ID列表这两种数据是已经存储好了的。拉取首页的流程如下:
1. 主服务器拉取当前用户的收听的人的列表;(花费时间T1)
2. 主服务器根据用户分区的规则,把获取这个人微博的请求分别发到对应的群集上,然后等待一定的时间;(发出请求只花费很少的时间,忽略不计,自身最多等待T2时间)
3. 群集服务器收到请求后,请求用户创建微博的ID列表,返回给主服务器;(花费时间T3)
4. 主服务器对N个用户的M条微博ID按时间排序,确定分页,取某个ID段;(花费时间T4)
5. 主服务器把微博ID发到多个微博存储服务器,等待时间T5;
6. 微博存储服务器根据ID返回微博,花费时间T6;
7. 主服务器输出内容……完成!
按照最慢的时间:T1+T2+T4+T5,T2,T5这个等待时间段内,可能都没返回,可能返回了一部分;都没返回就让用户重试,返回了一部分就显示这部分。有损服务,总比让用户一直傻等的好。
最快的情况是T3<T2, T6<T5,数据都拉到了,皆大欢喜。
在T3, T5这两个步骤,请求是分布在多个机器上完成的,庞大的数据分摊给了整个群集,而非顺序地逐个执行,因而性能得以提高。
没有评论:
发表评论