背景
一般生产环境单机所能承受的QPS压力为2w左右,过大的压力会导致服务器爆炸。即便是单机能够撑住2w QPS,一般也不会这么做,生产环境一般会预留50%的冗余能力,防止QPS因为某个热门的活动而爆炸。当QPS超过单机所能承受的压力时,自然而然会想到引入分布式集群。那么,某一个请求会被哪台服务器处理呢,这是随机的,还是说按照一定的规则处理的?这就是负载均衡算法所要干的事。
负载均衡器
负载均衡器就是实现一种或者多种负载均衡算法的软件或者硬件设备。负载均衡器根据协议层的不同,通常又分为两种,第一种在四层传输层实现,第二种就是在七层应用层实现。
很多专用的硬件负载均衡器都支持在TCP层实现负载均衡,效率高。当然TCP层实现负载均衡有它的缺点,如无法保存长连接等。所以一般类似于BAT这种大公司,都是多层负载均衡配合的。
一般纯软件实现的通常在应用层来实现,这也是应用比较多的一种实现方式。目前比较流行的实现有Nginx、HAProxy、Keepalived等。当然Linux内核自带的LVS(Linux Virtual Server)就是四层的实现。
轮询(Round Robin)
轮询是一种很简单的实现,依次将请求分配给后端服务器。优点就是实现简单,请求均匀分配。
缺点也恰恰在于请求均匀分配,因为后端服务器通常性能会有差异,所以希望性能好的服务器能够多承担一部分。也不适合对长连接和命中率有要求的场景。
加权轮询(Weighted Round Robin)
加权本质是一种带优先级的方式,加权轮询就是一种改进的轮询算法,轮询算法是权值相同的加权轮询。需要给后端每个服务器设置不同的权值,决定分配的请求数比例。这个算法应用就相当广泛了,对于无状态的负载场景,非常适合。
优点解决了服务器性能不一的情况,缺点是权值需要静态配置,无法自动调节。也不适合对长连接和命中率有要求的场景。
随机Random
随机把请求分配给后端服务器。请求分配的均匀程度依赖于随机算法了,因为实现简单,常常用于配合处理一些极端的情况,如出现热点请求,这个时候就可以random到任意一台后端,以分散热点。当然缺点也不言而喻。
哈希Hash
哈希算法想必大家并不陌生,应用最为广泛。根据Source IP、 Destination IP、URL、或者其它,算hash值或者md5,再采用取模。比如有N台服务器: S1、S2、S3……Sn
hash值 % N
显然,相同的请求会被映射到相同的后端。这非常适合维护长连接和提高命中率。
但是它天生也有一些缺点。比如说,现在某个请求通过哈希被映射到S3上去了,如果S3宕机了,就不得不二次Hash,重新计算路由时会剔除宕机的后端。
hash值 % (N - 1)
这样会导致几乎所有请求路由产生变化。由此导致命中率的急剧下降。当然一般生产环境通过提供S3的备机来解决这种问题,但是主备之间切换也是需要时间,它们之间的数据同步也是有延时的。所以需要根据业务场景来权衡了。
扩容也会有类似的问题,计算路由公式变为:
hash值 % (N + 1)
为了解决这种问题,一般生产环境可能采用成倍扩容的方式。N -> 2N,这样求路由可以做到与原来保持一致。当然必不可少的造成机器资源的浪费。请各位看官自行权衡。
对于热点请求,这种Hash算法也可能成在雪崩效应,取决于采用何种Hash,基于URL还是基于IP等。总之,不能把热点请求路由到单机上,否则单机撑不住,会逐个逐个被打爆,也就是雪崩效应。
最小连接数LC
最小连接数(Least Connection),把请求分配给活动连接数最小的后端服务器。它通过活动来估计服务器的负载。比较智能,但需要维护后端服务器的连接列表。
加权最小连接数WLC
加权最小连接数(Weighted Least Connection),在后端服务器性能差异较大的情况下,可以优化LC的性能,高权值的服务可以承受更多的连接负载。
最短响应时间LRT
最短响应时间(Least Response Time),把请求分配给平均响应时间最短的后端服务器。平均响应时间可以通过ping探测请求或者正常请求响应时间获取。
RT(Response Time)是衡量服务器负载的一个非常重要的指标。对于响应很慢的服务器,说明其负载一般很高了,应该降低它的QPS。
之前有人说使用CPU占用率作为负载均衡的指标,只能说没理解CPU占用率的实质。理论上CPU占用率是越高越好,说明服务充分利用了CPU资源。但对于设计不合理的程序导致的CPU占用过高这是程序的设计问题,并不违背这条理论。
一致性Hash
介绍
一致性哈希算法(Consistent Hashing)在1997年由麻省理工学院提出的一种分布式哈希(DHT)实现算法,设计目标是为了解决因特网中的热点(Hot spot)问题。
原理
想象抽象哈希环,32位整数表示即可,2^32个桶位,后端节点s0, s1…sn等,hash映射到不同的桶位,假想首尾相连,形成环。(以下图是我无耻的盗过来的)
将所有后端节点node通过Hash映射到环上,如下图所示:
实际请求Job以同样的方式映射到哈希环上,如下图所示:
再按照顺时针的规则,请求Job沿着哈希环找到最近的节点。如图中,请求Job_1按照规则就分配到Node_1上,请求Job_k、Job_k+1分配到Node_n上面。
优势
-
节点宕机
假设后端节点Node_1宕机,按照规则,请求Job_1被分配到Node_k上,但是Job_k、Job_k+1、Job_i不受影响。说白了,仅仅影响宕机节点上的请求。
-
扩容
按照规则,仅仅请求Job_k被重新分配到Node_i上去了,其它请求Job_1、Job_k+1、Job_i都不受影响。说白了仅仅影响分流的很少部分请求。
- 引入虚拟节点
Hash算法不均匀必然会导致映射到请求环上不均匀,部分后端节点会承受比较多的请求,如果突然发生宕机的话,会把宕机节点的全部请求转移到下一个节点,会导致下一个节点请求量暴增,也可能会宕机,也可能出现类似的雪崩效应。
为了保证均匀性,将给每个物理节点虚拟出一定数量的虚拟节点,分散到哈希环上,需要尽可能地随机分散开。虚拟节点承载的请求实际是指向真实的物理节点。出现物理机节点宕机时,虚拟节点上的请求按照规则转移至下一个节点,避免雪崩效应。
当物理节点较少时,虚拟节点数需要更高来确保更好的一致性表现。