All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.phantomthief.failover.impl.PriorityGroupManager Maven / Gradle / Ivy

There is a newer version: 0.1.32
Show newest version
package com.github.phantomthief.failover.impl;

import static java.util.stream.Collectors.toMap;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Random;
import java.util.Set;
import java.util.stream.Collectors;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe;

/**
 * 自动分组管理器,自动将资源分成若干组,第一组的priority是0,第二组是1,依次类推。
 * 同时,提供全量和增量变更管理的功能。
 *
 * 

* 举个例子,假设update后新的资源列表共100个,分两个组,分别有5个和95个资源。 *

  • 每个加入的资源有均等的机会进入第一组,几率为5%,这样可以避免新增的资源(服务器)没有流量,并且新增的资源和已有的资源流量应该是对等的; *
  • 对于保留的资源来说,如果一个资源A之前进入了前一组,而B没有,那么A仍然优先,不会出现B进了第一组而A没有进的情况 * (B有可能因为旧资源删除而晋升),这样可以尽量保持主调方的粘性,有利于连接复用和被调用方的缓存等。 *

    * * @author huangli * Created on 2020-02-03 */ @NotThreadSafe public class PriorityGroupManager { private final int sumOfCoreGroupSize; private final int[] coreGroupSizes; // contains resources from group 0 to group N-2, ordered private volatile ArrayList coreResources; // contains resources of group N-1 (the last group), has no order private volatile ArrayList restResources; // res -> [int priority, int logicIndex] private volatile HashMap resMap; /** * 构造自动分组管理器。 * @param initResources 初始资源列表 * @param coreGroupSizes 你对每组资源数量的要求,只填N-1个组,比如想分3组,第一组5个,第二组20个,剩下是第三组,那么这个参数传入[5, 10] */ public PriorityGroupManager(@Nonnull Set initResources, int... coreGroupSizes) { Objects.requireNonNull(initResources); Objects.requireNonNull(coreGroupSizes); this.coreGroupSizes = coreGroupSizes.clone(); int sum = 0; for (int size : coreGroupSizes) { if (size < 0) { throw new IllegalArgumentException("illegal coreGroupSizes:" + size); } sum += size; } this.sumOfCoreGroupSize = sum; ArrayList totalResources = new ArrayList<>(initResources); Collections.shuffle(totalResources); this.coreResources = new ArrayList<>(totalResources.subList(0, Math.min(sumOfCoreGroupSize, totalResources.size()))); if (totalResources.size() > sumOfCoreGroupSize) { this.restResources = new ArrayList<>(totalResources.subList(sumOfCoreGroupSize, totalResources.size())); } else { this.restResources = new ArrayList<>(); } resMap = buildResMap(coreResources, restResources, coreGroupSizes); } static HashMap buildResMap(List coreResources, List restResources, int[] coreGroupSizes) { final int totalGroupCount = coreGroupSizes.length + 1; HashMap map = new HashMap<>(); int logicIndex = 0; int priority = 0; int groupIndex = 0; for (T res : coreResources) { while (groupIndex >= coreGroupSizes[priority]) { priority++; groupIndex = 0; } map.put(res, new int[] {priority, logicIndex}); groupIndex++; logicIndex++; } for (T res : restResources) { map.put(res, new int[] {totalGroupCount - 1, logicIndex}); logicIndex++; } return map; } /** * 获取资源优先级map。 * @return map里面的key是资源,value是优先级(0 based) */ public Map getPriorityMap() { return resMap.entrySet().stream() .collect(toMap(Entry::getKey, e -> e.getValue()[0])); } /** * 获取某个资源的优先级 * @param res 资源 * @return 优先级(0 based) */ public int getPriority(T res) { return resMap.get(res)[0]; } /** * 全量更新,会自动计算出需要增加的、删除的、保留的资源,以前的分组数据会按几率分布保留。 * *

    * 举个例子,假设update后新的资源列表共100个,分两个组,分别有5个和95个资源。 *

  • 每个加入的资源有均等的机会进入第一组,几率为5%,这样可以避免新增的资源(服务器)没有流量,并且新增的资源和已有的资源流量应该是对等的; *
  • 对于保留的资源来说,如果一个资源A之前进入了前一组,而B没有,那么A仍然优先,不会出现B进了第一组而A没有进的情况 * (B有可能因为旧资源删除而晋升),这样可以尽量保持主调方的粘性,有利于连接复用和被调用方的缓存等。 *

    * @param resources 新的资源列表 */ public void updateAll(@Nonnull Set resources) { Objects.requireNonNull(resources); Set resNeedToAdd = resources.stream() .filter(res -> !resMap.containsKey(res)) .collect(Collectors.toSet()); Set resNeedToRemove = resMap.keySet().stream() .filter(res -> !resources.contains(res)) .collect(Collectors.toSet()); update0(resNeedToAdd, resNeedToRemove); } /** * 增量更新,会自动计算出需要增加的、删除的、保留的资源,以前的分组数据会按几率分布保留。 * *

    * 举个例子,假设update后新的资源列表共100个,分两个组,分别有5个和95个资源。 *

  • 每个加入的资源有均等的机会进入第一组,几率为5%,这样可以避免新增的资源(服务器)没有流量,并且新增的资源和已有的资源流量应该是对等的; *
  • 对于保留的资源来说,如果一个资源A之前进入了前一组,而B没有,那么A仍然优先,不会出现B进了第一组而A没有进的情况 * (B有可能因为旧资源删除而晋升),这样可以尽量保持主调方的粘性,有利于连接复用和被调用方的缓存等。 *

    * @param resNeedToAdd 需要添加的 * @param resNeedToRemove 需要删除的 */ public void update(@Nullable Set resNeedToAdd, @Nullable Set resNeedToRemove) { if (resNeedToAdd != null) { resNeedToAdd = new HashSet<>(resNeedToAdd); resNeedToAdd.removeAll(resMap.keySet()); if (resNeedToRemove != null) { // 以免两个set之间有重叠 resNeedToAdd.removeAll(resNeedToRemove); } } update0(resNeedToAdd, resNeedToRemove); } @SuppressWarnings("checkstyle:HiddenField") private void update0(@Nullable Set resNeedToAdd, @Nullable Set resNeedToRemove) { ArrayList coreResources = new ArrayList<>(this.coreResources); ArrayList restResources = new ArrayList<>(this.restResources); if (resNeedToRemove != null) { coreResources.removeAll(resNeedToRemove); restResources.removeAll(resNeedToRemove); } Random r = new Random(); while (coreResources.size() < sumOfCoreGroupSize && restResources.size() > 0) { int index = r.nextInt(restResources.size()); coreResources.add(restResources.remove(index)); } if (resNeedToAdd != null) { for (T res : resNeedToAdd) { int currentCount = coreResources.size() + restResources.size(); int index = r.nextInt(currentCount + 1); if (index < sumOfCoreGroupSize) { coreResources.add(index, res); } else { restResources.add(res); } if (coreResources.size() > sumOfCoreGroupSize) { // move last one restResources.add(coreResources.remove(coreResources.size() - 1)); } } } resMap = buildResMap(coreResources, restResources, coreGroupSizes); this.coreResources = coreResources; this.restResources = restResources; } }




  • © 2015 - 2024 Weber Informatics LLC | Privacy Policy