Java 案例:不同场景的权重分配方案
下面我给你一套可直接落地的权重分配方案,覆盖 5 种典型场景,每种都给出核心思路 + Java 代码。

场景总览
| 场景 | 典型业务 | 推荐算法 |
|---|---|---|
| 固定权重 | 页面模块占比、资源配额 | 静态配置 |
| 按比例动态分配 | AB 测试流量、灰度发布 | 比例/百分比 |
| 加权随机 | 广告投放、商品推荐 | 前缀和 + 二分 / 别名法 |
| 加权轮询 | 负载均衡、任务调度 | 平滑加权轮询 |
| 多因素综合评分 | 风控评分、排序 | 归一化 + 加权求和 |
场景 1:固定权重(配置化)
public class FixedWeightConfig {
// 场景 -> 权重配置
private static final Map<String, Map<String, Double>> CONFIG = Map.of(
"homepage", Map.of("banner", 0.5, "recommend", 0.3, "news", 0.2),
"search", Map.of("ad", 0.4, "organic", 0.6)
);
public static double getWeight(String scene, String item) {
return CONFIG.getOrDefault(scene, Map.of()).getOrDefault(item, 0.0);
}
}
适合:权重几乎不变、需要审计追溯的场景。
场景 2:按比例分配流量(AB 测试)
public class TrafficAllocator {
private final NavigableMap<Double, String> buckets = new TreeMap<>();
private double total = 0;
public TrafficAllocator(Map<String, Double> weights) {
weights.forEach((key, w) -> {
total += w;
buckets.put(total, key);
});
}
public String pick(double random) {
return buckets.ceilingEntry(random * total).getValue();
}
public static void main(String[] args) {
TrafficAllocator alloc = new TrafficAllocator(
Map.of("groupA", 30.0, "groupB", 50.0, "groupC", 20.0)
);
Random r = new Random();
long[] cnt = new long[3];
for (int i = 0; i < 100_000; i++) {
String g = alloc.pick(r.nextDouble());
if ("groupA".equals(g)) cnt[0]++;
else if ("groupB".equals(g)) cnt[1]++;
else cnt[2]++;
}
System.out.printf("A=%d, B=%d, C=%d%n", cnt[0], cnt[1], cnt[2]);
}
}
适合:灰度发布、实验分组。用户体验一致性可用
userId.hashCode()代替随机数。
场景 3:加权随机(推荐/广告)
方案 A:前缀和 + 二分(O(log n) 查询)
public class WeightedRandom {
private final double[] prefix;
private final String[] items;
private final Random random = new Random();
public WeightedRandom(Map<String, Double> weights) {
int n = weights.size();
this.items = weights.keySet().toArray(new String[0]);
this.prefix = new double[n];
double sum = 0;
for (int i = 0; i < n; i++) {
sum += weights.get(items[i]);
prefix[i] = sum;
}
}
public String next() {
double target = random.nextDouble() * prefix[prefix.length - 1];
int idx = Arrays.binarySearch(prefix, target);
if (idx < 0) idx = -idx - 1;
return items[Math.min(idx, items.length - 1)];
}
}
方案 B:别名法(Alias Method,O(1) 查询)
public class AliasMethod {
private final int[] alias;
private final double[] prob;
private final String[] items;
private final Random random = new Random();
public AliasMethod(Map<String, Double> weights) {
int n = weights.size();
items = weights.keySet().toArray(new String[0]);
double[] p = new double[n];
double sum = weights.values().stream().mapToDouble(d -> d).sum();
for (int i = 0; i < n; i++) p[i] = weights.get(items[i]) * n / sum;
alias = new int[n];
prob = new double[n];
Deque<Integer> small = new ArrayDeque<>(), large = new ArrayDeque<>();
for (int i = 0; i < n; i++) (p[i] < 1 ? small : large).push(i);
while (!small.isEmpty() && !large.isEmpty()) {
int s = small.pop(), l = large.pop();
prob[s] = p[s];
alias[s] = l;
p[l] = (p[l] + p[s]) - 1;
(p[l] < 1 ? small : large).push(l);
}
while (!large.isEmpty()) prob[large.pop()] = 1;
while (!small.isEmpty()) prob[small.pop()] = 1;
}
public String next() {
int i = random.nextInt(items.length);
return random.nextDouble() < prob[i] ? items[i] : items[alias[i]];
}
}
选型:场景候选少(<100)用 A;候选多、高频调用(广告召回/推荐)用 B。
场景 4:平滑加权轮询(负载均衡/任务调度)
public class SmoothWeightedRoundRobin {
static class Node {
String name;
int weight;
int current = 0;
Node(String name, int weight) { this.name = name; this.weight = weight; }
}
private final List<Node> nodes;
private int totalWeight;
public SmoothWeightedRoundRobin(Map<String, Integer> weights) {
nodes = new ArrayList<>();
weights.forEach((k, v) -> { nodes.add(new Node(k, v)); totalWeight += v; });
}
public synchronized String next() {
Node best = null;
for (Node n : nodes) {
n.current += n.weight;
if (best == null || n.current > best.current) best = n;
}
best.current -= totalWeight;
return best.name;
}
public static void main(String[] args) {
var lb = new SmoothWeightedRoundRobin(Map.of("A", 5, "B", 1, "C", 2));
for (int i = 0; i < 16; i++) System.out.print(lb.next() + " ");
// 输出:A B A C A A C A A B ...
}
}
相比朴素加权轮询,避免了大权重节点连续命中,分布更均匀。
场景 5:多因素综合评分(风控/排序)
public class ScoreCalculator {
// 各维度权重
private static final Map<String, Double> WEIGHTS = Map.of(
"credit", 0.40,
"behavior", 0.30,
"device", 0.20,
"history", 0.10
);
/** 各维度原始分需先归一化到 0~100 */
public static double score(Map<String, Double> rawScores) {
return WEIGHTS.entrySet().stream()
.mapToDouble(e -> {
double v = rawScores.getOrDefault(e.getKey(), 0.0);
double normalized = Math.max(0, Math.min(100, v)); // 截断
return normalized * e.getValue();
})
.sum();
}
public static void main(String[] args) {
double s = score(Map.of(
"credit", 85.0, "behavior", 70.0, "device", 60.0, "history", 90.0
));
System.out.println("综合分:" + s); // 77.0
}
}
关键点:
- 各维度必须先归一化再乘权重,否则量纲不同会导致权重失效
- 权重和最好 = 1,便于解读
- 复杂场景可用 AHP 层次分析法 或 熵权法 自动确定权重
选型决策树
权重是否恒定?
├── 是 → 硬编码/配置中心(场景1)
└── 否
├── 需要严格按比例(AB/灰度)→ 前缀和桶 + 稳定hash(场景2)
├── 只需概率分布 → 加权随机(场景3)
├── 需均匀轮转(负载)→ 平滑加权轮询(场景4)
└── 多维综合评估 → 归一化 + 加权求和(场景5)
生产环境建议
- 权重配置外置(Nacos/Apollo),避免重启
- 权重变化要原子切换:用
volatile引用整体替换对象,或用AtomicReference - 监控实际命中比例,与配置权重偏差 >5% 告警
- 兜底:所有权重为 0 或空时,走默认策略(如均匀分配)
- 可复现性:AB 场景用
userId的稳定哈希,同一用户始终进同一组
如果你有具体的业务场景(比如推荐系统的 CTI 加权、限流配额分配、多租户资源分配),告诉我,我可以给出更贴合的代码。
版权声明:本站文章为原创整理,转载请注明出处。