package com.taobao.tddl.group.utils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import com.taobao.tddl.common.utils.RuntimeConfigHolder;
import com.taobao.tddl.common.utils.logger.Logger;
import com.taobao.tddl.common.utils.logger.LoggerFactory;
/**
* <pre>
* 使用例子:
* WeightRandom weightRandom = new WeightRandom( {key1=>8, key2=9 , key3=10});
* weightRandom.select(excludeKeys);
*
* </pre>
*
* @author jianghang 2013-10-25 下午6:01:26
* @since 5.0.0
*/
public class WeightRandom {
private static final Logger logger = LoggerFactory.getLogger(WeightRandom.class);
public static final int DEFAULT_WEIGHT_NEW_ADD = 0;
public static final int DEFAULT_WEIGHT_INIT = 10;
private Map<String, Integer> cachedWeightConfig;
private final RuntimeConfigHolder<Weight> weightHolder = new RuntimeConfigHolder<Weight>();
/**
* 保持不变对象,只能重建,不能修改
*/
private static class Weight {
public Weight(int[] weights, String[] weightKeys, int[] weightAreaEnds){
this.weightKeys = weightKeys;
this.weightValues = weights;
this.weightAreaEnds = weightAreaEnds;
}
public final String[] weightKeys; // 调用者保证不能修改其元素
public final int[] weightValues; // 调用者保证不能修改其元素
public final int[] weightAreaEnds; // 调用者保证不能修改其元素
}
public WeightRandom(Map<String, Integer> weightConfigs){
this.init(weightConfigs);
}
public WeightRandom(String[] keys){
Map<String, Integer> weightConfigs = new HashMap<String, Integer>(keys.length);
for (String key : keys) {
weightConfigs.put(key, DEFAULT_WEIGHT_INIT);
}
this.init(weightConfigs);
}
private void init(Map<String, Integer> weightConfig) {
this.cachedWeightConfig = weightConfig;
String[] weightKeys = weightConfig.keySet().toArray(new String[0]);
int[] weights = new int[weightConfig.size()];
for (int i = 0; i < weights.length; i++) {
weights[i] = weightConfig.get(weightKeys[i]);
}
int[] weightAreaEnds = genAreaEnds(weights);
weightHolder.set(new Weight(weights, weightKeys, weightAreaEnds));
}
/**
* 支持动态修改
*/
public void setWeightConfig(Map<String, Integer> weightConfig) {
this.init(weightConfig);
}
public Map<String, Integer> getWeightConfig() {
return this.cachedWeightConfig;
}
/**
* 假设三个库权重 10 9 8 那么areaEnds就是 10 19 27 随机数是0~27之间的一个数 分别去上面areaEnds里的元素比。
* 发现随机数小于一个元素了,则表示应该选择这个元素 注意:该方法不能改变参数数组内容
*/
private final Random random = new Random();
private String select(int[] areaEnds, String[] keys) {
int sum = areaEnds[areaEnds.length - 1];
if (sum == 0) {
// 各个dskey的权重都为0,打印可读性更强的日志
Weight w = this.weightHolder.get();
String dsKeys = Arrays.toString(w.weightKeys);
String weightValues = Arrays.toString(w.weightValues);
logger.error("all of current dbkeys weight is 0, maybe db error happened, dbKeys:" + dsKeys + ", weight:"
+ weightValues);
return null;
}
// 选择的过
// findbugs认为这里不是很好(每次都新建一个Random)(guangxia)
int rand = random.nextInt(sum);
for (int i = 0; i < areaEnds.length; i++) {
if (rand < areaEnds[i]) {
return keys[i];
}
}
return null;
}
/**
* 根据权重获取随机后的key
*
* @return
*/
public String select() {
return select(new ArrayList<String>(1));
}
/**
* @param excludeKeys 需要排除的key列表
* @return
*/
public String select(List<String> excludeKeys) {
final Weight w = weightHolder.get(); // 后续实现保证不能改变w中任何数组的内容,否则线程不安全
if (excludeKeys == null || excludeKeys.isEmpty()) {
return select(w.weightAreaEnds, w.weightKeys);
}
int[] tempWeights = w.weightValues.clone();
for (int k = 0; k < w.weightKeys.length; k++) {
if (excludeKeys.contains(w.weightKeys[k])) {
tempWeights[k] = 0;
}
}
int[] tempAreaEnd = genAreaEnds(tempWeights);
return select(tempAreaEnd, w.weightKeys);
}
private static int[] genAreaEnds(int[] weights) {
if (weights == null) {
return null;
}
int[] areaEnds = new int[weights.length];
int sum = 0;
for (int i = 0; i < weights.length; i++) {
sum += weights[i];
areaEnds[i] = sum;
}
if (logger.isDebugEnabled()) {
logger.debug("generate " + Arrays.toString(areaEnds) + " from " + Arrays.toString(weights));
}
if (sum == 0) {
logger.warn("generate " + Arrays.toString(areaEnds) + " from " + Arrays.toString(weights));
}
return areaEnds;
}
}