2005年,我开始和朋友们开始拉活儿做网站,当时第一个网站是在linux上用jsp搭建的,到后来逐步的引入了多种框架,如webwork、hibernate等。在到后来,进入公司,开始用c/c++,做分布式计算和存储。(到那时才解开了我的一个疑惑:C语言除了用来写HelloWorld,还能干嘛?^_^)。
总而言之,网站根据不同的需求,不同的请求压力,不同的业务模型,需要不同的架构来给予支持。我从我的一些经历和感受出发,大体上总结了一下的一些阶段。详情容我慢慢道来。
【第一阶段:搭建属于自己的网站】
我们最先开始的网站可能是长成这个样子的:
拿Java做例子,我们可能会引入struts、spring、hibernate等框架,用来做URL分流,C、V、M隔离,数据的ORM等。这样,我们的系统中,数据访问层可以抽取出很多公用的类,业务逻辑层也可以抽取出很多公用的业务类,同一个业务逻辑可以对应多个展示页面,可复用性得到极大的增强。
不过,从性能上看,引入框架后,效率并不见得比第一种架构高,有可能还有降低。因为框架可能会大量引入“反射”的机制,来创建对应的业务对象;同时,也可能增加额外的框架逻辑,来增强隔离性。从而使得整体服务能力下降。幸好,在这个阶段,业务请求量不大,性能不是我们太care的事情。J
【第三阶段:降低磁盘压力 】
可能随着业务的持续发展,或者是网站关注度逐步提升(也有可能是搜索引擎的爬虫关注度逐步提升。我之前有一个网站,每天有超过1/3的访问量,就是各种爬虫贡献的),我们的请求量逐步变大,这个时候,往往出现瓶颈的就是磁盘性能。在linux下,用vmstat、iostat等命令,可以看到磁盘的bi、bo、wait、util等值持续高位运行。怎么办呢?
其实,在我们刚刚踏进大学校门的时候,第一门计算机课程——《计算机导论》里面就给出了解决方案。依稀记得下面这个图:
在我们的存储体系里面,磁盘一般是机械的(现在Flash、SSD等开始逐步大规模使用了),读取速度最慢,而内存访问速度较快(读取一个字节约10μs,速度较磁盘能高几百倍),速度最快的是CPU的cache。不过价格和存储空间却递减。
话题切换回来,当我们的磁盘出现性能瓶颈的时候,我们这个时候,就要考虑其他的存储介质,那么我们是用cpu cache还是内存呢,或是其他形态的磁盘?综合性价比来看,到这个阶段,我个人还是推荐使用内存。现在内存真是白菜价,而且容量持续增长(我现在就看到64G内存的机器[截止2012-4-3])。
但是问题来了,磁盘是持久化存储的,断电后。数据不会丢失,而内存却是易失性存储介质,断电后内容会丢失。因此,内存只能用来保存临时性数据,持久性数据还是需要放到磁盘等持久化介质上。因此,内存可以有多种设计,其中最常见的就是cache(其他的设计方式会在后面提及)。这种数据结构通常利用LRU算法(现在还有结合队列、集合等多种数据结构,以及排序等多种算法的cache),用于记录一段时间的临时性数据,在必要的时候可以淘汰或定期删除,以保证数据的有效性。cache通常以Key-Value形式来存储数据(也有Key-SubKey-Value,或者是Key-List,以及Key-Set等形式的)。因为数据存放在内存,所以访问速度会提高上百倍,并且极大的减少磁盘IO压力。
Cache有多种架构设计,最常见的就是穿透式和旁路式。穿透式通常是程序本身使用对应的cache代码库,将cache编译进程序,通过函数直接访问。旁路式则是以服务的方式提供查询和更新。在此阶段,我们通常使用旁路式cache,这种cache往往利用开源的服务程序直接搭建就可以使用(如MemCache)。旁路式结构如下图:
请求来临的时候,我们的程序先从cache里面取数据,如果数据存在并且有效,就直接返回结果;如果数据不存在,则从数据库里面获取,经过逻辑处理后,先写入到cache,然后再返回给用户数据。这样,我们下次再访问的时候,就可以从cache中获取数据。
Cache引入以后,最重要的就是调整内存的大小,以保证有足够的命中率。根据经验,好的内存设置,可以极大的提升命中率,从而提升服务的响应速度。如果原来IO有瓶颈的网站,经过引入内存cache以后,性能提升10倍应该是没有问题的。
不过,有一个问题就是:cache依赖。如果cache出问题(比如挂了,或是命中率下降),那就杯具了L。这个时候,服务就会直接将大的压力压向数据库,造成服务响应慢,或者是直接500。
另外,服务如果重新启动时,也会出现慢启动,即:给cache充数据的阶段。对于这种情况,可以采取回放日志,或是从数据库抽取最新数据等方式,在服务启动前,提前将一部分数据放入到cache中,保证有一定命中率。
通过这样的拆分后,我们就迈出了多机的第一步。虽然看起来比较简单和容易,但是这也是非常具有里程碑意义的。这样的优化,可能会提升20-30%左右的一个CPU idle。能够使得我们的网站能够经受更大的压力。
【第五阶段:逻辑程序的多机化】
当我们的访问量持续增加的时候,我们承受这成长的快乐和痛苦。流量刷刷往上涨,高兴!但是呢,服务器叫苦不绝,我们的程序已经快到不能服务的边缘了。怎么办?
“快使用分布式,哼哼哈嘿”J
这个时候,我们就需要针对CPU瓶颈,将我们的程序分别放在多台服务器上,让他们同时提供服务,将用户请求分摊到多个提供服务的机器。
好,如果提供这样的服务,我们会遇到什么样的问题?怎么样来解决?
我们一个个的来分析:
一、WebServer怎么样来分流用户请求?That’s a good question!
在考虑这个问题的时候,我们常见的WebServer早已给我们想好了解决方案。现在主流的WebServer几乎都提供一个叫“Load Balance”的功能,翻译过来就是负载均衡。我们可以在WebServer上配置一组机器列表,当请求来临的时候,WebServer会根据一定的规则选取某一台机器,将请求转发到对应的逻辑处理程序上。
有同学马上就会问了,“一定的规则”是怎么样的规则?他能解决什么样的问题?如果机器宕了怎么办?……
哈哈,这里的一定规则就是“Load Balance”。负载均衡其实是分布式计算和存储中最基础的算法,他的好坏,直接决定了服务的稳定性。我曾经设计和开发了一个负载均衡算法(现在正在大规模使用),有一次就因为一个很小的case,导致服务大面积出现问题。
负载均衡要解决的就是,上游程序如何在我们提供的一堆机器列表中,找到合适的机器来提供下游的服务。因此,我们可以将负载均衡分成两个方向来看:第一,根据怎样的规则来选机器;第二,符合规则的机器中,哪些是能提供服务的。对于第一个问题,我们通常使用随机、轮询、一致Hash等算法;对于第二个问题,我们要使用心跳、服务响应判定等方法检测机器的健康状态。关于负载均衡,要谈的话点其实很多,我之前也写过专门的一篇文章来介绍,后续有空了,我再详细的描述。
总之,分流的问题,我们可以通过负载均衡来比较轻松的解决了。