搭建大型网站使用的技术架构

本文探讨了大型网站从一万到百万用户的技术架构演进,包括初期的LAMP架构,到应用与数据库分离,再到百万用户级别的Nginx负载均衡和分布式系统。介绍了Nginx、Keepalived、数据库读写分离、缓存策略等关键技术和挑战,如用户Session共享、服务器宕机应对、灾备处理等。

大型网站软件系统的特点

  1. 高并发,大流量;
  2. 高可用:系统7*24小时不间断服务;
  3. 海量数据:需要储存,管理海量数据,需要使用大量服务器;
  4. 用户分布广泛,网络情况复杂:用户分布范围广,各地网络情况千差万别;
  5. 安全环境恶劣:由于互联网的开放性,使得互联网更容易受到攻击,随着网站的访问量增加几乎每天都会被黑客攻击;
  6. 需求快速变更,发布频繁:和传统软件的版本发布频率不同,互联网产品为快速适应市场,满足用户需求,其产品发布频率是极高的;
  7. 渐进式发展:与传统软件产品或企业应用系统一开始就规划好全部的功能和非功能需求不同,几乎所有的大型互联网网站都是从一个小网站开始,渐进地发展起来的。

网站架构演化发展历程

第一阶段的网站架构(一万用户网站架构)

应用程序,数据库,文件等所有的资源都在一台服务器上。

用户通过访问internet访问某个网站,通过DNS服务商解析,找到对应的服务器地址,请求服务器,响应用户请求的信息。

LAMP LinuxWindows+Apache+PHP+Mysql,经典配置。太多经典案例都是这样的架构设计,好处就是便易、方便、开发上线速度快、成本低。

第二阶段(十万用户网站架构)

十万用户左右的网站架构就略微复杂点,将应用程序,文件,数据库都分别单独放在一台服务器上,以下是服务器粗略架构图;

         这个十万级别的架构主要改变就是,把之前放在一个服务器上的应用程序,文件,数据库分别放在三个服务器上,实现了应用与数据库的分离,这也是系统优化的最基本思想之一。由于在逻辑上应用程序,文件,数据库本身就是分离的,这次只是在部署到不同的服务器上,所以在软件上需要修改的很少(主要就修改下图片,数据库的访问路径前缀),总的来说还是简单的分离。

第三阶段(百万及百万以上用户网站初期架构)

百万级别用户网站架构是目前阶段最合理也是最实用了,从市面上大多数中小型电商企业都采用百万级别的架构。从实际业务场景来看,99%系统的真实用户过不了百万,熟练精通百万用户架构,基本上可以为咱们公司解绝大部分问题;从技术角度来看,百万用户也是传统技术架构支持的巅峰,再想往上那就需要对产品和业务进行拆分,导致的后果就是系统的复杂程度与成本成倍增加,以下是网站的架构图:

服务器层次图例

公司网络架构图

这种架构,DNS只配置一条记录,该记录的IP,是统一对外的IP地址;同时增加Nginx做反向代理,所有请求必须通过Nginx处理,根据权重负载等,Nginx会自动计算,选择一台服务器处理用户请求。

         优点:

  1. 简化了DNS配置;
  2. 安全性,所有请求都经过Nginx,避免用户直接与应用服务器直接通信,Nginx与应用服务器之间使用内网地址通信;
  3. 如果某台应用服务器宕机,Nginx会自动将这台服务器从集群中剔除,并指定另外一台服务器作为响应,宕机的服务恢复后,Nginx会自动将它加入到集群中。

缺点

  1. 部署复杂;
  2. 用户Session共享问题,例如目前使用“Tomcat1-镇”服务器响应请求,突然宕机,这是Nginx会切换到另一台服务器“Tomcat1-本”服务器上,“Tomcat1-本”上没有用户信息,因此需要解决Session共享问题,只要涉及到集群都会遇到这个问题;
  3. Nginx万一宕机,整个应用将无法正常工作。

Nginx宕机方案

         Keepalived+Nginx集群

         Keepalived介绍:

                   Keepalived起初是LVS设计的,专门用来监控集群系统中各个服务节点的状态,它据TCP/IP参考模型的第三,第四层,第五层交换机制检测每个服务器节点状态,如果某个服务器节点出现异常,或者工作出现故障,Keepalived将检测到,并将出现的故障的服务器节点从集群系统中剔除,这些工作全部是自动完成的,不需要人工干涉,需要人工完成的是修复出现故障的服务器节点。

Niginx宕机前

Nginx宕机后

机房灾备处理方案

         灾备前图例

                   灾备后图例

当机房出现断网,断电等情况导致整个机房无法运作,需我们这边运维人员,通过DNS解析服务器,将所有产品域名ip指向到本地Nginx服务器。(解析过程大约需要0.5-1小时之间),在解析过程中,所有产品无法运行。

大型动态应用系统又可分为几个子系统:

l Web前 端系统

l 负 载均衡系统(nginx服务器)

l 数 据库集群系统(无)

l 缓 存系统(无)

l 分 布式存储系统(nfs技术)

l 分 布式服务器管理系统(宝塔暂代使用)

l 代 码分发系统(svn)

总结

         从上述第三阶段百万级网站架构图中,可以明了的看出系统由负载均衡Nginx服务器提供统一的入口和出口。这样做的好处就是系统架构,便于管理,只要在入口处做好管理,整个系统与外界的交互就搞定了;不好之处就是所有的流量都要从一个节点流入流出,会造成单个节点的并发压力很大,尤其当用户的快速增长,问题就愈加突显。上面的架构不仅仅在负载均衡服务器地方存在瓶颈问题,在数据库服务器那里也存在同样的瓶颈问题;在系统架构里只要存在不能进行集群或者是分布式的地方,随着业务量的增长最后都会成为系统的瓶颈,而这些瓶颈就是限制系统提高并发量的主要问题。

         当前的这种架构的瓶颈就限制了系统的最大支持量只有百万级,如果要获得更高的并发量,就必须解决掉上面的瓶颈。比如负载均衡服务的问题,可以用DNS域名解析,将请求分发到不同的负载均衡服务器;比如数据库服务器的问题,我们可以对数据库进行分表、分区、分库、分片等。但采用了这些方案后,意味着我们的系统将会变的更复杂,更难维护,成本自然就更高了;所以能用简单的方案解决问题时,我们就尽量不要用复杂的方案,根据目前阶段,暂不处理这些问题。

负载均衡服务器介绍-Nginx服务器

         百万级别的用户量,负载均衡服务器首推的Nginx。选择Nginx的原因很简单,开源免费,简单稳定,参考资料齐全,同时足够满足目前公司的业务需求,下面详细分析一下是否满足我们的业务场景。

         Nginx官方给出的数据是,最高支持5w的并发数;通过网上第三方给出的资料,一个8核+16GB的服务器正常情况下支持2W-3W的并发。我们架构的是百万真实用户,所以我们需要知道的是某一刻的最大用户数,这就要进行一定的模型推演,用总的用户数预算处某一刻最大的峰值。100W真实用户》30W活》10W活》3W日活在线的峰值。3W用户同时在线,也并不是每个用户都同时不停的发请求,所以说一台Nginx支持百万用户的负载均衡是ok的。

几种常见的服务器:

         Apache:是目前使用最广泛的web服务器,市场占有率超过50%,稳居第一。社区资料高度成熟,支持模块多,功能齐全,性能稳定(性能比nginx更稳定,bug少);但对高并发的支持不是很好,同样一个配置8核+16GB的服务器正常情况下支持2-3K的并发数。

         Microsoft:目前使用量第二的web服务器,由微软开发体系使用居多(Windows服务器)。

         Nginx:作为目前使用量第三的web服务器,目前使用量在增加,apache服务器使用量在减少。

         LVS:这个是免费产品中最好的负载均衡服务器,但配置及其复杂,对使用人员要求很高,LVS的支持范围也有限。LVS一般是在Nginx,甚至F5负载均衡满足不了需求时才会使用。LVS是数据链路层负载均衡,简单来说LVS只是做请求的转发,具体返回什么值由对应的服务器直接返回给客户端;nginx等服务器,不仅仅做请求的转发,还负责将应用服务器返回的结果,写给用户端。

         Tomcat:这个是应用服务器,一般用在Apache/Nginx/lvs的后面,提供动态数据服务。

应用服务器

         架构图上我们可以看到,应用服务器主要包含三块内容,应用程序,本地缓存,数据访问模块。

应用程序

         Web程序,Php运行程序,java运行程序,python运行程序等

         目前还在规划之中。

本地缓存

         关于缓存,有利有弊;一方面你需要使用缓存,来提高访问速度,减少程序与物理数据的直接交互;另一方面,也会给我们带来数据不一致问题,给服务器的集群带来很大不便。

关于缓存最典型的就算session(用户相关信息数据),下面就以session为例分析下我们的本地缓存。

        最理想的肯定是把session直接缓存到应用服务器上,这样程序取数据就直接从内存里取,100ns就能拿到数据,快的飞起,实际上小系统也都是这么做的。问题是当用户量大了,一台服务器满足不了需求,需要多台服务器集群就麻烦了。比如,用户登录请求的是A服务器,但是查订单信息请求的是B服务器,那么session是缓存在A上,B上面没有,这自然是不行的。

        这个时候你可能觉得这个问题好解决啊,方法一,在登录时我把A的session复制到B上不就ok了。确实应用服务器也提供了这种功能,其中jboss、weblogic对缓存的同步做的比较好,tomcat则差很多(官方建议:做缓存同步服务器不超过4台)。当用户量不大时这种方案确实可行,当量多时就麻烦了;比如一个用户的session为100KB,10w个用户,那么就缓存session就10G了,占用大量昂贵的内存肯定是不划算的,更麻烦的是多台服务器同步session时,会占用大量网络带宽,甚至拖垮网络系统。

        方法二,你也别每台服务器都去同步session这么麻烦了,直接在负载均衡时就给你分好,同一个IP的请求都转给同一个服务器,这样既不浪费内存,也不占用网络带宽,貌似完美解决方案。正常情况这样确实没毛病,有问题的不正常才是软件的常态啊,比如用户切换网络了,IP就变了,现在很多银行的APP,你切换网络后就提示你安全有问题;再比如,要添加一台服务器,或者那台服务器坏了,IP的分配策略就变了,这时候缓存就全乱了;还有后期如果涉及到模块拆分,更加麻烦了。

       关于session的缓存问题,无论是方法一还是二,都不是良好的解决方案,所以在此种架构下session并不建议存在本地,而应该使用远程分布式缓存集中存储,这样就将session和应用服务器分离,彻底解决了缓存问题。应用服务器如果没有了公用缓存的限制,只缓存服务器自身的数据,那么部署集群上就方便多了,并且所有的服务都可以做成面向无连接的,负载均衡服务器也可以根据运行情况调整请求分配,并且动态增减服务器都不受影响;另一方面,使用分布式缓存,缓存的数据量可以更大,安全性也更高,集中起来更便于管理。

        使用中心缓存是为了解决各个服务器缓存不一致的问题,如果该部分缓存只和当前服务器有关,比如一些静态文件等,还是要缓存到本地提高访问速度;只有是公共、并且还会出现修改的部分,都应该放到远程分布式缓存服务器上去。

数据访问模块

         关于数据库的读写分离,spring和中间件都能做。用spring来做,只需要在配置文件中配置多个数据源,然后用aop对读写进行自动分离,也可以在代码里手动指定数据源。用中间件(比如:proxy,mycat等)来做,引入中间件配置好后,程序直接连中间件就可以了。如果仅仅只做读写分离,就主从两个数据库,是用spring还简单些,性能也更好;如果是1主n从,还要考虑负载均衡,那自然是要用中间件了,这样不用自己去做负载均衡;如果考虑到后期扩展,需要分库、分片,那就只能用中间件。

       100w用户量应该还在数据库正常的接受范围,最多对某些可能大的表(比如:账单、消息、日志),进行简单分表、历史数据分离处理下就好了,这里的分表是指将不同类型的账单、消息、日志分表,仅仅从业务层面的水平拆分,而不是分布式数据库中的分片拆分。仅仅考虑当前的用户量,还是建议使用spring,毕竟简单、好用、还有方便。

数据库服务器

        在当前的这种架构下,数据库的访问压力并不比十万级大多少,可能感觉很奇怪,不过这是真的。主要原因就是采用了缓存技术,这样可以降低很多程序对物理数据源的直接访问。比如用户接口请求日志,就可以先写到缓存中,然后批量同步到数据库;比如用户对首页、常用列表的访问,可以把这些数据都缓存起来,就不用每次都访问数据库;比如用户的信息,可以直接缓存起来,每次查询时读缓存就可以了等等。

        通过一系列缓存后,除了一些实时更新的数据,需要立即写到数据库,其它大多可以用缓存解决,这样就很大程度上减轻了数据库的访问压力。即使是一些写操作,如果是非实时立即写入的,通过缓存过渡后,采用批处理一次写入,也能极大的提高写入性能。比如用户访问日志,如果每次都向数据库写记录,则用户请求1w次接口,就需要向数据库写1w条记录,以hibernate 35ms每条的速度,需要350S;如果使用了缓存,批处理写入1w条数据,1s左右就搞定了,这就是缓存带来性能的飞跃。

        关于数据库的读写分离,是为了将读和写分开,我们知道当前主流的关系型数据库(oracle、mysql)都是行级锁,这样当同时对同一张表进行写入、查询操作时,是会有线程阻塞的,尤其当阻塞严重时,会使系统性能急剧下滑、甚至崩溃;如果进行了读写分离后,就不存在这样的问题,主库主要用来写,查询少,这样阻塞很低,从库只读,是不存在阻塞的;这样不但能将单个的数据库服务器性能发挥到最佳,而且稳定性也大有提升,并且做了主从备份后,数据的安全性也得到了提高。总的来说读写分离虽说复杂了点,但带来的收益还是可观的,在条件允许的情况下还是建议做下读写分离,比如在百万级用户情况下,必须做读写分离。

        关于消息任务的批量处理,引入了缓存机制后,这个问题我们相对就好处理了。比如要向系统每个用户都发一条消息,之前我们是用批处理一次向数据库写入100w条记录,这样会耗时比较长,容易造成程序阻塞;有了缓存后,我们可以先把100w条记录先写到缓存中(如果用redis,每秒能set 5w次,100w条数据如果合并处理下,1s内就能搞定),然后再分批同步到数据库,基本上是不会有什么性能问题;这里麻烦的是,在数据同步时要手动处理事务问题,还要处理缓存中数据丢失问题。

        总的来说,数据库除了做下读写分离,其它需要调整的不大,而且读写分离的控制在应用程序那块已经讲了,这里就不再重复。至于主从数据库的数据同步,不管是oracle,还是mysql都提供了同步的方案,参照配置下就好了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值