疯狂代码,大型网站架构系列之四,多对多关系的以及并发缓存的设计

 
 
多对多关系以及多表查询优化处理
 
上篇以用户数据表为例介绍了基本的数据分割方案以及基本的配置方案。但是在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做;临界条件判断),流程如下图所示

 

上面简单的介绍了一下基于数据更新缓存的处理,下篇具体详细介绍基于并发更新机制的详细缓存处理机制
 
 
 

 

 

Tags:  大型网站架构 系统架构 网站架构 网站架构师 疯狂代码 DBMS 数据库设计 海量数据库 多对多

延伸阅读

最新评论

  1. hgjg
  2. 请问站长这个网站是用什么CMS建的?
  3. 这个网站是自己写的,很简单的一个东东
  4. 下篇何时出来?

发表评论