咱们以javaweb为例,来搭建一个简单的电商系统,看看这个系统能够如何一步步演变。前端
该系统具有的功能:java
用户模块:用户注册和管理 商品模块:商品展现和管理 交易模块:建立交易和管理 阶段1、单机构建网站mysql
网站的初期,咱们常常会在单机上跑咱们全部的程序和软件。此时咱们使用一个容器,如tomcat、jetty、jboos,而后直接使用JSP/servlet技术,或者使用一些开源的框架如maven+spring+struct+hibernate、maven+spring+springmvc+mybatis;最后再选择一个数据库管理系统来存储数据,如mysql、sqlserver、oracle,而后经过JDBC进行数据库的链接和操做。nginx
把以上的全部软件都装载同一台机器上,应用跑起来了,也算是一个小系统了。此时系统结果以下:web
阶段2、应用服务器与数据库分离redis
随着网站的上线,访问量逐步上升,服务器的负载慢慢提升,在服务器尚未超载的时候,咱们应该就要作好准备,提高网站的负载能力。假如咱们代码层面已难以优化,在不提升单台机器的性能的状况下,增长机器是一个不错的方式,不只能够有效地提升系统的负载能力,并且性价比高。算法
增长的机器用来作什么呢?此时咱们能够把数据库,web服务器拆分开来,这样不只提升了单台机器的负载能力,也提升了容灾能力。 应用服务器与数据库分开后的架构以下图所示:spring
阶段3、应用服务器集群sql
随着访问量继续增长,单台应用服务器已经没法知足需求了。在假设数据库服务器没有压力的状况下,咱们能够把应用服务器从一台变成了两台甚至多台,把用户的请求分散到不一样的服务器中,从而提升负载能力。多台应用服务器之间没有直接的交互,他们都是依赖数据库各自对外提供服务。著名的作故障切换的软件有keepalived,keepalived是一个相似于layer三、四、7交换机制的软件,他不是某个具体软件故障切换的专属品,而是能够适用于各类软件的一款产品。keepalived配合上ipvsadm又能够作负载均衡,可谓是神器。docker
咱们以增长了一台应用服务器为例,增长后的系统结构图以下:
系统演变到这里,将会出现下面四个问题:
用户的请求由谁来转发到到具体的应用服务器 有什么转发的算法 应用服务器如何返回用户的请求 用户若是每次访问到的服务器不同,那么如何维护session的一致性 咱们来看看解决问题的方案:
一、第一个问题便是负载均衡的问题,通常有5种解决方案:
一、http重定向。HTTP重定向就是应用层的请求转发。用户的请求其实已经到了HTTP重定向负载均衡服务器,服务器根据算法要求用户重定向,用户收到重定向请求后,再次请求真正的集群
优势:简单。
缺点:性能较差。
二、DNS域名解析负载均衡。DNS域名解析负载均衡就是在用户请求DNS服务器,获取域名对应的IP地址时,DNS服务器直接给出负载均衡后的服务器IP。
优势:交给DNS,不用咱们去维护负载均衡服务器。
缺点:当一个应用服务器挂了,不能及时通知DNS,并且DNS负载均衡的控制权在域名服务商那里,网站没法作更多的改善和更强大的管理。
三、反向代理服务器。在用户的请求到达反向代理服务器时(已经到达网站机房),由反向代理服务器根据算法转发到具体的服务器。经常使用的apache,nginx均可以充当反向代理服务器。
优势:部署简单。
缺点:代理服务器可能成为性能的瓶颈,特别是一次上传大文件。
四、IP层负载均衡。在请求到达负载均衡器后,负载均衡器经过修改请求的目的IP地址,从而实现请求的转发,作到负载均衡。
优势:性能更好。
缺点:负载均衡器的宽带成为瓶颈。
五、数据链路层负载均衡。在请求到达负载均衡器后,负载均衡器经过修改请求的mac地址,从而作到负载均衡,与IP负载均衡不同的是,当请求访问完服务器以后,直接返回客户。而无需再通过负载均衡器。
二、第二个问题便是集群调度算法问题,常见的调度算法有10种。
一、rr 轮询调度算法。顾名思义,轮询分发请求。
优势:实现简单
缺点:不考虑每台服务器的处理能力
二、wrr 加权调度算法。咱们给每一个服务器设置权值weight,负载均衡调度器根据权值调度服务器,服务器被调用的次数跟权值成正比。
优势:考虑了服务器处理能力的不一样
三、sh 原地址散列:提取用户IP,根据散列函数得出一个key,再根据静态映射表,查处对应的value,即目标服务器IP。过目标机器超负荷,则返回空。
四、dh 目标地址散列:同上,只是如今提取的是目标地址的IP来作哈希。
优势:以上两种算法的都能实现同一个用户访问同一个服务器。
五、lc 最少链接。优先把请求转发给链接数少的服务器。
优势:使得集群中各个服务器的负载更加均匀。
六、wlc 加权最少链接。在lc的基础上,为每台服务器加上权值。算法为:(活动链接数*256+非活动链接数)÷权重 ,计算出来的值小的服务器优先被选择。
优势:能够根据服务器的能力分配请求。
七、sed 最短时间望延迟。其实sed跟wlc相似,区别是不考虑非活动链接数。算法为:(活动链接数+1)*256÷权重,一样计算出来的值小的服务器优先被选择。
八、nq 永不排队。改进的sed算法。咱们想一下什么状况下才能“永不排队”,那就是服务器的链接数为0的时候,那么假若有服务器链接数为0,均衡器直接把请求转发给它,无需通过sed的计算。
九、LBLC 基于局部性的最少链接。均衡器根据请求的目的IP地址,找出该IP地址最近被使用的服务器,把请求转发之,若该服务器超载,最采用最少链接数算法。
十、LBLCR 带复制的基于局部性的最少链接。均衡器根据请求的目的IP地址,找出该IP地址最近使用的“服务器组”,注意,并非具体某个服务器,而后采用最少链接数从该组中挑出具体的某台服务器出来,把请求转发之。若该服务器超载,那么根据最少链接数算法,在集群的非本服务器组的服务器中,找出一台服务器出来,加入本服务器组,而后把请求转发之。
三、第三个问题是集群模式问题,通常3种解决方案:
一、NAT:负载均衡器接收用户的请求,转发给具体服务器,服务器处理完请求返回给均衡器,均衡器再从新返回给用户。
二、DR:负载均衡器接收用户的请求,转发给具体服务器,服务器出来玩请求后直接返回给用户。须要系统支持IP Tunneling协议,难以跨平台。
三、TUN:同上,但无需IP Tunneling协议,跨平台性好,大部分系统均可以支持。
四、第四个问题是session问题,通常有4种解决方案:
一、Session Sticky。session sticky就是把同一个用户在某一个会话中的请求,都分配到固定的某一台服务器中,这样咱们就不须要解决跨服务器的session问题了,常见的算法有ip_hash法,即上面提到的两种散列算法。
优势:实现简单。
缺点:应用服务器重启则session消失。
二、Session Replication。session replication就是在集群中复制session,使得每一个服务器都保存有所有用户的session数据。
优势:减轻负载均衡服务器的压力,不须要要实现ip_hasp算法来转发请求。
缺点:复制时宽带开销大,访问量大的话session占用内存大且浪费。
三、Session数据集中存储:session数据集中存储就是利用数据库来存储session数据,实现了session和应用服务器的解耦。
优势:相比session replication的方案,集群间对于宽带和内存的压力减小了不少。
缺点:须要维护存储session的数据库。
四、Cookie Base:cookie base就是把session存在cookie中,有浏览器来告诉应用服务器个人session是什么,一样实现了session和应用服务器的解耦。
优势:实现简单,基本免维护。
缺点:cookie长度限制,安全性低,宽带消耗。
值得一提的是:
nginx目前支持的负载均衡算法有wrr、sh(支持一致性哈希)、fair(本人以为能够归结为lc)。但nginx做为均衡器的话,还能够一同做为静态资源服务器。
keepalived+ipvsadm比较强大,目前支持的算法有:rr、wrr、lc、wlc、lblc、sh、dh
keepalived支持集群模式有:NAT、DR、TUN
nginx自己并无提供session同步的解决方案,而apache则提供了session共享的支持。
好了,解决了以上的问题以后,系统的结构以下:
阶段4、数据库读写分离化
上面咱们老是假设数据库负载正常,但随着访问量的的提升,数据库的负载也在慢慢增大。那么可能有人立刻就想到跟应用服务器同样,把数据库一份为二再负载均衡便可。但对于数据库来讲,并无那么简单。假如咱们简单的把数据库一分为二,而后对于数据库的请求,分别负载到A机器和B机器,那么显而易见会形成两台数据库数据不统一的问题。那么对于这种状况,咱们能够先考虑使用读写分离的方式。
读写分离后的数据库系统结构以下:
这个结构变化后也会带来两个问题:
主从数据库之间数据同步问题 应用对于数据源的选择问题 解决问题方案:
咱们可使用MYSQL自带的master+slave的方式实现主从复制。 采用第三方数据库中间件,例如mycat。mycat是从cobar发展而来的,而cobar是阿里开源的数据库中间件,后来中止开发。mycat是国内比较好的mysql开源数据库分库分表中间件。 阶段5、用搜索引擎缓解读库的压力
数据库作读库的话,经常对模糊查找力不从心,即便作了读写分离,这个问题还未能解决。以咱们所举的交易网站为例,发布的商品存储在数据库中,用户最常使用的功能就是查找商品,尤为是根据商品的标题来查找对应的商品。对于这种需求,通常咱们都是经过like功能来实现的,可是这种方式的代价很是大。此时咱们可使用搜索引擎的倒排索引来完成。
搜索引擎具备如下优势:
它可以大大提升查询速度。
引入搜索引擎后也会带来如下的开销:
带来大量的维护工做,咱们须要本身实现索引的构建过程,设计全量/增长的构建方式来应对非实时与实时的查询需求。 须要维护搜索引擎集群 搜索引擎并不能替代数据库,他解决了某些场景下的“读”的问题,是否引入搜索引擎,须要综合考虑整个系统的需求。引入搜索引擎后的系统结构以下:
阶段6、用缓存缓解读库的压力
一、后台应用层和数据库层的缓存
随着访问量的增长,逐渐出现了许多用户访问同一部份内容的状况,对于这些比较热门的内容,不必每次都从数据库读取。咱们可使用缓存技术,例如可使用google的开源缓存技术guava或者使用memcacahe做为应用层的缓存,也可使用redis做为数据库层的缓存。
另外,在某些场景下,关系型数据库并非很适合,例如我想作一个“每日输入密码错误次数限制”的功能,思路大概是在用户登陆时,若是登陆错误,则记录下该用户的IP和错误次数,那么这个数据要放在哪里呢?假如放在内存中,那么显然会占用太大的内容;假如放在关系型数据库中,那么既要创建数据库表,还要简历对应的java bean,还要写SQL等等。而分析一下咱们要存储的数据,无非就是相似{ip:errorNumber}这样的key:value数据。对于这种数据,咱们能够用NOSQL数据库来代替传统的关系型数据库。
二、页面缓存
除了数据缓存,还有页面缓存。好比使用HTML5的localstroage或者cookie。
优势:
减轻数据库的压力 大幅度提升访问速度 缺点:
须要维护缓存服务器 提升了编码的复杂性 值得一提的是:
缓存集群的调度算法不一样与上面提到的应用服务器和数据库。最好采用“一致性哈希算法”,这样才能提升命中率。这个就不展开讲了,有兴趣的能够查阅相关资料。
加入缓存后的结构:
阶段7、数据库水平拆分与垂直拆分
咱们的网站演进到如今,交易、商品、用户的数据都还在同一个数据库中。尽管采起了增长缓存,读写分离的方式,但随着数据库的压力继续增长,数据库的瓶颈愈来愈突出,此时,咱们能够有数据垂直拆分和水平拆分两种选择。
7.一、数据垂直拆分
垂直拆分的意思是把数据库中不一样的业务数据拆分道不一样的数据库中,结合如今的例子,就是把交易、商品、用户的数据分开。
优势:
解决了原来把全部业务放在一个数据库中的压力问题。 能够根据业务的特色进行更多的优化 缺点:
须要维护多个数据库 问题:
须要考虑原来跨业务的事务 跨数据库的join 解决问题方案:
咱们应该在应用层尽可能避免跨数据库的事物,若是非要跨数据库,尽可能在代码中控制。 咱们能够经过第三方应用来解决,如上面提到的mycat,mycat提供了丰富的跨库join方案,详情可参考mycat官方文档。 垂直拆分后的结构以下:
7.二、数据水平拆分
数据水平拆分就是把同一个表中的数据拆分到两个甚至多个数据库中。产生数据水平拆分的缘由是某个业务的数据量或者更新量到达了单个数据库的瓶颈,这时就能够把这个表拆分到两个或更多个数据库中。
优势:
若是咱们能客服以上问题,那么咱们将可以很好地对数据量及写入量增加的状况。 问题:
访问用户信息的应用系统须要解决SQL路由的问题,由于如今用户信息分在了两个数据库中,须要在进行数据操做时了解须要操做的数据在哪里。 主键的处理也变得不一样,例如原来自增字段,如今不能简单地继续使用了。 若是须要分页,就麻烦了。 解决问题方案:
咱们仍是能够经过能够解决第三方中间件,如mycat。mycat能够经过SQL解析模块对咱们的SQL进行解析,再根据咱们的配置,把请求转发到具体的某个数据库。 咱们能够经过UUID保证惟一或自定义ID方案来解决。 mycat也提供了丰富的分页查询方案,好比先从每一个数据库作分页查询,再合并数据作一次分页查询等等。 数据水平拆分后的结构:
阶段8、应用的拆分
8.一、拆分应用
随着业务的发展,业务愈来愈多,应用愈来愈大。咱们须要考虑如何避免让应用愈来愈臃肿。这就须要把应用拆开,从一个应用变为俩个甚至更多。仍是以咱们上面的例子,咱们能够把用户、商品、交易拆分开。变成“用户、商品”和“用户,交易”两个子系统。
拆分后的结构:
问题:
这样拆分后,可能会有一些相同的代码,如用户相关的代码,商品和交易都须要用户信息,因此在两个系统中都保留差很少的操做用户信息的代码。如何保证这些代码能够复用是一个须要解决的问题。 解决问题:
经过走服务化的路线来解决 8.二、走服务化的道路
为了解决上面拆分应用后所出现的问题,咱们把公共的服务拆分出来,造成一种服务化的模式,简称SOA。
采用服务化以后的系统结构:
优势:
相同的代码不会散落在不一样的应用中了,这些实现放在了各个服务中心,使代码获得更好的维护。 咱们把对数据库的交互放在了各个服务中心,让”前端“的web应用更注重与浏览器交互的工做。 问题:
如何进行远程的服务调用 解决方法:
咱们能够经过下面的引入消息中间件来解决 阶段9、引入消息中间件
随着网站的继续发展,咱们的系统中可能出现不一样语言开发的子模块和部署在不一样平台的子系统。此时咱们须要一个平台来传递可靠的,与平台和语言无关的数据,而且可以把负载均衡透明化,能在调用过程当中收集调用数据并分析之,推测出网站的访问增加率等等一系列需求,对于网站应该如何成长作出预测。开源消息中间件有阿里的dubbo,能够搭配Google开源的分布式程序协调服务zookeeper实现服务器的注册与发现。
引入消息中间件后的结构:
10、总结
以上的演变过程只是一个例子,并不适合全部的网站,实际中网站演进过程与自身业务和不一样遇到的问题有密切的关系,没有固定的模式。只有认真的分析和不断地探究,才能发现适合本身网站的架构。
下一篇,我将根据小型项目的实际状况,设计出11套具体的方案,分别对应拥有1~12机器时,如何利用这些稀少的资源搭建小型的高可用,高扩展的结构出来。
下下篇,才轮到实战,我将结合docker虚拟技术,实现快速的集群搭建。
敬请期待~
本文有什么说错的地方,但愿你们指出,让我好改正过来,多谢。
参考:
《大型网站技术架构:核心原理与案例分析》——李智慧 著
《大型网站系统与Java中间件实践》——曾宪杰 著
《MySQL性能调优与架构设计》——简朝阳 著
《keepalived权威指南》
《mycat权威指南》
《dubbo用户指南》
《计算机网络》
《操做系统》