简介
在微服务架构中,负载均衡器是常见的一类组件,它可以将各种形式的网络Request负载“均摊”到不同的机器上,在不同场景选用不同的负载均衡算法,能够避免集群中部分服务器压力过大,而另一些服务器比较空闲的情况,让每台服务器获取到适合自己处理能力的负载。
负载均衡可分为软件负载均衡和硬件负载均衡,本篇主要分析软件负载均衡,这是我们后端开发比较经常接触的均衡器,常见的如HAProxy、LVS、Nginx、定制化的应用请求负载(RPC框架如Dubbo)等,诸如CDN、DNS也运用到了负载均衡算法。
根据网络层区分负载类型
通常会根据转发的协议类型区分为四层负载(TCP、UDP)与七层负载(HTTP、HTTPS),当然还有其他类型,例如三层甚至二层。
二层负载均衡会通过一个虚拟 MAC 地址接收请求,然后再分配到真实的 MAC 地址;
三层负载均衡会通过一个虚拟 IP 地址接收请求,然后再分配到真实的 IP 地址;
四层通过虚拟 IP + 端口接收请求,然后再分配到真实的服务器;
七层通过虚拟的 URL 或主机名接收请求,然后再分配到真实的服务器。
四层负载均衡
四层负载均衡根据传输层的信息来决定如何分发请求,通常应用会根据来源、目标 IP 地址和请求头中的端口直接进行分发,而不处理数据包(报文)内容,如AWS的ALB,通过报文中的IP地址和端口,再加上负载均衡设备所采用的负载均衡算法,最终确定选择后端哪台下游服务器。缺点是可以直接使用SYN Flood攻击后端机器,而七层负载受攻击的只能是负载均衡器。
七层负载均衡器
七层负载均衡器根据监控应用层来决定怎样分发请求,可以根据URL、请求头的内容、甚至cookie来进行分发。七层服务只能先与负载均衡设备进行TCP连接,然后负载均衡设备再与后端服务器建立另外一条TCP连接通道,因此会比四层负载多一层的连接,在网络性能损耗会更多一些。七层负载对应用具体报内容进行处理分发,可以做一些更加精细的定制化逻辑,如针对包大小分发不同的机器。
虽然七层负载均衡比四层负载均衡花费更少时间和计算资源,但实际上依照现在一般机器的配置,对性能影响甚微。
负载均衡算法
负载均衡器会根据不同的算法dispatch请求到不同的机器,以下介绍常见的几种负载均衡算法
1.随机算法
Random随机,可以按权重设置随机概率。比如有三台机器,对每个请求运行随机算法路由到不同的机器
int serverIndex = random.nextInt(serverSize);
优点是算法简单,调用量越大分布越均匀。调用量小的时候很容易不均
2.轮询及加权轮询
轮询(RoundRobbin)当服务器群中各服务器的处理能力相同时,且每笔业务处理量差异不大时,最适合使用这种算法。顺序把请求给到不同的机器,比如请求1给机器A,请求2给机器B。
j = i;
do {
j = (j + 1) % n;
i = j;
return Si;
} while (j != i);
return null;
如果各个服务器直接性能不一,还可以给不同的机器加权,给性能好的机器更多请求,为轮询中的每台服务器附加一定权重的算法。比如服务器1权重1,服务器2权重2,服务器3权重3,则顺序为1-2-2-3-3-3-1-2-2-3-3-3。
3.最小连接
最少连接(Least Connections)在多个服务器中,与处理连接数(会话数)最少的服务器进行通信的算法。即使在每台服务器处理能力各不相同,每笔业务处理量也不相同的情况下,也能够在一定程度上降低服务器的负载。
public static String getLeasetConnServer() {
Map<String, ConnectionsServer> serverMap = new TreeMap<>(ConnectionsServerManager.serverMap);
Iterator<String> iterator = serverMap.keySet().iterator();
ConnectionsServer minConnectionsServer = null;
while (iterator.hasNext()){
ConnectionsServer server = serverMap.get(iterator.next());
if(minConnectionsServer == null){
minConnectionsServer = server;
}
if(minConnectionsServer.getConnnections() > server.getConnnections()){
minConnectionsServer = server;
}
}
// TODO 记录各server的连接数
return minConnectionsServer.getServer();
}
4.哈希算法
相同参数的请求总是发到同一提供者,这种方式可以保证某个请求一定发到某台机器,比如使用ip哈希或者url哈希,通常还会使用一致性hash,当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。
普通的hash不固定节点数,即当node数发生变化(增加、移除)后,数据项会被重新“打散”,导致大部分数据项不能落到原来的节点上,从而导致大量数据需要迁移。一致性Hash使用虚拟节点,固定节点数,保证原来分配到的某个node,现在仍然应该分配到那个node,请求会更分散。
我们常用的Nginx有5种负载算法,轮询、加权、ip_hash、fair(按照响应时间负载,需要安装单独模块)、url_hash