6、伸缩性-永无止境

架构的伸缩性设计

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

纵向分离(分层后分离)
横向分离(业务分割后分离)
在这里插入图片描述
在这里插入图片描述

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

当一头牛拉不动车的时候,不要去寻找一头更强壮的牛,而是用两头牛来拉车。

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

HTTP 重定向负载均衡

在这里插入图片描述
根据请求,计算一个服务器地址,利用 HTTP 302 重定向到服务器地址。
优点:
比较简单。
缺点:
浏览器需要两次请求服务器才能完成一次访问,性能较差;
重定向服务器自身的处理能力可能成为性能瓶颈;
302 重定向可能被搜索引擎判断为 SEO 作弊,降低搜索排名。

DNS 域名解析负载均衡

在这里插入图片描述
在 DNS 服务器中配置多个 A 记录,如: www.xxx.com IN A 114.100.80.1 和 www.xxx.com IN A 114.100.80.2。每次域名解析请求都会根据负载均衡算法计算一个不同的 IP 地址返回。
优点:
负载均衡工作转交给 DNS,省去了一些工作内容。
DNS 还支持基于地理位置的域名解析。
缺点:
DNS 多级解析,每一级 DNS 都可能缓存 A 记录,当某台服务器下线后,即使修改了 A 记录,也得等一会才能生效。

可以部分使用 DNS 域名解析,解析作为第一级负载均衡手段,即解析得到的一组服务器并不是真正提供 Web 服务的物理服务器,而是同样提供负载均衡的内部服务器。

反向代理负载均衡

在这里插入图片描述
反向代理服务器转发请求在 HTTP 协议层面,因此也称为 应用层负载均衡。
优点:
和反向代理服务器功能集成在一起,部署简单。
缺点:
反向代理服务器是所有请求和响应的中转站,性能可能会成为瓶颈。

IP 负载均衡

在这里插入图片描述
SNAT 源地址转换!
优点:
IP 负载均衡在内核进程中完成数据分发,比反向代理负载均衡(在应用层分发)有更好的性能;
缺点:
所有请求和响应都要经过负载均衡服务器,集群最大相应数据屯独立受制于负载均衡服务器网卡带宽;

数据链路负载均衡

在这里插入图片描述
在通信协议的数据链路层修改 mac 地址进行负载均衡。
这种负载均衡方式又称作直接路由方式(DR)。
也称为三角模式。
在 Linux 上比较好的开源产品是 LVS(Linux Virtual Server)

负载均衡算法

负载均衡服务器的实现可以分为 2 个部分:
1、根据负载均衡算法和 Web 服务器列表得到集群中一台 Web 服务器的地址。
2、将请求数据发送到该地址对应的 Web 服务器上。

轮询(Round Robin,RR)

适用于所有服务器硬件都相同的场景。

加权轮询(Weighted Round Robin,WRR)

根据硬件性能,高性能服务器能分配更多请求。

随机(Random)

简单实用,随机数本身就很均衡。
也可以改一改成为加权随机法。

最少连接(Least Connections)

记录每个服务器正在处理的连接数(请求数),新请求发给连接数最少的那个。
同样可以实现个加权最少连接。

源地址散列(Source Hashing)

一个 IP 地址进行 Hash 得到应用服务器地址,这样来自同一个 IP 地址的请求总在同一个服务器上处理,该请求的上下文信息可以存储在这台服务器上,在一个会话周期内复用,从而实现会话粘滞。

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

Memcached 分布式缓存集群的访问模型

set(key, data)、get(key)
路由根据 key 计算对应应该从哪台服务器写缓存,从哪台服务器读缓存。

Memcached 分布式缓存集群的伸缩性挑战

对于集群的管理,路由算法至关重要,决定着究竟该访问集群中的哪台服务器。
简单的路由算法就是余数 Hash。
对于余数 Hash,当集群需要扩容的时候,事情就比较糟糕了。
假设 3 台缓存服务器需要扩容到 4 台,更改服务器列表,依然使用余数 Hash,用 4/key 的 Hash 值大约有 75%(3/4) 不能正确命中,随着服务器集群规模的增大,这个比例直线上升!
当 100 台集群中增加一台,不能命中的概率是 99%( n/(n+1) )。

大部分的业务数据读操作请求事实上是通过缓存获取的,只有少量的操作请求会访问数据库。
当大量的缓存出现不能命中时,对于数据库的压力就很大了,甚至宕机。

一种解决方法就是在网站访问了最少的时候进行缓存服务器集群扩容,然后通过模拟请求的方法来预热缓存。
另一种方法就是通过下面的算法了!

分布式缓存的一致性 Hash 算法

一致性 Hash 算法通过一个叫做一致性 Hash 环的数据结构实现 KEY 到缓存服务器的 Hash 映射。
image.png
具体算法过程为:
先构造一个长度为 2^32 的整数环,这个环被称为一致性 Hash 环。
根据节点名称的 Hash 值(其分布范围为[0, 2^32-1])将缓存服务器节点放置在这个 Hash 环上。
然后根据需要缓存的数据 KEY 的值计算得到其 Hash 值(分布范围同上)。
然后在 Hash 环上顺时针找举例这个 KEY 的 Hash 值最近的缓存服务器节点,完成 KEY 服务器的 Hash 映射查找。

当缓存服务器集群需要扩容的时候,只需要将新加入的节点名称(NODE3)的 Hash 值放入一致性 Hash 环中,由于 KEY 是顺时针查找举例其最近的节点,因此新加入的节点只影响整个环中的一小段。
image.png
3台 -> 4台扩容时候的命中率是 75%,远高于余数 Hash 的 25%。

在具体应用中,这个长度为 2^32 的一致性 Hash 环通常使用二叉查找树实现,Hash 查找过程实际上是在二叉查找树中查找不小于查找数的最小数值,这个数的最左叶子节点和最右叶子节点连接构成环。

但是!这个过程还存在一些小问题!

新加入的节点 NODE3 只影响了原来的节点 NODE1,也就是说一部分原来需要访问 NODE1 的缓存数据现在需要访问 NODE3(概率上市 50%)。
但是原来的节点 NODE0 和 NODE2 不受影响,这就意味着 NODE0 和 NODE2 缓存数据量和负载压力是 NODE1 和 NODE3 的两倍。
如果这 4 台机器性能是一样的,那么这种结果不是想要的。

怎么办?

计算机的任何问题都可以通过增加一个虚拟层来解决!
计算机硬件、计算机网络、计算机软件都莫过于此。
计算机网络7层,每一层可以看做是下一层的虚拟层;计算机操作系统可以看做是硬件的虚拟层;JVM 可以看作是操作系统的虚拟层;分层软件架构事实上也是如此。

将每台物理缓存服务器虚拟为一组虚拟缓存服务器,将虚拟服务器的 Hash 值放置在 Hash 环上,KEY 在环上先找到虚拟服务器节点,再得到物理服务器信息。

这样在新加入物理服务器节点时,是将一组虚拟节点加入环中,如果虚拟节点的树木足够多,这组虚拟节点将会影响同样多树木的已经在换上存在的虚拟节点,这些已经存在的虚拟节点又对应不同的物理节点。
最终结果是:新加入一台缓存服务器,将会较为均匀的影响原来及群众已经存在的所有服务器,也就是说分摊原有缓存服务器集群中所有服务器的一小部分负载。

image.png
如上图,加入 NODE3 对应的一组虚拟节点为 V30 V32 V32,加入到一致性 Hash 环上后,影响 V01 V12 V22
这三个被影响的虚拟节点对应 NODE0 NODE1 NODE2 三个物理节点。
最终加入一个节点,影响三个物理节点,理想情况下,每个物理节点收到影响的缓存数据量是 1/4 (x/ (x+n)),N 为原物理节点数,X 为新加入物理节点数。也就是及群众已经被缓存的数据有 75% 可以被命中。

理论上虚拟节点数越多,物理节点之间的负载就越均衡。
但是太多影响性能,太少负载不均衡,一般来说经验是 150,根据集群规模和均衡的精度需求,具体对待。

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

关系数据库集群的伸缩性设计

分库分表,质数。

NoSQL 数据库的伸缩性设计

NoSQL 更关注高可用性和可伸缩性。
舍弃了以关系代数为基础的结构化查询语言(SQL)和事务一致性保证(ACID)。


EOF!