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;

选择建议:

  1. 方案一:适用于不需要考虑历史记录,每次独立随机的情况,实现最简单

  2. 方案二:适用于需要严格控制分配比例的场景,但高并发下可能有性能问题

  3. 方案三:最推荐,分配平滑,能很好地按权重比例分配,且性能较好

根据你的需求,如果只是想根据权重随机分配,建议使用方案一;如果需要精确控制分配比例,建议使用方案三

posted @ 2026-03-17 11:59  VipSoft  阅读(2)  评论(0)    收藏  举报