1.解决方案概述
1.1 假设条件: 考虑本论坛的快速发展, 按最理想情况, 可能三年内总主题, 会到接近千万水平. 为保证架构的扩展性, 本项目假设主题数为最大值的十倍, 作为架构设计参考数据量. 假设为几千万贴子
1.2 声明:本架构文档只作样例演示用处,故本论坛架构文档只对以下对象做架构设计:版块、帖子、回复、用户及权限。
2.总体架构
Figure 1 总体架构图
系统包含三个层次功能,来满足最终用户、管理员和系统管理员的日常操作需求。说明如下:
应用层是基于提供给用户使用的功能进行划分的;
模块层是基于开发模块划分的;
系统层是划分是面向服务的(SOA)。
3.组件架构
Figure 2 组件架构图
3.1 Model为数据源的基本单位,系统所有的操作都是围绕Model实例对象进行的。
3.2 数据源,即数据层,是为逻辑层提供数据的一系列组件封装。它由五个组件组成,其中SQLBaseDAL为数据库访问统一接口组件,CachingBaseDAL为缓存memcached访问统一接口组件,XMLBaseDAL为XML文件访问统一接口组件,FormBaseDAL为表单对象化组件,CookieBaseDAL为界面cookie对象化组件。
//Example 1 XMLBaseDAL使用例子 public Topic GetTopic(int tid) { //得到XML存放目录 string xmlFolder = GetXmlFolder(tid); //生成XML文件名 string xmlFileName = string.Format("{0}.xml", tid); Topic mTopic = null; BaseXmlDAL<Topic> xmlDAL = new BaseXMLDAL<Topic> { FileName = xmlFileName, FilePath = xmlFileName, //XML文件失效时间为一天 TimeOut = 60*24 } BaseXmlModel<Topic> xmlModel = xmlDAL.GetXmlModel(); if(xmlModel!=null) { mTopic = xmlModel.Model; } else { //从数据库得到帖子对象 mTopic = GetTopicFromDB(tid);
//保存XML文件 xmlDAL.XmlModel = new BaseXmlModel<Topic>(); xmlDAL.XmlModel.Model = mToic; xmlDAL.SaveModel(); }
return mTopic; } |
//Example 2 FormBaseDAL使用例子 class FormData { //操作类型 public string action = ""; //版块编号 public int fid; //标题 public string title = ""; //内容 public string mainbody = ""; } FormData formdata = new BaseFormDAL<FormData>().GetModel(); protected override void OnInit(EventArgs e) { base.OnInit(e); //显示表单提交过来的title Response.Write(formdata.title); } |
3.3 Business为系统的逻辑层,数据处理规则都在这个层里。
3.4 Utility里包含一些常用的小功能,如IP字符串转整型方法,邮件发送类,文件操作类等。
3.5 Json是一个第三方类库,实现.net对象与javascript对象的转换。
3.6 Search WCF 是面向服务的搜索引擎。包括建立索引组件与查询组件。
3.7 Passport WCF 是面向服务的通行证。因为通行证的member模型不属于本系统模型,所以Model并不包含在本系统里。
3.8 WebUI 即是界面层,包括html界面,webservice及rewrite规则。
4.网站访问流程
Figure 3 网站访问流程架构图
5.技术要点
5.1 Squid Cache 服务器
基于两点选择Squid:它是优秀的透明代理服务器,可以实现分布式的WebServer群,均衡负载;它可以实现页面级的缓存。Squid 访问哪台webserver规则暂时定为随机。
Figure 4 Squid架构图
5.2 大数据量处理
基本原则是, 按ID值范围, 先分库, 再分区
分库:千万级的主题数和回复, 放在同一个库里, 不论如何处理, 查询都会慢, 且放在同一数据库里, 查询请求会很多, 单服务器很难负荷. 如下图所示, 本项目以TopicsID为标识, 按500万范围进行分段, 将2000万主题贴和回复, 分放到四台服务器上, 以降低单服务器负载, 查询时, 根据TopicID范围, 分别到不同的服务器上查询
Figure 5 分库架构图
分区:经过分库处理后, Topics表单表记录数仍然接近500万, Post表记录数近千万, 查询时仍然会较慢, 必须进行分区处理, 以降低单分区记录数, 提高查询效率
Topics: 由于对本表的主要查询语句为select top 10 * from Topics where pid>=210000000 and pid<220000000 order by createon desc等类似语句, 因此以pid作为分区字段, 将Topics表分为多个区, 以保证每个分区记录数低于百万,主题表分区情况
Figure 6 帖子分区图
将目前157万主题分为4个区, 分区1为pid值从210000000至230000000, 记录数为332116, 分区后, select top 10 * from Topics where pid>=210000000 and pid<220000000 这种查询语句, 只会到会区1去查, 大大缩小查询范围, 提高大数据量查询性能. 按以上原则, 对四台服务器的Topics表进行分区
Posts: 对posts表的主要查询句为select * from Posts where TopicID=3028392等形式, 因此以TopicID作为分区字段, 将Posts表分为多个区, 基本原则是尽量保证单个分区记录数不超过百万.
Figure 7 回复分区图
将目前345万记录, 分为五个区, 分区1为TopicID值从1到900000的所有回复, 从上图可看出, 记录数最多的为分区3, 约135万记录. 按以上分区后, 单分区记录数都不会太大, 当数据量进一步增加时, 查询性能不会明显下降. 按以上原则, 分别对四台服务器的Posts表进行分区。
5.3 缓存服务器Memcached
Webserver都将数据保存到数据库中,Webserver从中读取数据并在浏览器中显示。但随着数据量的增大、访问的增多并集中,就会出现数据库的负担加重、数据库响应恶化、网站显示延迟等重大影响。各大网站都采取增加专门的缓存服务器解决此问题,而缓存服务器又以memcached较为出色。其特征如下:
协议简单:memcached的服务器与客户端的通信是使用简单的基于文本行的协议,因此对客户端的要求不高,任何平台,任何语言都可以开发客户端。
基于libevent的事件处理:libevent是个将linux的epoll、BSD类操作系统的kqueue等事件处理功能封装成统一的接口的程序库,对多连接操作能发挥很好的性能。
内置内存存储方式:为了提高性能,memcached把数据都存储在内置的内存存储空间的,并没有保存在其它介质中,当内容容量达到指定值之后,就基于LRE(Least Recently Used)算法自动删除不使用的缓存,并将清空的内存分配给新的缓存内容。
互不通信的分布式:各个memcached不会互相通信以共享信息,这样做的好处有两个,一是减少相互通信所消耗的性能,二是部署跟扩展简单。它们是整合是依赖于分布式算法来统一的,即对key进行哈希(HASH)计算。
Figure 8 Memcached架构图
5.4 XML文件服务器
XML文件服务器其实为文件缓存,将对象序列化后,以文件的形式保存在硬盘系统里,当要访问这个对象的时候,再从硬盘里拿出此XML,反序列化对对象。此操作由WebServer执行。由于Webserver是分布式的群,所以XML文件服务器以共享文件夹的形式提供给Webserver访问,如//FileServerName/share/topic。Xml统一调用接口的使用请看代码Example 1
5.5 图片服务器分离
对于Web服务器来说,图片是最消耗资源的,于是有必要将图片与页面进行分离,这样的架构可以降低提供页面访问请求的服务器系统压力,并且可以保证系统不会因为图片问题而崩溃,在应用服务器和图片服务器上,可以进行不同的配置优化,比如web服务器在配置ContentType的时候可以尽量少支持,保证更高的系统消耗和执行效率,甚至可以使用squid做图片缓存。
5.6 Ajax
Prototype.js 它是一套优秀的js封装类库,实现了常用的js操作函数,支持各类浏览器,如IE,Firefox,使用案例网易的论坛bbs.163.com。
Json是javascript里的一个子集,实现面向对象的数据模型及跨平台的数据通讯
Figure 9 Json通讯架构图
Javascript MVC是一种架构方法论,它将一个应用执行分成三个部件:模型,视图,控制器。
- 模型负责按指定逻辑维护状态;
- 视图为用户提供界面接口;
- 控制器负责把模型加载到视图事件;
//Example 3 javascript MVC例子 <!---mvc.html--->
<script type="text/javascript"> window.οnlοad=function(){ //Controller document.getElementById("btnSubmit").onclick = DoSubmit; }
//Model function DoSubmit(){ //Do something alert('Succuess'); } </script> <!---View---> <input type="button" id="btnSubmit" value="Sumbit" /> |