有关架构的概念和其重要性此处就不再详细讨论了,在很多社区和书籍中都有介绍过。在这里推荐两本书,分别是《企业应用架构模式》和《Microsoft.NET企业级应用架构设计》,其中,第二本适合.NET开发人员来看。另外,选择不同的网站 后台语言就意味着不同的架构路线和不同的开发框架,我们使用的开发语言和相关软件技术,已经在第二章中有过介绍。
互联网项目(门户、社区、电商等)在初期架构阶段,首先,要分清楚项目所针对的人群有哪些,并根据需求分析和上线后的推广力度来估算有多大的访问量;然后, 负责架构的人员根据这些资料设计架构粒度。现在投资互联网项目的成本都很大,已经不像几年前买个虚拟主机就可以搞定了。所以,前期一般不会把架构搭的很 大,或者买很多服务器来支撑这个架构。但是我们可以根据需求设计一套可灵活扩展的架构,然后根据项目的市场发展状况来扩展架构,两位圈内名人曾讲过这样的话:
- 高德纳:过早优化是万恶之源
- 冯大辉:好的架构和最初设计有关系,但最重要是发展中的演化。
本章分别从“物理架构”和“逻辑架构”两方面来描述目前项目的架构设计。
1.1 物理架构
我理解的物理架构也可以称之为宏观架构,主要包括硬件与网络的部署策略、整体架构硬件的可伸缩性、安全以及性能上分析。下图是目前项目的逻辑架构视图,此图虽没有把相关扩展点画出来,但是会给大家介绍哪些是可以在后期进行扩展。
1.1.1 客户端缓存
关于客户端缓存应该属于网站优化方面的知识,随着互联网技术的发展和客户端浏览器的支持,有效的利用客户端缓存可以为服务器减少很大的压力。客户端浏览器根据每个HTTP请求的Header信息来决定当前请求的资源是否需要缓存,目前项目的客户端缓存分为“动态页面”和“静态资源”两种:
- 动态页面:由ASP.NET MVC输出,根据需求为每个所输出的页面设计缓存策略,比如:网站总首页会在客户端“相对”缓存5分钟,而文章会缓存10分钟。如果服务器端对缓存页面内容更新后,可以通过改变“ETag”值来强制更新客户端缓存。
- 静态资源:包括“脚本文件、样式文件、网站图片、静态页面(404、专题等)”,目前这些文件都存储在“静态资源服务器”,可以通过Web服务器的批量设置调整资源文件的客户端缓存,不同的Web服务器需要不同的配置,大家可以根据需求去Google。
有关“静态资源”在客户端缓存的强制更新,可以通过版本号的形式来强制更新客户端缓存,比如:
1.1.2 防火墙
硬件防火墙的主要功能可以参考百度百科,这里介绍此项目正在使用的几个主要功能:
- 内网端口统一屏蔽:防火墙对外网服务器(IP)只开放80端口(目前需求),虽然操作系统本身也具备防火墙功能,但是配置比较麻烦,又不统一。
- 外网和内网IP的映射:在防火墙内配置了外网和内网(公网千兆交换机)的IP规则对应,浏览者通过外网访问会直接转换为内网IP进行访问。
- VPN:如果需要通过外网管理内容区网站或者远程管理服务器,就必须通过VPN进行连接,连接成功后会自动分配内网地址,然后才能对服务器进行管理。
注:在购买防火墙时,要根据需求查看防火墙所支持多大的“吞吐量”,因为它是测量防火墙性能的重要指标。
1.1.3 公网千兆交换机和内网存储交换机
公网千兆交换机
当外网请求过来时,防火墙会把其转换为内网请求,通过公网千兆交换机访问具体内网服务器,根据IDC提供的带宽,这里配置“千兆”已足够。
内网存储交换机
目前所有服务器都会有一块网卡接着内网存储交换机(万兆),服务器上的所有数据(除系统文件和程序文件)都是通过“iSCSI”协议存储到“存储设备”上的。
1.1.4 Web服务器
一个网站最重要的就是Web服务器,因为它会把数据转换为页面(HTML)返回给浏览者,这种说法仅限于目前的环境。在SNS区,Web服务器后面是由多台应用程序服务器组成。为了减少成本,现在项目只有一台Web服务器,但是此台服务器上运行了多个站点和服务,后期可以根据访问量,把这些站点和服务扩展到 另外的服务器上。此台服务器的系统环境和所运行的服务如下:
- 操作系统:Windows Server 2008R2 x64
- Web服务器:IIS 7、.NET Framework 4、ASP.NET MVC 3+Razor
- 运行站点:内容区网站、内容区百科、内容区搜索服务
为了方便扩展,不同的站点都由不同的域名划分,同时也运行在不同的应用程序池当中。需要注意的是,Web服务器不会存储任何有关共享资源和用户的相关数据,所有资源都是通过绝对路径访问其它服务器上的内容,这样就方便以后为某个站点或服务增加负载均衡。有关负载均衡可以通过软件(Nginx)、硬件(F5) 或DNS轮循等几种方案来实现,因为硬件比较贵,所以我们在内部针对Nginx做过测试,在分离以上站点时,可以正常运行。很多大型站点是采用“混合型负载均衡”——以上几种方案都会使用。如果考虑到今后会使用负载均衡,在初期架构时,就要首先解决所有和状态有关的问题,比如用户登陆和验证码状态,在我们 SNS区架构时,就要考虑采用专门的状态服务器来存储这些内容。
301跳转:很多网站都会申请一些保护域名,并把这些域名转向到主域名上,我们的解决方案是在IIS上建立多个空站点,然后设置所有请求都转向到主域名站点上。
下面分享一些关于IIS7配置方面的资料:
- 错误页设置:如果采用“集成模式”,一定要把网站错误地址写成绝对地址,比如(http://stat01.xxx.com/404.html),如果网站启动过程发生错误,这时就会导致错误死循环。
- 启用动态压缩(GZIP):http://www.cnblogs.com/ntwo/archive/2011/01/10/1932081.html
- 多个进程处理一个应用程序池设置:WebGardens 特别功效:降低CPU占用、Web Farm和Web Garden的区别?
- IIS默认连接数设置:让Windows Server 2008 + IIS 7+ ASP.NET 支持10万个同时请求
- 权限设置:http://www.cnblogs.com/limshirley/archive/2011/05/17/2049039.html
- IIS7教程:http://msdnwebcast.net/webcast/8/1970/
1.1.5 缓存服务器
当网站没有应用缓存时,每一个动态页面的访问请求,都需要通过连接数据库,执行相关数据查询,最后返回给客户端。这样无疑增加了服务器的压力,并且数据库连接是相对耗时的操作。在服务器端可以通地以下方式实现缓存:
- Web服务器:不同Web服务器的缓存设置方法不一样,Nginx和Squid都可以通过配置文件来设置资源的缓存,IIS也可以同时开启“静态资源”和“动态内容”的缓存(GZIP)。
- ASP.NET MVC:可以通过Action的OutputCache属性来设置缓存的类型和时间。
- 自定义缓存:根据网站自身的业务逻辑来使用缓存,把一些业务数据存储在缓存当中,可以使用ASP.NET中的“System.Web.Caching.Cache”来管理缓存,或者使用第三方缓存“Memcache、Redis”。
缓存模块设计
在设计缓存模块时,我们定义一系列的缓存操作接口,使用者(Web服务器)并不清楚缓存模块当前所采用的是哪种缓存方案,使用者只需要调用具体的方法来管理缓存。缓存模块在初始化时会根据配置文件来决定当前系统需要使用哪种缓存方案,目前系统支持ASP.NET Cache、Memcache、Redis三种缓存策略,三种缓存策略在不同的部署环境都有各自的优势。
- ASP.NET Cache:网站初步上线时,当没有单独的缓存服务器,并且访问量和缓存数据量都不是太大时,就可以采用ASP.NETCache缓存策略,因为它相对于 后两种是最快的。缺点是它和应用程序池在一个进程当中,网站重启或应用程序池崩溃都会导致缓存直接丢失。
- Memcache:弥补了第一种的缺点,支持分布式,很多大型网站都在使用。缺点是,如果在访问量高峰期出现了缓存雪崩,就会导致缓存大量失效,同时数据库服务器将承受巨大的压力,严重的话会导致整个网络集群的瘫痪。
- Redis:可以用来做缓存服务器,支持数据持久化,把缓存中的数据定期存储到磁盘上,目前,国内新浪微博正在使用此技术,我们只是尝试使 用了它的缓存功能,并没有它的NoSQL特性。
缓存策略
缓存是一个好东西,但是如果我们错误的使用了它,就会带来意想不到的麻烦。在实践当中,我总结了以下常见的注意事项:
- 缓存键的设计:一般缓存键都是以常量的形式存储在类中,这样就会方便调用,并且缓存键常量是采用模板形式,灵活度比较高。如果要设计更为灵活的缓存键,可以参考Discuz!NT的设计。
- 缓存对象粒度:缓存粒度的原则当然是越小越好,但是这样就会增加组装环节。根据业务逻辑灵活控制缓存对象的粒度,减少缓存中的数据冗余。
- 缓存管理:网站启动时,会把必要的初始化数据放入缓存中,其它的数据是在第一次访问时存储到缓存当中,在网站后台会有缓存的主动更新和强制更新功能。
1.1.6 数据库服务器
目前网站的数据存储在一台数据库服务器上,采用 “SQLServer 2008 Enterprise Edition (64-bit)”数据库。考虑到后期的扩展,前期规划时需要完成以下工作:
- 模块分库:根据内容区的产品模块,进行合理的分库,方便以后根据需求把这些数据库部署到不同的数据库服务器上。
- 读写分离:当数据库访问量很大时,可以把数据库部署到不同的数据库服务器上来解决压力,但是当某个模块数据库服务器压力过大时,可以通过读写分离来解决数 据库服务器的压力,SQL Server可以通过发布订阅来解决这个问题,但是网站程序的数据库访问层也需要提供支持,把不同的读写业务逻辑分发到不同的数据库服务器上。第一种方法是在读写业务逻辑中调用不同数据库连接字符串。另外一种方法,前期规范好存储过程和SQL语句的书写标准,在数据库访问组件中根据最终执行的命令,分发到 具体的数据库服务器,比如所有“select”都分发到“读”数据库服务器,而所有“Update、Delete”都分发到“写”数据库服务器上。随着业务的增加,如果存在多台“读”服务器时,就需要考虑数据同步延时的问题,可以根据具体情况来解决,比如:当用户刚添加一条评论时,在客户端增加一个标记, 在几分钟内他的所有读操作都分发到“写”数据库上。
- 负载均衡:如果存在多台“读”数据库服务器时,需要在数据库访问组件上增加负载均衡的功能,目前项目只是简单实现了“轮循算法”,也可以在算法中为每个数据库器连接字符串增加“权重”值。
安全设置
在开放的互联网环境中,互联网项目第一个要谈的就是安全问题,根据项目的不同,安全的侧重点也不一样。在保证操作系统本身的权限划分和漏洞升级后,在“第六章、内部测试”会讨论网站程序所需要的安全测试。在保证网站程序没有安全问题外,还要保证数据库服务器的访问安全性,我们分别从以下几个方面来确保数据库 服务器(SQL Server)的安全性。
- 隔离原则:在防火墙内隔离数据库服务器,禁止外部链接,并在数据库服务器操作系统防火墙中设置只允许网站程序服务器连接,修改默认连接端口。
- 权限分配:首先关闭没有用的数据库账户,对数据库文件所在的文件夹设置操作系统级别的权限分配。针对不同的网站程序,分配不同的数据库访问账号,并根据不同的业务逻辑为不同的账号分配不同的执行权限。比如:网站前台程序所拥有的数据库账号只有执行“特定存储过程”和“特定表”的查询操作,并不具备创建、删除表的操作,更没有查看数据库有多少表的操作。
- 数据加密:第一、对网站程序的配置文件进行加密,防止Web服务器被攻破后拿到明文的数据库连接账号。第二、对必要的“数据列”进行加密,不做明文存储, 比如会员表的密码字段,并且不能用简单的加密方式进行加密,防止碰撞破解,保存好加密干扰字符,并定期更新干扰字符。
- 最小化运行:只开启网站程序业务所需要的SQLServer服务。
备份机制
SQL Server已经具备了很强的备份功能,可以设置作业进行定时备份,根据需求选择具体备份策略,具体操作可以参考这篇文章“SQL Server 维护计划实现数据库备份”。
数据镜像和同步
当网站“主数据库服务器”发生问题后,网站程序需要自动切换到另外一台正常工作的数据库服务器,这时可以采用主从数据库镜像的方案,网站程序的数据库访问组 件只需要配置主数据库的连接字符串,就可以做到主从镜像自动切换,具体配置方法可考MSDN或者“SQL Server 2005 镜像构建手册”。如果只是需要在主数据库服务器发生问题后,手动切换或者开启另一台工作的数据库服务器,可以采用数据库复制的方案,具体配置方法参考“通过SQL Server 2008数据库复制实现数据库同步备份”。
1.1.7 静态资源服务器
前端优化中最好做的就是动静分离,在前期项目规划时就应该在内部搭建动静分离的开发环境,在项目中采用绝对路径和前端进行协作开发。如果前期只有一台服务器,可以在服务器上建立多个站点存放静态资源。静态资源服务器可以采用的CentOS系统,使用高性能的Nginx作为Web服务器。
缓存和压缩
调整Nginx的配置,为静态资源增加客户缓存输出,并且可以把静态资源存入到Nginx内存当中,当然也可以使用Squid来做。关于静态资源的压缩,推 荐使用“UI Compressor、Google Closure Compiler)”。如果有更高的区域性需求,可以使用第三方 CDN来做。
1.1.8 图片浏览服务器
独立图片浏览服务器原因同上,图片浏览对于内容型网站来说是很重要的功能,在前期规划时需要注意以下事项:
- 图片上传:后台程序会把编辑上传的图片通过内网发送给图片上传服务,图片上传服务根据后台上传需求进行上传并保存图片,返回上传成功后的图片路径和文件名。图片上传服务具备如下功能(缩略图、水印、版权、保存Exif信息到数据库)。
- 目录规划:根据需求设计图片的存储机制(路径、文件名)以及原图片的备份,对后期的扩展很重要。
- 数据分析:图片上传成功后,会把图片的所有Exif信息存储到数据库中,方便后期进行数据分析。
- 图片访问:对图片的访问增加防盗链功能,采用“HttpHandler”来实现,目前为了良好的体验,考虑到图片都是编辑上传的,不会造成大量的恶意引用,此功能暂没有开启。
1.1.9 内容区后台管理服务器
基于安全和程序架构的原因,内容区前台和后台项目是分开部署的,并且采用不同的开发框架。内容区后台是单独部署在一台服务器上,管理员需要通过VPN才能登陆管理后台,并且后台采用HTTPS登陆。
1.1.10 数据存储设备
以上所有服务器的数据存储都分为两种:
- 原始数据:操作系统和应用程序所需要的文件是存储在本地硬盘上的,应用程序主要包括杀毒软件、运行环境和软件工具。
- 用户数据:网站程序所产生的数据和文件都是通过“iSCSI”协议存储在内部存储设备上的,后期根据发展会购买专门的存储设备,采用“FC HBA卡”进行数据传输。
单独的存储设备是防止服务器系统或者服务器硬件坏掉时,不需要单独从硬盘中把数据复制出来的麻烦,只需要启动一台新服务器连接到内部存储设备就可以正常运行了。
1.1.11 其它
远程管理卡
主要方便“运维”通过远程监控服务器(开机、关机)的重启过程,当然也可以远程管理操作系统。
管理工作站
一台远程管理服务器,安装 “VMware”系统,虚拟化了多个操作系统,方便安装相关监控和日志分析软件。
摄像头
监控机柜在IDC机房的动静。
1.2 逻辑架构
逻辑架构从某种程度来看是物理架构的实现,但不仅限于此。它需要考虑功能的重用性与可扩展性、服务接口的定义,并定义整个系统的架构风格。
在分解复杂的软件系统时,软件设计者用得最多的技术之一就是分层。当用分层的观点来考虑系统时,可以将各个子系统想像成按照“多层蛋糕”的形式来组织,每一 层都依托在其下层之上。在这种组织方式下,上层使用了下层定义的各种服务,而下层对上层一无所知。另外,每一层对自己的上层隐藏其下层的细节。——《企业应用架构模式》
一般软件系统都是有“表现层”、“领域层”、“数据源层”组成,当然我们的系统也是:
1.2.1 基础设施和公共类库
公共类库
统一公共类库的目的是为了方便团队使用一致的“公用方法”,所有能提练出来并与业务逻辑无有关系的“公用方法”。公共类库与业务逻辑无关,公司的所有开发小组都可以使用,并长期对它进行维护。在开发公共类库时注意以下几点:
- 不求多而全,但要精而强,每个方法要求有详细注释和具体使用方法(测试用例)。内部要提供API清单,方便新人查看有哪些方法,免的重新制造轮子。
- 避免从网上直接下载使用,必须经过代码审查和测试。
- 规范类库和方法的命名规范,方便开发人员记忆和使用。
目前项目的公共类库包括:工具函数库、Web请求处理库、XML操作库、IO操作库、脚本输出辅助库、常用算法库(加密、过滤等)。
系统日志
在第三章的功能介绍中,已经详细介绍了日志系统的功能和作用,系统日志项目的具体设计如下:
- ILog:日志记录操作的接口。
- FileLog/XmlLog:具体日志记录操作的实现,目前支持“文本文件”和“XML文件”。
- LogInfo:日志信息和日志级别。
- LogInfo:日志系统的业务逻辑,常用日志记录方法。
系统配置
访问配置文件的接口,详细功能在第三章有介绍。
数据缓存
上一节介绍了缓存的具体设计,数据缓存项目的具体设计如下:
- ICacheHelper:数据缓存操作的接口。
- CacheStrategy:具体数据缓存操作的实现,目前支持“ASP.NET Cache、Memcache、Redis”。
- CacheHelper:数据缓存的业务逻辑,每个系统的具体缓存业务逻辑都存储在各项目中,方便数据缓存项目的复用。
- CacheKeys:公共缓存键。
1.2.2 表现层
内容区前台(ASP.NET MVC)
ASP.NET WebForm的缺点在网上也都有讨论,目前很多采用ASP.NET的互联网项目都是自己来实现“HttpHandler”做项目,不再使用ASP.NET自带的页面模型。微软也已经意识到了这个问题,这才有了ASP.NET MVC框架,ASP.NET MVC主要取代项目中的表现层,下面分享我们用它都做了什么:
- Razor:ASP.NET MVC支持自定义视图引擎,自带了Razor,上手快并且有很好的智能感应输入提示支持。
- ASP.NET Routing:在之前的ASP.NET项目中,大家都采用第三方URL重写组件来实现更好的URL设计和SEO(盲目)优化,ASP.NET MVC中已经解决了这个问题,当然在ASP.NET 4.0 WebForms中也可以使用。具体讨论可参考“ASP.NETRouting相关”
- XmlRoute:ASP.NET MVC默认路由规则是存储在“Global.asax.cs”中,也可以通过扩展把规则存储在XML中,具体实现参考“ASP.NET MVC Routing Using XMLCustom Configuration Settings”。
- Ajax:在ASP.NET MVC中,Action可以返回多种丰富类型的结果,这样就不需要在传统ASP.NET中实现“HttpHandler”或创建“ashx”,也更方便我们来开发API。
- 重写输出:为了更好的控制页面缓存和客户端缓存,我们重写了ASP.NET MVC默认请求和输出,在请求时根据当请求的路由判断是否已经缓存并输出,输出时根据客户缓存策略输出对应的HTTP Hearder。
内容区后台(ASP.NET WebForm)
ASP.NETWebForm自有它的缺点,当然也有它存在的优点,合理的使用可以有效的提高开发效率和代码重用。
- Code-Behind:使用ASP.NET WebForm的页面模型和服务器端控件(自定义),可以快速开发应用系统,当然也会带来性能上的瓶颈,内容区后台只有内部人员使用,性能问题暂不考虑。
- 自定义服务器端控件:我们没有直接使用ASP.NETWebForm自带的控件,而是根据“效果图”和“前端交互”封装了所有控件,把常用的组件也封装为自定义控件,比如:图片上传控件、分类选择控件、超文本编辑器控件、数据显示控件(Repeater)等。
- ASP.NET Ajax:后台开发并没有专业的前端人员来编写Javascript实现Ajax效果,所以采用ASP.NET Ajax更适合我们在ASP.NET WebForm下开发。
1.2.3 服务层
处理领域逻辑的常见方法是将领域层再细分成两层。服务层独立出来,置于底层的领域模型或表模块之上。通常只有使用领域模型或表模块时才会这样细分,因为仅使用事务脚本的领域层并不复杂,没有必要再单独设服务层。表现逻辑与领域层的交互完全通过服务层,就好像应用程序的API一样。——《企业应用架构模式》
从逻辑架构视图上可以很清楚看到,内容区前台和后台的服务层架构也是不同的,前台的服务层主要有“ViewModels”和“Business logic”组成,而后台只有“Business logic”,后台很多页面逻辑已经在“Code-behind”中完成了。
在“第五章迭代开发”会分享服务层的一些最佳实践。
1.2.4 数据访问层
数据库访问层是完成服务层的具体实现,执行相关的存储过程或脚本命令,并以表模块或者对象(Data Mappings)的形式返回给服务层。如果项目有“读写分离”需求,就需要根据具体的业务逻辑在数据访问层把具体的操作路由到具体的数据库服务器,可以 通过调用数据库访问组件来实现,但前提是数据库访问组件必须支持多台数据库的操作。
- 前台:存储过程、对象映射
- 后台:脚本命令、表模块(DataTable)、对象映射
注意:所有数据库访问操作都必须采用参数化传递,防止SQL注入,采用using来使用DataReader对象。
1.2.5 数据库访问组件
在项目中,数据库访问组件也属于“基础设施层”,因为它是一个统用的组件。在项目中,并没有采用第三方ORM框架来实现数据库访问,主要是为了后期扩展考虑,使用ADO.NET可以灵活的实现读写分离、负载均衡,也方便日志记录和异常信息的捕获。
在开发数据库访问组件时,需要以下注意事项:
- 数据库访问组件是决定性能最重要的一层,每一行代码都要经过思考。根据需求进行设计,比如架构路线已经决定采用了SQL Server数据库,就不要盲目设计让数据库访问组件支持多种数据库。
- 增加错误日志记录功能,当数据库操作发生错误时,应该及时记录错误日志并通知管理员。
- 合理使用using,避免打开数据库连接后没有关闭,这样会导至数据库表加锁或占满默认连接池数目。(利用SQL Server的活動監視器(圖形化介面)瞭解Connection Pool運作)
- 支持事务操作、参数化传递。
- 性能测试,数据库访问组件开发完毕后,所有功能都必须经过严格的测试,并且根据需求进行压力测试,编写多种场景的测试功能并对这些功能进行压力测试。
本系列目录:2011 年终项目总结