大型网站架构核心要素之伸缩性:伸缩性架构

前言

续上节大型网站架构核心要素性能之后,我们今天要讲的是第三个要素:伸缩性,所谓的网站伸缩性是指不需要改变网站的软硬件设计,仅仅通过改变部署的服务器数量就可以扩大或者缩小网站的服务处理能力。

前面我们已经介绍过一个大型网站都是从小网站一步步演变过来的,在这个渐进式的演化过程中,最重要的手段就是使用服务器集群,通过不断向集群中添加新的服务器来增强整个集群的处理能力,这就是网站系统的伸缩性架构,只要技术上能够做到向集群中加入服务器的数量和集群的处理能力成线性关系,那么网站就可以以此手段不断提升自己的规模和处理能力。

上面说的这个渐进式演化过程,说的都是“伸”,也就是说,网站的规模和服务器规模总是在不断扩大,但是这个过程也可能因为运营上的策划调整,需要进行“缩”,例如电商网站的年中大促,仅仅是在活动的那几天访问量和交易量规模突然爆发式增长,这个时候就得向集群中增加服务器的数量来满足此时网站的流量访问,活动结束后,这个访问量和交易量又恢复到正常水平,这时候我们就需要将新加入的那些服务器下线以此节约成本,这才是网站真正的伸缩性。

网站的伸缩性我们主要从下面4个方向展开介绍:网站架构的伸缩性设计应用服务器集群的伸缩性设计分布式缓存集群的伸缩性设计数据存储服务器集群的伸缩性设计

网站架构的伸缩性设计

一般来说网站的伸缩性设计可以分为2类,一类是根据功能进行物理分离实现伸缩(不同的服务器部署不同的服务,提供不同的功能),另一类是单一功能通过集群实现伸缩(集群内的多台服务器部署相同服务,提供相同的功能)

不同功能进行物理分离实现伸缩

网站早期都是通过增加服务器提高网站处理能力,新增服务器总是从现有服务器中分离部分功能和服务,如下图:

每次分离都会有更多的服务器加入,使用新增加的服务器处理某种特定服务,具体的分离手段我们又可以分为2类:纵向分离和横向分离

纵向分离(分层后分离):将业务处理流程上的不同部分分离部署

横向分离(业务分割后分离):将不同的业务模块分离部署,横向分离的粒度可以非常小,甚至某些热点访问的一个页面都可以独立服务,具体粒度根据网站实际情况划分;

单一功能通过集群规模实现伸缩

将不同功能分离部署可以实现一定程度上的伸缩性,但是随着网站访问量的逐步增加,即使分离到最小粒度独立部署,单一服务器也不能满足业务规模要求。因此必须使用服务器集群,即将相同服务部署在多台服务器上构成一个集群整体对外服务(有句话形容最合适:当一头牛拉不动车的时候,不要企图去寻找一头更强壮的牛来,而是应该用两头牛来拉车)

应用服务器集群的伸缩性设计

上节我们提过,应用服务器应该设计成无状态的,也就是说应用服务器不存储请求上下文信息,如果将部署有相同应用的服务器组成一个集群,每次用户请求都可以发送到集群中任意一台服务器上处理,任何一台服务器处理的结果都是一样的(幂等性),这样只要将用户请求按照某个规则分发到集群上就可以构成应用服务器集群如下所示:

如果HTTP请求转发器可以感知或者可以配置集群的服务器数量可以及时发现集群中新加入或者下线的机器,并能向新上线的机器分发请求,停止向已下线的机器分发请求,那么就实现了应用服务器集群的伸缩性,我们将这种HTTP请求转发器称之为负载均衡器,具体负载均衡器的实现技术有下面这几种:

HTTP重定向负载均衡:HTTP重定向服务器是一台普通的应用服务器,其唯一的功能就是根据用户的HTTP请求计算一台真实的web服务器地址,并将该web地址写入HTTP重定向响应中(响应码302)返回给用户浏览器,然后浏览器重定向到真实的web服务器上完成访问,整个过程如下图:

这种负载方案的优点:是比较简单;

缺点:浏览器需要两次请求服务器才能完成一次访问,性能较差;重定向服务器自身的处理能力也存在瓶颈,整个集群的伸缩性规模有限;使用302响应码重定向有可能会被搜索引擎判断为SEO作弊,降低搜索排名。

DNS域名解析负载均衡:在DNS域名服务器中配置多个A记录例如:(www.xxx.com IN A 139.19.0.1、www.xxx.com IN A 139.19.0.2 ... ),每次域名解析请求都会感觉负载均衡算法计算一个不同的IP地址返回给浏览器,浏览器再根据IP重新访问服务器,过程如下:

DNS域名解析负载均衡的优点:将负载均衡的工作交给了DNS,省掉了网站维护负载均衡器的麻烦,同时很多的dns还支持基于地理位置的域名解析,即将域名解析成离用户最近的一个服务器地址,这样加快了用户访问速度,改善性能;

缺点:但是母亲的DNS是多级解析,每一级DNS都有可能缓存下A记录,当下线某台服务器,即使修改了DNS的A记录,也不是立即生效,这段时间内,DNS仍然可能解析到已经下线的服务器;此外DNS负载均衡的控制权在域名服务商那里,网站无法对其进行更多的改善和管理;

实际应用中,某些大型网站是部分使用DNS域名解析,利用域名解析作为第一级负载均衡,也就是说解析得到的不是真正的web服务器,而是同样提供负载均衡的内部服务器,这组内部服务器再次进行负载均衡,请求分发。

反向代理负载均衡:之前我们提过反向代理缓存资源,实际上在部署位置上,反向代理服务器处于web服务器前面,这个位置正好也是负载均衡器的位置,所以大多数的反向代理服务器都提供负载均衡的能力,管理一组web服务器,将请求根据负载均衡算法转发到不同的web服务器上,web服务器处理完请求也需要通过反向代理返回给用户,由于web服务器不直接对外提供访问,所以web服务器不需要使用外部IP(内网IP加快访问速度),而反向代理则需要配置双网卡和内部外部两套IP地址,过程如下:

反向代理服务器转发请求在HTTP协议层面,因此也称为应用层负载均衡。优点:和反向代理功能集成在一起,部署简单;

缺点:反向代理服务器是所有请求和响应的中转站,其性能会成为瓶颈;

IP负载均衡:通过网络层修改目标地址进行负载,用户请求数据包到达负载均衡器后,负载均衡器在操作系统内核进程中获取网络数据包,根据负载均衡算法计算得到一台web服务器的真实IP,然后把数据目标IP换成web服务器的IP,真实的web服务器处理完请求之后,响应数据包回到负载均衡器,负载均衡器再将数据包源地址修改为自身IP(负载均衡器IP地址)返回给用户浏览器,整个过程如下图所示:

这里的关键点在于:真正处理请求的web服务器响应数据如何返回给负载均衡器,主要2种方案,一种是负载均衡器在修改目标IP地址同时修改源地址,将数据包源地址设为自身IP,这叫地址转换(SNAT),这样web服务器的响应就会再回到负载均衡器;另一种方案是将负载均衡器同时作为真实物理服务器集群的网关服务器,这样所有响应数据都会到达负载均衡器;

优点:IP负载在内核进程完成数据分发,性能比较好;

缺点:所有的响应都必须经过负载均衡器,集群的最大响应数据吞吐量就得受限于负载器的网卡带宽,对于大量数据的响应就不太友好了;那么能不能让响应直接返回浏览器,不走负载均衡器呢?

数据链路层负载均衡:在通信协议的数据链路层修改mac地址进行负载均衡,负载过程中不修改IP地址,只修改目标mac物理地址,通过配置真实的物理服务器集群所有机器虚拟IP和负载均衡器IP地址一致,从而达到不修改数据包就能进行数据分发的目的,由于实际处理的web服务器IP(这里指虚拟IP)和数据请求目标IP一致,不需要经过负载器进行地址转换,所有可以直接将数据响应返回给用户浏览器,避免负载均衡器网卡带宽成为瓶颈,这种方式又称为直接路由方式(DR),整个过程如下图:

上述这种数据传输方式又叫三角传输模式,是目前应用比较广泛的一种负载均衡手段,在Linux平台上一般使用链路层负载均衡开源产品LVS。

负载均衡算法:负载均衡器的实现分为两个部分:

根据负载均衡算法和web服务器列表计算得到集群一台web服务器地址

将请求数据发送到该地址对应的web服务器上

前面几种负载方案中我们都有提过怎么将数据发生给web服务器,而具体的负载均衡算法没有细说,接下来我们讲下,主要分几下几种:

轮询:所有请求依次分发到每台应用服务器上,即每台服务器需要处理的请求数目都是一样的,适合于所有服务器硬件都对等的环境;

加权轮询:根据应用服务器硬件性能,在轮询的基础上,按照配置的权重将请求分发给每个服务器,高性能的服务器能分配更多请求;

随机:请求被随机分配到各个服务器,很多情况下,这种方案简单实用,因为好的随机数本身就很均衡,即使应用服务器硬件配置不同,也在随机的基础上加个加权随机;

最少连接:记录每个服务器正在处理的连接数(请求数),将新到的请求分发到最少连接的服务器上,这是最合理的均衡算法,可以最大化利用每台服务器,同样,也可以在最少连接基础上加个加权最少连接;

源地址散列:根据请求来源IP进行hash计算,得到应用服务器,这样来自同一个IP地址的请求总会在同一个服务器上被处理,该请求的上下文信息也可以保存在这台服务器上,在一个会话周期内重复使用,从而实现会话黏滞;但是这种方案会存在负载不均衡现象(某台服务器处理的请求特别多)。

分布式缓存集群的伸缩性设计

之前我们有提过分布式缓存,不同于应用服务器集群的伸缩性设计,不能使用简单的负载均衡手段来实现,和应用服务器集群不同,分布式缓存服务器集群中不同服务器中缓存的数据各不相同,缓存访问请求不可以在缓存集群中任意一台处理,必须先找到缓存有需要数据的服务器,然后才能访问,这个特点会严重制约了分布式缓存的伸缩性设计,因为新上线的缓存服务器没有缓存任何数据,而已下线的缓存服务器还存着网站的许多热点数据。必须让新上线的缓存服务器对整个分布式缓存集群影响最小,也就是说新加入的缓存服务器应使整个缓存服务器集群已经缓存的数据尽可能还被访问到,这是分布式缓存集群伸缩性设计最主要的目标。

简单路由算法:余数hash,用服务器数目除以缓存数据KEY的Hash值,余数作为服务器列表下标编号,对余数hash路由算法也可以加权路由;如果不考虑缓存集群伸缩性的话,余数hash基本满足大多数缓存路由需求,但是当分布式缓存集群需要扩容的时候,事情就比较糟糕了,假设,我们将3台缓存服务器增加至4台,更改服务器列表,仍旧使用余数hash,如果此时KEY就可能路由到别的服务器上了,而这台服务器上并没缓存KEY的数据,缓存命中失败,命中失败的概率为99%(N/(N+1))。当大部分缓存下来的数据因为扩容而不能正确读取时,这些数据访问的压力就落到数据库身上了,将造成数据库的负载压力,严重的情况可能导致数据库宕机,很显然这是不能被接受的。一种解决方案是在网站访问量最少的时候扩容,这时候对数据负载压力最小,但是这并不是合理的方案,那么请看改进的路由算法;

分布式缓存一致性Hash算法:又叫一致性hash环的数据结构实现KEY到缓存服务器的hash映射,如下图

具体算法过程:先构造一个长度为0-2^32的整数环(称为一致性Hash环),根据节点名称的hash值(分布范围跟hash环长度相等)将缓存服务器节点放置在这个hash环上,然后根据需要缓存的数据KEY值计算得到其Hash值,然后在hash环上顺时针查找距离这个key的值hash值最近的缓存服务器节点,完成key到服务器的hash映射查找;

当缓存服务器需要扩容的时候,只需要将新加入的节点的hash值放入一致性hash环中,由于key是顺时针查找距离最近的节点,因此新加入的节点只影响整个环的一小段,如下所示:

所受影响的只是节点1到节点3之间的缓存数据,所以当缓存的节点越多,所影响的范围就越小。再看看缓存命中,图中由3个节点变成4个节点,之前访问节点2的数据就会改成访问节点3,这部分数据将会缓存命中失败,即节点1到节点3的数据无法命中,其他节点不变,此时缓存命中率有75%,所以节点越多缓存命中也越高;但是正如我所说的这种方式只会影响到1节点到3节点之间的数据,原来访问2节点的数据现在有一部分需要访问3,其他节点不变,这就意味着其他节点缓存的数据量和负载压力是2和3节点的2倍,如果4个节点机器性能都是一样的,那么这种结果很明显不是我们想要的。

要解决一致性hash算法带来的负载不均衡问题,我们可以通过使用虚拟层的手段:将每台物理缓存服务器虚拟为一组虚拟缓存服务器,将虚拟服务器的hash值放置在hash环上,key在环上先找到虚拟服务器节点,在得到物理服务器的信息。这样新加入的物理服务器节点是将一组虚拟机加入环中,如果虚拟节点的数目足够多,这组虚拟节点将会影响同样多数目的已经在环上存在的虚拟节点,这些已经存在的虚拟节点又对应不同的物理节点,最终的结果就是:新加入一台缓存服务器,将会较为均匀地影响原来集群中已经存在的所有服务器,也就是说分摊原有缓存服务器集群中所有服务器的一小部分负载,总的影响范围和之前的相同。只是将影响的点均匀分布了。

显然每个物理节点对应的虚拟节点越多,各个物理节点之间的负载越均衡,新加入的物理服务器对原有的物理服务器的影响越保持一致,显然增加节点虚拟节点并不会增加缓存命中,只是保证了负载均衡分布。

数据存储服务器集群的伸缩性设计

和缓存服务器集群的伸缩性设计不同,数据存储服务器的伸缩性对数据的持久性和可用性提出了更高的要求。

缓存的目的是加速数据读取速度并减轻数据存储服务器的负载压力,因此部分缓存数据的丢失不影响业务的正常处理,因为数据还可以从数据库等存储服务器上获取。

而数据存储服务器必须保证数据的可靠存储,任何情况下都必须保证数据的可用性和正确性,因此数据存储服务器集群的伸缩性设计相对更复杂一些,具体来说又可以分以下两大类:

关系数据库集群的伸缩性设计:关系数据库凭借简单强大的SQL和众多成熟的数据库产品,占据从企业应用到网站系统大部分业务数据存储服务,市场上的关系数据库都支持数据复制功能,使用这个功能就可以对数据库进行简单的伸缩,以MySQL为例子:

这种架构中,虽然多台服务器部署MySQL实例,但是他们角色有主从之分,数据写操作都在主服务器上,由主服务器将数据同步到集群中其他服务器,数据读操作及数据等离线操作在从服务器上进行;

除了数据库主从读写分离,前面还提到的业务分割模式也可以用在数据库,不同业务数据表部署在不同的数据库集群上,即数据的分库,这种方式的制约条件是跨库的表不能进行join操作;

在大型网站实际应用中,即使进行了分库和主从复制,对一些单表数据仍然很大的表,还需要进行分片,将一张表拆开分别存储在多个数据库中,即所谓的分表;

NoSQL数据库的伸缩性设计:在计算数据存储领域,还有一种数据库正在逐渐占领市场,那就是非关系型数据库NoSQL,NoSQL就是为了解决大型网站遇到的关系数据库难以克服的缺陷:糟糕的海量数据处理能力和僵硬的设计约束;

NoSQL主要指非关系的、分布式的数据库设计模式,应该理解为是关系数据库的补充,而不是替代方案更合适,一般来说,NoSQL数据库产品都放弃了关系数据库的两大重要基础:以关系代数为基础的结构化查询语言和事务一致性保证(ACID),而强化了其他一些大型网站更关注的特性:高可用性和可伸缩性。

开源社区各种NoSQL产品很多,支持的数据结构和伸缩性也不相同,这里我们就以Apache HBase为例吧:

hbase为可伸缩海量数据存储而设计,实现面向在线业务的实时数据访问延迟,hbase的伸缩性主要依赖其可分裂的HRegion及可伸缩的分布式文件系统HDFS,整体架构图如下:

所有HRegion的信息(存储的key值区间,所在HRegionServer地址、访问端口号等)都记录在HMaser服务器上,为了保证高可用,HBase启动多个HMaser,并通过Zookeeper获得主HMaser的地址,输入key值获得这个key所在的HRegionServer地址,然后请求HRegionServer上的HRegion,获取需要的数据。

数据写入过程也是一样,需要先得到HRegion才能继续操作,HRegion会把数据存储在若干个叫做HFile格式的文件中,这些文件使用HDFS分布式文件系统存储,在整个集群内分布并高可用,当一个HRegion中的数据量太多时,HRegion(包括HFile文件)会分裂成两个HRegion,并根据集群中服务器负载进行迁移,如果集群中有新的服务器加入,也就是说有新的HRegionServer加入,由于负载较低,也会把HRegion迁移到新的HRegionServer上并记录到HMaster上,从而实现hbase的线性伸缩。

注:对于本文提到的HDFS,分布式缓存集群,后期我们在讲分布式文件系统和redis时会详细讲解

  • 发表于:
  • 原文链接:http://kuaibao.qq.com/s/20180209G05WJK00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

扫码关注云+社区

领取腾讯云代金券