前言
负载均衡
- 概念:一种将网络流量或业务请求均匀分配到多个服务器或服务实例上的技术,旨在提高系统的可用性、性能和可伸缩性。
- 作用:
- 提高性能:通过将请求分散到多个实例上,避免单个实例因请求过多而过载,从而提高系统的整体处理能力。
- 增强可用性:当某一实例发生故障时,负载均衡器可以自动将流量重定向到其他健康的实例,确保服务仍然可用。
- 实现可伸缩性:可以根据系统的负载情况,动态地添加或删除服务实例,以适应业务需求的变化。
- 实现方式
- 服务器端负载均衡:SpringCloud的LoadBalancer、Netflix的Ribbon
- 客户端负载均衡:本地负载均衡器【可基于特定算法手写】
- 中间层负载均衡:Nginx
负载均衡器
- 概念:是一种软件或硬件设备,用于在多个服务器或服务实例之间分发网络流量或业务请求。
常见负载均衡算法【策略】
- 随机算法
- 随机选取集群中的一台服务器访问。
- 随着客户端调用服务端的次数增多,其实际效果越来越接近于平均分配调用量到后端的每一台服务器。
- 轮询算法【默认】
- 按顺序向每个服务实例发送请求,适用于系统中的节点处理能力相同的情况。
- 权重算法
- 给配置高、负载低的机器配置更高的权重,让其处理更多的请求;
- 而配置低、负载高的机器,给其分配较低的权重,降低其系统负载。
通俗理解
- 在微服务架构中,为保证项目的高可用性【如故障转移】和可拓展性【如动态服务注册】,通常需要对特定服务进行集群;
- 而调用服务者[消费者]通过RPC远程调用目标服务[生产者]的接口时则存在:"具体应该调用集群中的哪个服务?"的问题;
- 此时需要通过负载均衡实现具体服务的调用[目标服务实例的筛选];
简单概述
负载均衡 = 负载均衡器 + 负载均衡算法【策略】 —》 按特定策略从服务集群中挑选一个服务实例【服务器】调用。
实践(以轮询算法为例)
package com.xiaohan.loadbalance;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @program: SpringCloud_Demo
* @description: 权重算法实现
* @author: 韩小豪
* @create: 2025-01-18 16:09
**/
/**
* 当集群中两个服务的权重设置如下时:
* serviceInstances[0] = 192.168.2.139:8080 --> weight=2
* serviceInstances[1] = 192.168.2.139:8081 --> weight=1
* --
* 基本思想:
* 1.从nacos服务注册中心端获取到两个服务的权重后,创建一个新的服务列表
* 2.因为第一个服务的权重为2,因此新的服务列表中应存入两个该服务
* [
* 192.168.2.139:8080,
* 192.168.2.139:8080,
* 192.168.2.139:8081
* ]
* 3.当需要访问该目标服务时,从新的服务列表中通过轮询的策略分配可用服务实例,具体表现为:
* 第1次调用: 1 % 3 = 1 [192.168.0.2:8080]
* 第2次调用: 2 % 3 = 2 [192.168.0.1:8081]
* 第3次调用: 3 % 3 = 0 [192.168.0.2:8080]
* --
* 第4次调用: 4 % 3 = 1 [192.168.0.1:8080]
* 第5次调用: 5 % 3 = 2 [192.168.0.2:8081]
* 第6次调用: 6 % 3 = 0 [192.168.0.1:8080]
*/
@Component
public class WeightLoadBalance implements LoadBalance {
@Autowired
private DiscoveryClient discoveryClient;
//计数器:记录当前为第几次访问目标服务接口
private AtomicInteger count = new AtomicInteger(0);
@Override
public ServiceInstance getInstance(String serviceId) {
//获取所有服务实例
List<ServiceInstance> serviceInstances = discoveryClient.getInstances(serviceId);
//判空
if(serviceInstances == null || serviceInstances.isEmpty())
{
return null;
}
//新的服务列表
ArrayList<ServiceInstance> newInstances = new ArrayList<>();
//遍历服务列表,通过每个服务的权重往新的服务列表中添加
for( ServiceInstance serviceInstance : serviceInstances){
//获取每个服务实例的权重【Double类型,一般设置为整数】
double weight = Double.parseDouble(serviceInstance.getMetadata().get("nacos.weight"));
//往新的服务列表中添加对应权重数的服务实例
for(int i = 0;i < weight;i++){
newInstances.add(serviceInstance);
}
}
//对新的服务列表采取轮询的策略分配可用服务实例
int index = count.getAndIncrement() % newInstances.size(); //getAndIncrement返回旧值后+1、incrementAndGet返回+1后的新值
//返回目标服务实例
return newInstances.get(index);
}
}
结语
- 了解原理和具体实现才利于更好使用【理解 > 会用】
- 基于随机和轮询算法的实现见首页其他文章