【转】大型网站架构系列
转载自www.crazycoder.cn 疯狂代码
大型网站架构系列之一,前言,不得不考虑的问题
前言:这两天机器坏了,正在送修中,写个系列的大型网站架构的文章,希望对有志在互联网做出一番事业的站长朋友们一些帮助。
注意:这里的大型网站架构只包括高互动性高交互性的数据型大型网站,基于大家众所周知的原因,我们就不谈新闻类和一些依靠HTML静态化就可以实现的架构了,我们以高负载高数据交换高数据流动性的网站为例,比如海内,开心网等类似的web2.0系列架构。我们这里不讨论是PHP还是JSP或者.NET环境,我们从架构的方面去看问题,实现语言方面并不是问题,语言的优势在于实现而不是好坏,不论你选择任何语言,架构都是必须要面对的。
文入正题:
首先讨论一下大型网站需要注意和考虑的问题
A. 海量数据的处理。
众所周知,对于一些相对小的站点来说,数据量并不是很大,select和update就可以解决我们面对的问题,本身负载量不是很大,最多再加几个索引就可以搞定。对于大型网站,每天的数据量可能就上百万,如果一个设计不好的多对多关系,在前期是没有任何问题的,但是随着用户的增长,数据量会是几何级的增长的。在这个时候我们对于一个表的select和update的时候(还不说多表联合查询)的成本的非常高的。
B. 数据并发的处理
在一些时候,2.0的CTO都有个尚方宝剑,就是缓存。对于缓存,在高并发高处理的时候也是个大问题。在整个应用程序下,缓存是全局共享的,然而在我们进行修改的时候就,如果两个或者多个请求同时对缓存有更新的要求的情况下,应用程序会直接的死掉。这个时候,就需要一个好的数据并发处理策略以及缓存策略。
另外,就是数据库的死锁问题,也许平时我们感觉不到,死锁在高并发的情况下的出现的概率是非常高的,磁盘缓存就是一个大问题。
C. 文件存贮的问题
对于一些支持文件上传的2.0的站点,在庆幸硬盘容量越来越大的时候我们更多的应该考虑的是文件应该如何被存储并且被有效的索引。常见的方案是对文件按照日期和类型进行存贮。但是当文件量 是海量的数据的情况下,如果一块硬盘存贮了500个G的琐碎文件,那么维护的时候和使用的时候磁盘的Io就是一个巨大的问题,哪怕你的带宽足够,但是你的磁盘也未必响应过来。如果这个时候还涉及上传,磁盘很容易就over了。
也许用raid和专用存贮服务器能解决眼下的问题,但是还有个问题就是各地的访问问题,也许我们的服务器在北京,可能在云南或者新疆的访问速度如何解决?如果做分布式,那么我们的文件索引以及架构该如何规划。
所以我们不得不承认,文件存贮是个很不容易的问题
D. 数据关系的处理
我们可以很容易的规划出一个符合第三范式的数据库,里面布满了多对多关系,还能用GUID来替换INDENTIFY COLUMN 但是,多对多关系充斥的2.0时代,第三范式是第一个应该被抛弃的。必须有效的把多表联合查询降到最低。
E. 数据索引的问题
众所周知,索引是提高数据库效率查询的最方面最廉价最容易实现的方案。但是,在高UPDATE的情况下,update和delete付出的成本会高的无法想想,笔者遇到过一个情况,在更新一个聚焦索引的时候需要10分钟来完成,那么对于站点来说,这些基本上是不可忍受的。
索引和更新是一对天生的冤家,问题A,D,E这些是我们在做架构的时候不得不考虑的问题,并且也可能是花费时间最多的问题,
F. 分布式处理
对于2.0网站由于其高互动性,CDN实现的效果基本上为0,内容是实时更新的,我们常规的处理。为了保证各地的访问速度,我们就需要面对一个绝大的问题,就是如何有效的实现数据同步和更新,实现各地服务器的实时通讯有是一个不得不需要考虑的问题。
G. Ajax的利弊分析
成也AJAX,败也AJAX,AJAX成为了主流趋势,突然发现基于XMLHTTP的post和get是如此的容易。客户端get或者post到服务器数据,服务器接到数据请求之后返回来,这是一个很正常的AJAX请求。但是在AJAX处理的时候,如果我们使用一个抓包工具的话,对数据返回和处理是一目了然。对于一些计算量大的AJAX请求的话,我们可以构造一个发包机,很容易就可以把一个webserver干掉。
H. 数据安全性的分析
对于HTTP协议来说,数据包都是明文传输的,也许我们可以说我们可以用加密啊,但是对于G问题来说的话,加密的过程就可能是明文了(比如我们知道的QQ,可以很容易的判断他的加密,并有效的写一个跟他一样的加密和解密方法出来的)。当你站点流量不是很大的时候没有人会在乎你,但是当你流量上来之后,那么所谓的外挂,所谓的群发就会接踵而来(从qq一开始的群发可见端倪)。也许我们可以很的意的说,我们可以采用更高级别的判断甚至HTTPS来实现,注意,当你做这些处理的时候付出的将是海量的database,io以及CPU的成本。对于一些群发,基本上是不可能的。笔者已经可以实现对于百度空间和qq空间的群发了。大家愿意试试,实际上并不是很难。
I. 数据同步和集群的处理的问题
当我们的一台databaseserver不堪重负的时候,这个时候我们就需要做基于数据库的负载和集群了。而这个时候可能是最让人困扰的的问题了,数据基于网络传输根据数据库的设计的不同,数据延迟是很可怕的问题,也是不可避免的问题,这样的话,我们就需要通过另外的手段来保证在这延迟的几秒或者更长的几分钟时间内,实现有效的交互。比如数据散列,分割,内容处理等等问题
K.数据共享的渠道以及OPENAPI趋势
Openapi已经成为一个不可避免的趋势,从google,facebook,myspace到海内校内,都在考虑这个问题,它可以更有效的留住用户并激发用户的更多的兴趣以及让更多的人帮助你做最有效的开发。这个时候一个有效的数据共享平台,数据开放平台就成为必不可少的途径了,而在开放的接口的情况保证数据的安全性和性能,又是一个我们必须要认真思考的问题了。
当然还有更多需要考虑的问题,我这里就写一个最需要考虑的问题,欢迎补充。
下一篇文章将针对问题A,提出具体的解决方案和思路
待续(www.admin5.com 和www.crazycoder.cn同步发布,转载请注明出处)
疯狂代码,大型网站架构系列之二,底层架构概论
书结上回,www.crazycoder.cn/Yuanchuang/Article10919.html
首先澄清上篇中关于几个朋友的评论。
上篇疯狂代码介绍的基于AJAX的攻击很多人提出疑问,比如不能跨域,减轻负担之类。Ajax是通过简单的GET和POST进行数据传递的,采用HTTPDEBUGGER,抓取数据,然后采用如下方案,顺便写个示例的攻击代码.比传统的webform,我们更容易构造一些,其实对于webform和ajax的处理和发包过程是一样的,ajax数据量相对小,速度也快一些。
结合SharpPcap和HttpWebRequest我们构造一个合理的正常的IP数据包过去,代码很长,我们用伪代码简单的表达一下。
request.CreateUrl(Ajax处理页面);
request.Method=”GetORPost”;
request.refere=”网页来源”;
SharpPcap.SetLinkConnection(伪造IP地址);
String content = request.GetResponseStream() 如果作为一个多线程的应用程序对对方的WEB构成批量发包的话(假如是DEDECMS),足可以把Dedecms的数据库搞垮
文入正题:
对于上回书提到要解决问题A,我们先讲解一下电信公司ADSL的布局方案。机器上没有装VISIO,我简单的用文字描述一下流程。
Adsl用户Aè输入用户名密码è远程连接到账户数据库(在天津)è账户数据库连接计费数据库并返回状è如果成功,连接PPPOE服务器,并进一步连接计费数据库è认证服务并连接。
这里没有什么特别的地方,但是和qq通讯服务是一样的,就是采用了统一的用户验证服务器,同时对于用户验证的信息数据库是只读的,我们从其中可以想到什么吗?
以上是个简单的例子,下面开始谈具体的架构策略,首先对于上篇提到的问题A,我们首先以用户数据库为例来做解释和要求。
首先做用户量估算需求,假如我们做的是学术社区,那么这个用户量不会很大,可能我们不需要考虑这个,对于用户量的级别,我们暂时把用户量级别定为三种,百万级别(M)和千万界别(S),以及亿万级别(Q),并考虑用户登录验证以及查询常用的操作,对M和S进行扩充以及了解。
众所周知,在这个情况下,对于用户数据的负载其实并非可行而不可行的问题,而是如何最大化的保证查询和更新以及各个服务器之间的数据同步。这里我们不再讲解如何优化如何索引,只介绍架构初期的方案,下面介绍的方案如果涉及全表查询,可以采用分区视图的方案,大家可以具体搜索相关资料。
对于M级别来说,现有的DBMS经过合理的布局完全可以满足需求。我们需要解决的问题的关键其实是处理IO方面的问题,处理方案相对简单一些,对数据库的FILE文件分磁盘存贮(不是分区,是不同的硬盘),根据负载量大小,我们可以适当的控制硬盘的数量和文件分区的数量。
对于S级别,上个处理方案已经不能完全满足需求了,这个时候我们需要对注册和入库的流程进行简单的修改了,解决方案是数据散列和分区视图的概念,具体概念大家去google一下,我不详细说明了。
我们常用的方案有三种。第一种是等容扩充法,在用户注册控制的基础上,保证每个库的用户容量不超过500万,超过之后入第二个库,以此类推,这个方案可以保证系统有效的扩充性,但不能保证数据被有效的索引。第二种就是共区索引方案,其实和第一种方案有异曲同工的之说但是讲第一种方案进行了合理的优化,按照用户名进行分库存贮。比如我们可以建立26的数据库,按照用户名的索引来控制用户数据入哪个库。假如用户名是CrazyCoder,那么就讲该用户名的数据存放在用户表C中,在数据存贮的时候可以很方便的根据用户名进行相应的数据查询,方案二可以有效的解决数据索引问题。方案三是一个更具模型化的方案,结合方案一和方案二,进行用户ID的编码,不是INDENTIFY Cloumn,我们用一种序列化的方案将用户名以编码的形式存贮,比如用户名是CrazyCoder,我们的编码方案就是通过算法进行数字化,将CrazyCoder按照C,R,A,….存贮为数字索引,然后进行分区存贮,数字类型的数据在数据库中可以更有效的被查询和被更新和共享,结合方案一和方案二这个就是方案三。
对于Q级别。数据量已经是足够海量了,这个时候无论用哪种方案都是一个让人头大的数据,不能简单的用查询的方案来处理了,可以参考S级别的进行处理。但这个时候我们采用的方案是根据用户活跃度的权值结合数据量进行临时数据表的存放。如果一个非意外的数据情况下,每天登录的用户量不会上千万。这个时候我们需要做的是一个简单的数据代理程序。一个临时的用户验证数据库,每天执行一次批处理,将活跃度高的用户账户提取到临时数据库中,查询的时候先查询临时库,如果没有在进行全库查询。这个根据系统的负载情况来估计阈值,不同的系统估算方案也不尽相同。
上面对于,M,S,Q三种界别进行了简单的概述,下面介绍一个在其之上更高级的一个查询方案,数据缓存服务器,我们也可以把它理解为缓冲服务器,数据做为只读来使用。
具体实现方案如下:以为涉及了海量,DBMS常规的缓存方案已经不符合我们的要求了,那么我们需要一个有效的缓存方案,这个时候处理的流程其实就是讲最常用最直接的数据直接存放在缓存服务器中,而这个缓存服务器定时从主服务器获取并更新信息。这个是一个简单的查询,我们还可以更深入的讲缓存服务器做二次缓存,也就是一次性处理输入并存放到LIST数据中,作为全局变量放到内存中进行查询,同时用HASHTABLE或者数组进行数据组索引(可以是多级),根据查询分布到各个变量中。直接从内存中读取数据。
以笔者的经验来说的话,对于ITEM数据不超过10K的来说,每个列表最佳的存放范围是0到6万之间。
这里简单的介绍了一下DBMS基本架构,里面具体细节处理的还有很多,这里只介绍个大概的纲要。有问题请给我发邮件(Heroqst # Gmail.com),请讲#替换为@
这里只是简单的介绍了一下DBMS的基本布局,下章讲具体对我们常见的多对多关系数据库进行具体配置说明。
首先介绍一下问题的大概,比如对于文章和标签,每个文章可以有多个标签,而每个标签下又会有多个文章,那么数据量将是文章数乘以标签数,这个时候如何进行处理并有效的索引,将是下章要介绍的内容。
疯狂代码,大型网站架构系列,同步发布于(http://www.admin5.com和http://www.crazycoder.cn) ,转载请注明出处。
疯狂代码,大型网站架构系列之三,多对多关系的优化设计
接上篇:http://www.crazycoder.cn/Yuanchuang/Article11064.html
疯狂代码原创发布,转载请注明出处 http://www.crazycoder.cn/
上篇以用户数据表为例介绍了基本的数据分割方案以及基本的配置方案。但是在2.0时代,这种简单的列表索引已经远远实现起来是问题的,多对多关系将是最常见的关系。现在我们针对web2.0数据中广泛存在的多对多关系进行阐述和具体行为判断,比如一个很简单的例子,在2.0时代,好友功能是最常被用到的,每个用户会有很多的好友,同时也会是很多人的好友,那么这个数据量将会是用户数的平方的级别。同样,对于文章标签,每个文章可以有多个标签,而每个标签又可以有多个文章,这又是一个几何乘积,数据量又会是个天文数字。
传统的处理方案有两种,一种是通过SEARCH的方法来实现,一种是通过另建一个索引表,存贮对应的ID以进行存贮。对于第一种方案,因为要涉及大量的LIKE查询,性能不敢恭维,第二种的情况下,数据库的行的数量也是惊人海量级别的,并且要跨表跨区查询,还要维护数据的唯一性,数据处理过程相当的复杂性能也就不言而喻了。
文入正题,下面对数据多对多关系举出来具体的解决方案,我们这里以标签和文章之间的多对多关系为例来讲解,大家可以举一反三的思考群组和用户之间,相册和被圈用户之间等等复杂的多对多关系。
首先滤清一下流程,我们以传统方案的第二种为例,在传统的数据库设计中我们是如下走的:当一篇博文发布的时候并插入标签的时候一般是三步走(也可以理解为四步,以为还要判断标签是否存在的问题),第一步插入文章数据库并获取文章的ID,第二步插入标签数据库同时查询标签是否存在,如果存在就取出标签的ID,否则的话插入新标签并取出ID,第三部,将文章的ID和标签的ID插入索引表来建立关联。如果这个时候在索引表上建立了索引的话就是灾难性的,特别是在数据量大的情况下,尽管它可以有效的提高查询速度,但是发布的速度可能就会让人无法忍受了。
我们处理的方法也是三部曲,对多对多关系进行进一步的处理。
用标签的时候,我们用的最多的就是查询标签下的文章和显示文章的标签,所以我们实现这例就成了。
第一步,抛弃索引表。
对文章做冗余字段,加一个TAG列,我们可以讲TAG的标签如下写[TagID,TagName]| [TagID,TagName]| [TagID,TagName] 同样 对于TAG表,我们做如下冗余加个Article字段,如下内容[ArticleID,Title]| [ArticleID, Title]| [ArticleID, Title],在需要增加的时候我们只要APPEND一下就可以了,至于ARTICLE的结构和TAG的结构可以参考我上一篇文章的介绍。其实根据需要还可以存贮更多。
有人会问,为什么要存贮TagName和ArticleTitle呢,其实是为了避免跨表查询和INNERJOIN查询来做的,In查询和跨表查询会造成全表遍历,所以我们在执行的时候In查询是必须要找到一个有效的替代方法的。
第二部:异步加载。
在设计模式下我们常思考的是单件模式,我们采用另类的单件模式来处理,也就是把文章和标签之间的索引作为专门的进程来做,异步的实现。
为了避免文章在发布的时候以为要检查TAG表而造成的线程拥堵,我们需要采取延迟加载的方案来做。服务器应该维护一个进程专业的对标签和文章地段的查询和索引,我们在发布文章的时候应该把标签同步这一块托管给另外的一个程序进行处理,并进行索引。
第三部:标签缓存索引:
对于频繁的判断标签去或者热门的标签我们还可以组织一套有效的索引,比如对于标签“疯狂代码”和”傲博知识库”,我们用树来把它表示出来。对于疯狂代码我们索引一个疯,其实用程序表达就是疯狂代码[0],同样傲博知识库就是傲博知识库[0]。而在数组”疯”中存贮以疯开头的标签组,以”傲”的数组中存贮以”傲”开头的标签。如果量更大的话还可以再做二级索引。
这涉及另外一个话题了就是分词,上面是一个简单的分词方案,大家在进行GOOGLE搜索的时候应该很输入它的Suggest方法吧,就是这个道理。最终讲标签有效的索引,并提取热门的作为一个全局静态变量,我们就可以绕过数据查询这一关,对第二部的单件模式又是一个进化。
以上是对多对多关系的一个简单的架构说明,肯定有人会问,如果这样做的话工作量不是太大了吗,分词处理什么的,对每个多对多关系进行处理。
OK,咱们可以进一步的把它来抽象化,我们用TableA 表示Article表,用TagbleT表示Tag表,我们可以讲字段抽象化出来,也就是一个ID,一个Tag的String 同理对于标签表也是如此。朋友们应该可以理解我的意思了。
对,就是做个代码生成器把对应的多对多关系给生成出来,这个很好写的,几个Append就可以搞定。如果想更方便的处理,那么把这个东西做成单件的模式抽象化出来,然后再违反一下原则,做成基类,其他关系继承这个基类。。。。。剩下的应该很简单了,具体实现大家思考吧。
请参照第二篇的文章进行进一步优化设计来实现更高的负载性能
下章接着讲述数据分割和散列方面的内容
疯狂代码原创发布,转载请注明出处 http://www.crazycoder.cn/
疯狂代码,大型网站架构系列之四,多对多关系的以及并发缓存的设计
接上篇,并对上篇文章进行了补充,以下是相关的链接,这是第四篇
多对多关系以及多表查询优化处理
上篇以用户数据表为例介绍了基本的数据分割方案以及基本的配置方案。但是在2.0时代,这种简单的列表索引已经远远实现起来是问题的,多对多关系将 是最常见的关系。现在我们针对web2.0数据中广泛存在的多对多关系进行阐述和具体行为判断,比如一个很简单的例子,在2.0时代,好友功能是最常被用 到的,每个用户会有很多的好友,同时也会是很多人的好友,那么这个数据量将会是用户数的平方的级别。同样,对于文章标签,每个文章可以有多个标签,而每个 标签又可以有多个文章,这又是一个几何乘积,数据量又会是个天文数字。这里不再介绍基于硬件,IO,集群方面的问题,我们以项目开发的角度来实现他
这里先介绍一个基本的施行方案,而后我们进一步的对它进行扩充以满足我们的以后的具体需求
对于多对多关系,传统的处理方案有三种,一种是通过SEARCH的方法来实现,第二一种是通过另建一个索引表,存贮对应的ID以进行存贮,第三种是通过二次归档缓冲来实现(本人不知道用什么语言来描述这种处理方法,姑且如此吧)
对于第一种方案,因为要涉 及大量的LIKE查询,性能不敢恭维,基于全文索引的方式可能解决这个问题,但是利用第三方的数据可能未必能适合我们的胃口,我们也可能没有足够的时间和精力来独立开发实现。第二种的情况下,数据库的行的数量也是惊人海量级别的,维护索引表的散列处理,并且要跨表跨区查询,还要维护数据的唯一性,数据处理过程相 当的复杂性能也就不言而喻了。
文入正题,下面以一个简单的例子解释下第三种方案,对数据多对多关系举出来具体的解决方案,我们这里以标签和文章之间的多对多关系为例来讲解,大家可以举一反三的思考群组和用户之间,相册和被圈用户之间等等复杂的多对多关系,如下方案可能不是最好的方案,但是实践证明还是综合时间和开发成本是最合理的。
首先滤清一下流程,在传统的数据库设计中我们是如下走的:当一篇博文发布的时候并插入标签的时候一般是三步走(也 可以理解为四步,以为还要判断标签是否存在的问题),第一步插入文章数据库并获取文章的ID,第二步插入标签数据库同时查询标签是否存在,如果存在就取出 标签的ID,否则的话插入新标签并取出ID,第三部,将文章的ID和标签的ID插入索引表来建立关联。如果这个时候在索引表上建立了索引的话就是灾难性 的,特别是在数据量大的情况下,尽管它可以有效的提高查询速度,但是发布的速度可能就会让人无法忍受了。
我们处理的方法也是四部曲,对多对多关系进行进一步的处理。
用标签的时候,我们用的最多的就是查询标签下的文章和显示文章的标签,所以我们实现这例就成了。
第一步,数据冗余
老生常谈的话题,对文章做冗余,加一个TAG列,我们可以讲TAG的标签如下写[TagID,TagName]| [TagID,TagName]| [TagID,TagName] 同样 对于TAG表,我们做如下冗余加个Article字段,如下内容[ArticleID,Title]| [ArticleID, Title]| [ArticleID, Title],在需要增加的时候我们只要APPEND一下就可以了,至于ARTICLE的结构和TAG的结构可以参考我上一篇文章的介绍。其实根据需要还 可以存贮更多。
有人会问,为什么要存贮TagName和ArticleTitle呢,其实是为了避免跨表查询和INNERJOIN查询来做的,In查询和跨表查询会造成全表遍历,所以我们在执行的时候In查询是必须要找到一个有效的替代方法的。关于数据冗余的问题,我们可能还会做的更变态一些,这个后面慢慢说。
第二步:异步存贮。
在设计模式下我们常思考的是单件模式,我们采用另类的单件模式思维来处理,也就是把文章和标签之间的索引作为专门的进程来做,异步的实现。
为了避免文章在发布的时候以为要检查TAG表而造成的线程拥堵,我们需要采取延迟加载的方案来做。服务器应该维护一个进程专业的对标签和文章地段的查询和索引,我们在发布文章的时候应该把标签同步这一块托管给另外的一个进程或者服务器进行处理,并进行索引。
第三步:二次索引:
对于频繁的判断标签去或者热门的标签我们还可以在内存里组织一套有效的索引,比如对于标签“疯狂代码”,我们用树来把它表示出来。对于 疯狂代码我们索引一个疯,其实用程序表达就是疯狂代码[0]。而在数组”疯”中存贮以疯开头的标签组,以”傲”的数 组中存贮以”傲”开头的标签。如果量更大的话还可以再做N级索引,将这些常用的标签对应设计内存索引,我们可以把它想象的理解为内存中的Suggest(比如google搜索时的Suggest),使用中我们可以直接拿来使用
第四步:针对跨表查询的处理
很多情况下,我们可能避免不了多表查询,或者IN,or查询,除去业务层封装的分区视图集群之外,我们还可以处理的更好,在很多情况下,我们的查询会是非常频繁非常统一的(这里的统一指热门查询),比如在SNS中常见的性别,嗜好等多条件搜索,而这些数据可能存贮在多个数据表结构中,而这样会吧不可避免的会产生全表遍历查询。
处理方法也很简单,把原来散列的垂直分割的表再合并起来,合并到另外的只读的订阅服务器上,然后做适当的结构优化和索引,剩下的大家应该明白我的意思了,虽然简单,但是这种处理方法非常适合以后服务器的横向扩充。
以上是对多对多关系和多表查询的一个简单的架构说明,肯定有人会问,如果这样做的话工作量不是太大了吗,分词处理什么的,对每个多对多关系进行处理。
OK,咱们可以进一步的把它来抽象化,我们用TableA 表示Article表,用TagbleT表示Tag表,我们可以讲字段抽象化出来,也就是一个ID,一个Tag的String 同理对于标签表也是如此。朋友们应该可以理解我的意思了。
对,就是做个代码生成器把对应的多对多关系给生成出来,这个很好写的,几个Append就可以搞定。如果想更方便的处理,那么把这个东西做成单件的模式抽象化出来,然后再违反一下原则,做成基类,其他关系继承这个基类。。。。。剩下的应该很简单了,具体实现大家思考吧。
让并发来的更猛烈些吧,高并发环境下的数据处理方案
对于高并发性质的网站,在sns特别是webgame方面应该是最容易也是最难处理的地方了,容易处理的是如果是纯粹基于数据库驱动也就是select和update的问题,而难的地方也是不是select而是update,在高并发的驱动下,update经常会超时,虽然我们可以在finally把它处理掉,让人郁闷的是,数据库连接池仍然会饱和,数据仍然会丢失….
上面的情况是非常常见的web项目失败的原因之一,在数据飞速膨胀和并发呈几何级增长的情况下,制约我们的可能是io,database本身的问题了,让我们头痛的是不管是哪种数据库,Oracle也好,mysql也好,sqlserver也好都会timeout,而且是频繁的timeout频繁的Exception。这个时候就需要我们的应用程序在处理的前期就应该考虑到的,一个好的数据缓存策略常常决定了我们的成败,而缓存策略也是web项目最难以测试和最容易出错的地方。
在大型网站架构中,最关键最核心的也是缓存策略了,介于其复杂性,这里只简单的介绍一下基于高并发数据库缓存方案,后面的将详细介绍常用的缓存策略。这个方法与其叫缓存不如叫数据缓冲,其实也是异步更新数据,根据负载情况不同,我们哪怕仅仅将数据缓冲1秒,带来的负载提升就已经非常好了。
实现原理很简单,将并发的更新首先缓存到一个应用程序池中,然后定时查询(注意这里的方案应和缓存方案具体结合,这里只介绍概要情况)。
传统的update请求处理流程是:请求—》应用程序—》更新数据库,如下图:
数据缓冲和更新部分可以在数据层里独立实现,也就是update的传递的时候首先传递缓冲池,然后定时更新,这里需要注意的数据缓冲池的还要做的另外一份工作就是全局的数据缓存,缓存数据更新到数据这段的时间间隔,我们可以理解为临时表,再提取上下文请求的即时信息的时候首先从缓冲池里读取(这里有很多技巧,比如巧妙的利用cookie,session做;临界条件判断),流程如下图所示
上面简单的介绍了一下基于数据更新缓存的处理,下篇具体详细介绍基于并发更新机制的详细缓存处理机制
疯狂代码,大型网站架构系列之五,缓存策略设计概要
上篇对疯狂代码缓存配置进行了概要的设计,可能说的有点模糊了,有几个朋友发了几个问题探讨了下,这里有必要先澄清一个问题,和常见的缓存策略不同,我们的缓存策略将重点放在更新策略而不是只读策略上。只读缓存以及共性缓存策略性质实现的难度并不大,我们要解决的是非共性缓存,并发更新缓存,可扩充性缓存,分布式缓存更新运算的问题,而对于共性的东西的话我们可以很轻松的实现,而不必做太多的运算。
试想一个问题,对于一个多用户的并发的系统,如果对每个用户都维护一份缓存策略还要保证更新的及时性以及处理的必要性来说的话,我们很难想到一个有效的处理机制来维护每份(每用户)缓存的副本的,缓存的存储性质也决定了做分布式缓存策略处理的难度和分布式通讯更新的的难度,我们也很难尝试对于一些访问量很小且少有共性的页面实现有效的缓存命中率,比如某某用户的博客。
简单的总结了一下关于缓存策略讨论的重点
A. 基于海量非共性数据的缓存策略
B. 基于数据缓存级别并发更新的缓存策略
C. 基于数据并发存储的缓存策略
D. 基于分布式的缓存策略
E.基于搜索的缓存策略
我们这里不再赘谈关于页面静态化以及类似的问题,静态化的情况非常适合在系统初期,用户的基数并不算很大的情况下实现,而在涉及集群的情况下,静态化的实现成本,IO成本,维护成本,扩充成本以及更新成本会远远的超出缓存策略的成本,当然我们也会有一套建立在缓存基础上的静态化处理方案,这些放在以后再谈。我们的目的是要建立一个可伸缩,便于维护扩展的缓存策略,下面就具体问题进行分析。
对于问题A:
常见的博客系统就是一个最好的例子,每个用户的首页都是相对个性的数据,共性的地方不多,以常见的处理方案来说的话,我们可能需要维护每个用户访问的缓存副本,而对于一些访问量极小的博客站点来说的话这种方式无疑会造成巨大的浪费。
对于大量非共性的数据缓存来说,几个处理方案:
1) 量化缓存目标并分配相应的缓存权值。(权值分级)
目的很简单,只缓存有效的数据。首先抽取活跃用户,以及高访问量用户,将数据进行分组分权制缓存(对于交友型的SNS系统来说,我们称之为美女效应)
2) 非连接持久性的缓存保持(临时的持久性)
珍惜并有效利用数据查询,将未被缓存命中时的查询或者无权值的数据持久化保存(序列化存贮静态存贮等),当缓存未被命中时优先取得持久化数据而非数据查询。可以理解为临时数据存贮,或者临时存贮于子服务器的某个位置。
3) 基于数据更新的缓存清除(一次性使用)
当持久性缓存保持失效(依赖数据发生修改),直接删除临时数据(缓存只在访问时被激活并储存,一旦修改或者失效,我们立刻抛弃)。
4)缓存更新代理规则
由另外的线程进行维护,并维护线程的有效性,最大限度的分离主程序对无效缓存以及临时持久性缓存数据的清理
对于问题B:
在小型缓存策略中,缓存处理对于整个应用程序对于每个请求来说都是唯一的,可操作的和非物理存储的。而在并发更新的过程中,一个小小的并发更新就会很现实的清空所有的缓存池,造成缓存命中率奇低而初始化率奇高而起不到缓存策略应有的作用。
在这种情况下,处理方案也和A.4中提到的方案是一样的,由独立的缓存更新进程来处理,对于应用程序中所有涉及缓存更新的请求由专门的更新代理来执行。这个处理方案相对简单,不再赘述。
对于问题C:
上篇已经提到关于并发数据更新会带来的问题也就是数据库的I/O响应,超时,死锁,以及线程的阻塞问题。我们用一个写入缓存来处理这个方案,其实这个并非传统意义上的读缓存,姑且命名为写缓存吧,我们可以形象的理解为类似硬盘缓冲区的问题。这里处理的操作稍微有点多了,还要涉及只读缓存的更新的问题了。
根据系统的不同,我们需要分析处理的角度也不同,我们以常见的webgame为例来简单介绍一下处理机制,这里有两种常见的情况
1) 对于webgame的最终用户玩家来说,每个在线用户的数据是非共性的(问题A),而在一个战斗场景下,每组数据时刻都在变化之中,如果我们对数据的变化采用数据库日志记录的形式保存的情况显然对Database的压力很大,而我们需要记录的仅仅是战斗的结果,战斗的过程我们完全没有必要进行保存,这个时候我们就用写入缓存来执行相应的数据操作。这个处理很简单,用服务器变量的形式就能解决他。
2) 对于webgame的服务器角色来说,如果战斗场景的用户量非常多,而数据更新非常大的情况下,我们采用方法1中的处理也可能力不从心,这个时候我们可以将缓存来进一步的抽象,在某个时间段内(比如3分钟),维护一个唯一的缓存对象,所有的数据操作都在这个时间段来被缓存进程来记录,来更新。而由另外的一个进程来进行异步的定时的数据保存操作。
对于问题D
这个是比较常见的分布式缓存服务器组了,而对缓存服务器来说其实要解决的问题就是服务器间之间互相通讯的问题,并保证数据一致性的问题。那么我们的有四个处理规则:
1) 数据缓存应该被有效的分组并索引
目标是实现数据耦合的程度降到最低,甚至没有耦合。比如以用户ID为分割的数据缓存分布,或者以文章分类为分割的缓存分布
2) 数据缓存应该被有效的更新
如果数据被有效的分组完成后,这个就是问题C.2的方案了,和C.2不同的是,因为缓存组可能未必在一组服务器中,可能涉及缓存和DATABASE数据通讯延迟的问题。这个时候要保证缓存服务器被即时的传递到databse,那么需要另外的一个缓存检测进程来完成这项工作(数据完整性检查,并备份两个缓存段的数据)
3) 缓存服务器间的数据完整性
对于无法分组的数据,比如时间段内的用户认证数据和资料数据,我们需要保证两组数据同步,最好的处理方法就是清除相应的缓存段,让它在下次使用的时候初始化
4) 缓存服务器间的连通性
这个取决于物理线路,如果缓存服务器在天南地北的话,我们还需要一个队列进程来进行同步和数据矫正,我们称之为缓存路由。
对于问题E
在分布式缓存的情况下,多条件搜索往往涉及多个缓存服务器,处理起来笔者尚未有一套完善的出来方案。笔者用的是敷衍原则和集成原则了
敷衍原则:
对于搜索型的数据来说,很多情况下并不是非常重要,我们的搜索结果完全可以晚一会提供给用户,允许搜索的数据有10分钟或者更长时间的延迟。
集成原则
将搜索字段和表整合出来,用独立的只读查询服务器来分担负荷
如果您有比较好的方案,不妨mail:heroqst@gmail.com ,和疯狂代码探讨下。
本文到这里简单的介绍了几种缓存处理的方案,仅供参考。下篇将结合本文的缓存策略探讨web 2.0下的数据规划原则