“大型Web网站架构演变”的版本间的差异
(→阶段四、数据库读写分离化) |
|||
第178行: | 第178行: | ||
==阶段四、数据库读写分离化== | ==阶段四、数据库读写分离化== | ||
+ | 上面我们总是假设数据库负载正常,但随着访问量的的提高,数据库的负载也在慢慢增大。那么可能有人马上就想到跟应用服务器一样,把数据库一份为二再负载均衡即可。 | ||
+ | 但对于数据库来说,并没有那么简单。假如我们简单的把数据库一分为二,然后对于数据库的请求,分别负载到A机器和B机器,那么显而易见会造成两台数据库数据不统一的问题。那么对于这种情况,我们可以先考虑使用读写分离和主从复制的方式。 | ||
+ | |||
+ | 读写分离后的系统结构如下: | ||
+ | |||
+ | [[文件:cloud1-12.png]] | ||
+ | |||
+ | 这个结构变化后也会带来两个问题: | ||
+ | |||
+ | *主从数据库之间数据同步问题。 | ||
+ | *应用对于数据源的选择问题。 | ||
+ | |||
+ | 解决方案: | ||
+ | |||
+ | *使用MySQL自带的Master + Slave的方式实现主从复制。 | ||
+ | *采用第三方数据库中间件,例如MyCat。MyCat是从Cobar发展而来的,而Cobar是阿里开源的数据库中间件,后来停止开发。MyCat是国内比较好的MySql开源数据库分库分表中间件。 | ||
+ | |||
+ | ==阶段五、用搜索引擎缓解读库的压力== | ||
+ | |||
+ | 数据库做读库的话,常常对模糊查找力不从心,即使做了读写分离,这个问题还未能解决。以我们所举的交易网站为例,发布的商品存储在数据库中,用户最常使用的功能就是查找商品,尤其是根据商品的标题来查找对应的商品。对于这种需求,一般我们都是通过like功能来实现的,但是这种方式的代价非常大,而且结果非常不准确。此时我们可以使用搜索引擎的倒排索引来完成。 | ||
+ | |||
+ | ===搜索引擎的优点=== | ||
+ | 搜索引擎具有的优点:它能够大大提高查询速度和搜索准确性。 | ||
+ | |||
+ | ===引入搜索引擎的开销=== | ||
+ | |||
+ | *带来大量的维护工作,我们需要自己实现索引的构建过程,设计全量/增加的构建方式来应对非实时与实时的查询需求。 | ||
+ | *需要维护搜索引擎集群 | ||
+ | |||
+ | 搜索引擎并不能替代数据库,它解决了某些场景下的精准、快速、高效的“读”操作,是否引入搜索引擎,需要综合考虑整个系统的需求。 | ||
+ | |||
+ | 引入搜索引擎后的系统结构如下: | ||
+ | |||
+ | [[文件:cloud1-13.png]] | ||
+ | |||
+ | ==阶段六、用缓存缓解读库的压力== | ||
+ | |||
+ | 常用的缓存机制包括页面级缓存、应用数据缓存和数据库缓存。 | ||
+ | |||
+ | ===应用层和数据库层的缓存=== | ||
+ | |||
+ | 随着访问量的增加,逐渐出现了许多用户访问同一部分热门内容的情况,对于这些比较热门的内容,没必要每次都从数据库读取。我们可以使用缓存技术,例如可以使用Google的开源缓存技术Guava或者使用Memecahed作为应用层的缓存,也可以使用Redis作为数据库层的缓存。 | ||
+ | |||
+ | 另外,在某些场景下,关系型数据库并不是很适合,例如我想做一个“每日输入密码错误次数限制”的功能,思路大概是在用户登录时,如果登录错误,则记录下该用户的IP和错误次数,那么这个数据要放在哪里呢?假如放在内存中,那么显然会占用太大的内容;假如放在关系型数据库中,那么既要建立数据库表,还要建立对应的Java bean,还要写SQL等等。而分析一下我们要存储的数据,无非就是类似{ip:errorNumber}这样的key:value数据。对于这种数据,我们可以用NOSQL数据库来代替传统的关系型数据库。 | ||
+ | |||
+ | ===页面缓存=== | ||
+ | 除了数据缓存,还有页面缓存。比如使用HTML5的localstroage或者Cookie。除了页面缓存带来的性能提升外,对于并发访问且页面置换频率小的页面,应尽量使用页面静态化技术。 | ||
+ | |||
+ | *优点:减轻数据库的压力, 大幅度提高访问速度; | ||
+ | *缺点:需要维护缓存服务器,提高了编码的复杂性。 | ||
+ | |||
+ | 值得一提的是: | ||
+ | |||
+ | 缓存集群的调度算法不同与上面提到的应用服务器和数据库。最好采用一致性哈希算,这样才能提高命中率。 | ||
+ | |||
+ | 加入缓存后的系统结构如下: | ||
+ | |||
+ | [[文件:cloud1-14.png]] | ||
==参考文档== | ==参考文档== | ||
[1] https://www.jianshu.com/p/5a67d789216e | [1] https://www.jianshu.com/p/5a67d789216e |
2019年11月14日 (四) 03:17的版本
目录
前言
我们以Java Web为例,来搭建一个简单的电商系统,看看这个系统可以如何一步步演变 该系统具备的功能:
用户模块:用户注册和管理 商品模块:商品展示和管理 交易模块:创建交易和管理
阶段一、单机构建网站
网站的初期,我们经常会在单机上跑我们所有的程序和软件。此时我们使用一个容器,如Tomcat、Jetty、Jboss,然后直接使用JSP/Servlet技术,或者使用一些开源的框架如Maven + Spring + Struts + Hibernate、Maven + Spring + Spring MVC + Mybatis。最后再选择一个数据库管理系统来存储数据,如MySQL、SqlServer、Oracle,然后通过JDBC进行数据库的连接和操作。
把以上的所有软件包括数据库、应用程序都装载同一台机器上,应用跑起来了,也算是一个小系统了。此时系统结果如下:
阶段二、应用服务器与数据库分离
随着网站的上线,访问量逐步上升,服务器的负载慢慢提高,在服务器还没有超载的时候,我们应该就要做好准备,提升网站的负载能力。假如我们代码层面已难以优化,在不提高单台机器的性能的情况下,采用增加机器是一个不错的方式,不仅可以有效地提高系统的负载能力,而且性价比高。
增加的机器用来做什么呢?此时我们可以把数据库服务器和Web服务器拆分开来,这样不仅提高了单台机器的负载能力,也提高了容灾能力。
应用服务器与数据库分开后的架构如下图所示:
阶段三、应用服务器集群
随着访问量继续增加,单台应用服务器已经无法满足需求了。在假设数据库服务器没有压力的情况下,我们可以把应用服务器从一台变成了两台甚至多台,把用户的请求分散到不同的服务器中,从而提高负载能力。而多台应用服务器之间没有直接的交互,他们都是依赖数据库各自对外提供服务。著名的做故障切换的软件有KeepAlived,KeepAlived是一个类似于Layer3、4、7交换机制的软件,他不是某个具体软件故障切换的专属品,而是可以适用于各种软件的一款产品。KeepAlived配合上ipvsadm又可以做负载均衡,可谓是神器。
我们以增加了一台应用服务器为例,增加后的系统结构图如下:
系统演变到这里,将会出现下面四个问题:
- 用户的请求由谁来转发到到具体的应用服务器?
- 有那些转发的算法和策略可以使用?
- 应用服务器如何返回用户的请求?
- 用户如果每次访问到的服务器不一样,那么如何维护session的一致性?
负载均衡
HTTP 重定向(重点)
HTTP重定向就是应用层的请求转发。用户的请求其实已经到了HTTP重定向负载均衡服务器,服务器根据算法要求用户重定向,用户收到重定向请求后,再次请求真正的集群
- 优点:简单易用;
- 缺点:性能较差。
DNS域名负载均衡
DNS域名解析负载均衡就是在用户请求DNS服务器,获取域名对应的IP地址时,DNS服务器直接给出负载均衡后的服务器IP。
- 优点:交给DNS,不用我们去维护负载均衡服务器;
- 缺点:当一个应用服务器挂了,不能及时通知DNS,而且DNS负载均衡的控制权在域名服务商那里,网站无法做更多的改善和更强大的管理。
反向代理服务器(重点)
在用户的请求到达反向代理服务器时(已经到达网站机房),由反向代理服务器根据算法转发到具体的服务器。常用的Apache,Nginx都可以充当反向代理服务器。
- 优点:部署简单;
- 缺点:代理服务器可能成为性能的瓶颈,特别是一次上传大文件。
IP层负载均衡(重点)
在请求到达负载均衡器后,负载均衡器通过修改请求的目的IP地址,从而实现请求的转发,做到负载均衡。
- 优点:性能更好;
- 缺点:负载均衡器的宽带成为瓶颈。
数据链路层负载均衡
在请求到达负载均衡器后,负载均衡器通过修改请求的MAC地址,从而做到负载均衡,与IP负载均衡不一样的是,当请求访问完服务器之后,直接返回客户。而无需再经过负载均衡器。
集群调度转发算法
(学生要了解,重点了解前6种算法,后面几种用的较少)
rr轮询调度算法
顾名思义,轮询分发请求。
- 优点:实现简单
- 缺点:不考虑每台服务器的处理能力
wrr加权调度算法
我们给每个服务器设置权值Weight,负载均衡调度器根据权值调度服务器,服务器被调用的次数跟权值成正比。
优点:考虑了服务器处理能力的不同
sh原地址散列算法
提取用户IP,根据散列函数得出一个key,再根据静态映射表,查处对应的value,即目标服务器IP。过目标机器超负荷,则返回空。
优点:实现同一个用户访问同一个服务器, 解决了session 问题
dh目标地址散列算法
原理同上,只是现在提取的是目标地址的IP来做哈希。
优点:实现同一个用户访问同一个服务器。
lc最少连接算法
优先把请求转发给连接数少的服务器。
优点:使得集群中各个服务器的负载更加均匀。
wlc加权最少连接算法
在lc的基础上,为每台服务器加上权值。算法为:(活动连接数 * 256 + 非活动连接数) ÷ 权重,计算出来的值小的服务器优先被选择。
优点:可以根据服务器的能力分配请求。
。。。
集群请求返回模式问题
NAT
负载均衡器接收用户的请求,转发给具体服务器,服务器处理完请求返回给均衡器,均衡器再重新返回给用户。
DR
负载均衡器接收用户的请求,转发给具体服务器,服务器出来玩请求后直接返回给用户。需要系统支持IP Tunneling协议,难以跨平台。
TUN
同上,但无需IP Tunneling协议,跨平台性好,大部分系统都可以支持。
集群Session一致性问题
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共享的支持。
应用:电商购物,视频点播
解决了以上的问题之后,系统的结构如下:
上图可以横向迅速扩展
阶段四、数据库读写分离化
上面我们总是假设数据库负载正常,但随着访问量的的提高,数据库的负载也在慢慢增大。那么可能有人马上就想到跟应用服务器一样,把数据库一份为二再负载均衡即可。
但对于数据库来说,并没有那么简单。假如我们简单的把数据库一分为二,然后对于数据库的请求,分别负载到A机器和B机器,那么显而易见会造成两台数据库数据不统一的问题。那么对于这种情况,我们可以先考虑使用读写分离和主从复制的方式。
读写分离后的系统结构如下:
这个结构变化后也会带来两个问题:
- 主从数据库之间数据同步问题。
- 应用对于数据源的选择问题。
解决方案:
- 使用MySQL自带的Master + Slave的方式实现主从复制。
- 采用第三方数据库中间件,例如MyCat。MyCat是从Cobar发展而来的,而Cobar是阿里开源的数据库中间件,后来停止开发。MyCat是国内比较好的MySql开源数据库分库分表中间件。
阶段五、用搜索引擎缓解读库的压力
数据库做读库的话,常常对模糊查找力不从心,即使做了读写分离,这个问题还未能解决。以我们所举的交易网站为例,发布的商品存储在数据库中,用户最常使用的功能就是查找商品,尤其是根据商品的标题来查找对应的商品。对于这种需求,一般我们都是通过like功能来实现的,但是这种方式的代价非常大,而且结果非常不准确。此时我们可以使用搜索引擎的倒排索引来完成。
搜索引擎的优点
搜索引擎具有的优点:它能够大大提高查询速度和搜索准确性。
引入搜索引擎的开销
- 带来大量的维护工作,我们需要自己实现索引的构建过程,设计全量/增加的构建方式来应对非实时与实时的查询需求。
- 需要维护搜索引擎集群
搜索引擎并不能替代数据库,它解决了某些场景下的精准、快速、高效的“读”操作,是否引入搜索引擎,需要综合考虑整个系统的需求。
引入搜索引擎后的系统结构如下:
阶段六、用缓存缓解读库的压力
常用的缓存机制包括页面级缓存、应用数据缓存和数据库缓存。
应用层和数据库层的缓存
随着访问量的增加,逐渐出现了许多用户访问同一部分热门内容的情况,对于这些比较热门的内容,没必要每次都从数据库读取。我们可以使用缓存技术,例如可以使用Google的开源缓存技术Guava或者使用Memecahed作为应用层的缓存,也可以使用Redis作为数据库层的缓存。
另外,在某些场景下,关系型数据库并不是很适合,例如我想做一个“每日输入密码错误次数限制”的功能,思路大概是在用户登录时,如果登录错误,则记录下该用户的IP和错误次数,那么这个数据要放在哪里呢?假如放在内存中,那么显然会占用太大的内容;假如放在关系型数据库中,那么既要建立数据库表,还要建立对应的Java bean,还要写SQL等等。而分析一下我们要存储的数据,无非就是类似{ip:errorNumber}这样的key:value数据。对于这种数据,我们可以用NOSQL数据库来代替传统的关系型数据库。
页面缓存
除了数据缓存,还有页面缓存。比如使用HTML5的localstroage或者Cookie。除了页面缓存带来的性能提升外,对于并发访问且页面置换频率小的页面,应尽量使用页面静态化技术。
- 优点:减轻数据库的压力, 大幅度提高访问速度;
- 缺点:需要维护缓存服务器,提高了编码的复杂性。
值得一提的是:
缓存集群的调度算法不同与上面提到的应用服务器和数据库。最好采用一致性哈希算,这样才能提高命中率。
加入缓存后的系统结构如下: