Java LoadBalanceUtil 负载均衡、轮询加权
权重随机算法:https://www.cnblogs.com/vipsoft/p/19741845
负载均衡、轮询加权
LoadBalanceUtil
@Component
public class LoadBalanceUtil {
private List<Node> nodes;
/**
* 初始化 约定的invoker和权重的键值对
*/
public void init(Map<Invoker, Integer> invokersWeight){
if (invokersWeight != null && !invokersWeight.isEmpty()) {
nodes = new ArrayList<>(invokersWeight.size());
invokersWeight.forEach((invoker, weight) -> nodes.add(new Node(invoker, weight)));
} else {
nodes = null;
}
}
/**
* 算法逻辑:
* 1. 对于每个请求,遍历集群中的所有可用后端,对于每个后端peer执行:
* peer->current_weight += peer->effecitve_weight。
* 同时累加所有peer的effective_weight,保存为total。
* 2. 从集群中选出current_weight最大的peer,作为本次选定的后端。
* 3. 对于本次选定的后端,执行:peer->current_weight -= total。
*
* @Return ivoker
*/
public Invoker select() {
if (!checkNodes()) {
return null;
} else if (nodes.size() == 1) {
if (nodes.get(0).invoker.isAvalable()) {
return nodes.get(0).invoker;
} else {
return null;
}
}
Integer total = 0;
Node nodeOfMaxWeight = null;
for (Node node : nodes) {
total += node.effectiveWeight;
node.currentWeight += node.effectiveWeight;
if (nodeOfMaxWeight == null) {
nodeOfMaxWeight = node;
} else {
nodeOfMaxWeight = nodeOfMaxWeight.compareTo(node) > 0 ? nodeOfMaxWeight : node;
}
}
nodeOfMaxWeight.currentWeight -= total;
return nodeOfMaxWeight.invoker;
}
private boolean checkNodes() {
return (nodes != null && nodes.size() > 0);
}
public interface Invoker {
Boolean isAvalable();
String nodeName();
}
private static class Node implements Comparable<Node> {
final Invoker invoker;
final Integer weight;
Integer effectiveWeight;
Integer currentWeight;
Node(Invoker invoker, Integer weight) {
this.invoker = invoker;
this.weight = weight;
this.effectiveWeight = weight;
this.currentWeight = 0;
}
@Override
public int compareTo(Node o) {
return currentWeight > o.currentWeight ? 1 : (currentWeight.equals(o.currentWeight) ? 0 : -1);
}
}
}
invoker
if (totalCount > 0) {
//根据计算结果修改权重
for (LoadBalanceUtil.Invoker key : invokersWeight.keySet()) {
if (weightMap.containsKey(key.nodeName())) {
invokersWeight.put(key, weightMap.get(key.nodeName()));
}
}
//初始化权重
loadBalanceUtil.init(invokersWeight);
//根据AI空闲数量,获取待分配任务
List<TaskInfo> waitAssignList = scheduleService.listWaitAllocate(totalCount);
if (ObjectUtil.isEmpty(waitAssignList)) {
//没数据,停30秒。
Thread.sleep(cronInterval * 1000);
return ReturnT.SUCCESS;
}
for (TaskInfo item : waitAssignList) {
LoadBalanceUtil.Invoker invoker = loadBalanceUtil.select();
item.setAiServerName(invoker.nodeName());
item.setStatus(ReportStatus.ALLOCATED.getStatus());
item.setAllocationTime(new Date());
scheduleService.allocationServer(item);
String logMsg = StrUtil.format("{} 分配给 {}", item.getCustodyNo(), invoker.nodeName());
logger.info(logMsg);
}
}
要实现根据权重随机分配视频,有两种常见方案:
方案一:简单权重随机(每次独立随机)
如果不需要考虑历史分配记录,每次完全独立随机:
List<VideoInfo> list = videoInfoService.list(videoQueryWrapper);
VideoInfo videoInfo = null;
if (list != null && !list.isEmpty()) {
if (list.size() == 1) {
videoInfo = list.get(0);
} else {
// 计算总权重
int totalWeight = list.stream().mapToInt(VideoInfo::getWeight).sum();
// 生成随机数(1到总权重之间)
Random random = new Random();
int randomNumber = random.nextInt(totalWeight) + 1;
// 根据权重选择视频
int currentWeight = 0;
for (VideoInfo v : list) {
currentWeight += v.getWeight();
if (randomNumber <= currentWeight) {
videoInfo = v;
break;
}
}
}
param.setVideoId(videoInfo.getId());
param.setVideoTitle(videoInfo.getTitle());
param.setCombination(videoInfo.getCombination());
extractDetail(videoInfo);
memberPromoterRelationMapper.insert(param);
}
return videoInfo;
方案二:考虑历史分配的权重轮询
如果需要考虑历史分配,可以使用权重轮询算法:
// 添加静态变量记录分配次数
private static Map<Long, Integer> allocationCountMap = new ConcurrentHashMap<>();
// 方法实现
List<VideoInfo> list = videoInfoService.list(videoQueryWrapper);
VideoInfo videoInfo = null;
if (list != null && !list.isEmpty()) {
if (list.size() == 1) {
videoInfo = list.get(0);
} else {
// 计算当前实际分配比例与目标权重的差距
double maxPriority = -1;
VideoInfo selectedVideo = null;
// 获取总分配次数
int totalAllocations = allocationCountMap.values().stream().mapToInt(Integer::intValue).sum();
for (VideoInfo v : list) {
int allocated = allocationCountMap.getOrDefault(v.getId(), 0);
double targetRatio = v.getWeight() / 100.0; // 假设权重总和为100
double actualRatio = totalAllocations == 0 ? 0 : (double) allocated / totalAllocations;
// 计算优先级(目标比例与实际比例的差距)
double priority = targetRatio - actualRatio;
if (priority > maxPriority) {
maxPriority = priority;
selectedVideo = v;
}
}
videoInfo = selectedVideo;
// 更新分配计数
allocationCountMap.merge(videoInfo.getId(), 1, Integer::sum);
}
param.setVideoId(videoInfo.getId());
param.setVideoTitle(videoInfo.getTitle());
param.setCombination(videoInfo.getCombination());
extractDetail(videoInfo);
memberPromoterRelationMapper.insert(param);
}
return videoInfo;
方案三:平滑权重轮询(推荐)
使用Nginx的平滑权重轮询算法:
// 定义权重对象
public class WeightedVideo {
private VideoInfo videoInfo;
private int weight; // 固定权重
private int currentWeight; // 当前权重
public WeightedVideo(VideoInfo videoInfo, int weight) {
this.videoInfo = videoInfo;
this.weight = weight;
this.currentWeight = 0;
}
}
// 静态变量存储权重状态
private static List<WeightedVideo> weightedVideos = new ArrayList<>();
private static final Object lock = new Object();
// 方法实现
List<VideoInfo> list = videoInfoService.list(videoQueryWrapper);
VideoInfo videoInfo = null;
if (list != null && !list.isEmpty()) {
if (list.size() == 1) {
videoInfo = list.get(0);
} else {
synchronized (lock) {
// 初始化或更新权重列表
if (weightedVideos.isEmpty() || weightedVideos.size() != list.size()) {
weightedVideos.clear();
for (VideoInfo v : list) {
weightedVideos.add(new WeightedVideo(v, v.getWeight()));
}
}
// 平滑权重轮询算法
int totalWeight = 0;
WeightedVideo selected = null;
// 计算总权重并找到currentWeight最大的
for (WeightedVideo wv : weightedVideos) {
totalWeight += wv.weight;
wv.currentWeight += wv.weight;
if (selected == null || wv.currentWeight > selected.currentWeight) {
selected = wv;
}
}
// 选择视频并减少其currentWeight
if (selected != null) {
selected.currentWeight -= totalWeight;
videoInfo = selected.videoInfo;
}
}
}
param.setVideoId(videoInfo.getId());
param.setVideoTitle(videoInfo.getTitle());
param.setCombination(videoInfo.getCombination());
extractDetail(videoInfo);
memberPromoterRelationMapper.insert(param);
}
return videoInfo;
选择建议:
-
方案一:适用于不需要考虑历史记录,每次独立随机的情况,实现最简单
-
方案二:适用于需要严格控制分配比例的场景,但高并发下可能有性能问题
-
方案三:最推荐,分配平滑,能很好地按权重比例分配,且性能较好
根据你的需求,如果只是想根据权重随机分配,建议使用方案一;如果需要精确控制分配比例,建议使用方案三。
本文来自博客园,作者:VipSoft 转载请注明原文链接:https://www.cnblogs.com/vipsoft/p/19728820
浙公网安备 33010602011771号