第4节:策略权重概率装配
这节课主要学习了“StrategyArmoryDispatch”这个接口
-
他继承了如图两个类



-
它调用的方法如下:

-
他本身的方法如下:

-
本身的方法assemble 只有一个参数用来查询加装配,过程如下:
阶段一:策略装配 assembleLotteryStrategy(100001L)
步骤 1 — 查询奖品列表
repository.queryStrategyAwardList(100001L)→ 先查 Redis 缓存,没有再查 DB(strategy_award表),查出该策略下所有奖品的概率配置,返回List<StrategyAwardEntity>,然后写入 Redis 缓存。
步骤 2 — 组装基础概率查找表
- 调用
assembleLotteryStrategy("100001", strategyAwardEntities) - 计算最小概率值
minAwardRate和概率总和totalAwardRate - 概率范围
rateRange = totalAwardRate / minAwardRate(比如 1 / 0.0001 = 10000) - 按每个奖品的概率比例,把奖品 ID 填入一个 List 中,概率越高的奖品占位越多
Collections.shuffle乱序- 存入 Redis:
strategy_rate_range_key100001→ rateRange 值strategy_rate_table_key100001→ 乱序后的 Map(key=索引, value=奖品ID)
步骤 3 — 查询策略实体,检查权重规则
repository.queryStrategyEntityByStrategyId(100001L)→ 查strategy表,得到ruleModels = "rule_weight,rule_blacklist"- 提取出
ruleWeight = "rule_weight"(不为 null,继续)
步骤 4 — 查询权重规则配置
repository.queryStrategyRule(100001L, "rule_weight")→ 查strategy_rule表- 得到
StrategyRuleEntity,其ruleWeightValues是一个 Map,类似:text"4000:102,103,104,105" → [102, 103, 104, 105]"5000:102,103,104,105,106,107" → [102, 103, 104, 105, 106, 107]"6000:102,103,104,105,106,107,108,109" → [102, 103, 104, 105, 106, 107, 108, 109]
步骤 5 — 为每个权重档位组装独立的概率查找表
- 遍历每个权重 key,把奖品列表按权重值过滤(只保留该权重包含的奖品),然后重复步骤 2 的组装逻辑
- 存入 Redis:
strategy_rate_range_key100001_4000:102,103,104,105→ rateRangestrategy_rate_table_key100001_4000:102,103,104,105→ 乱序 Map- 同理 5000、6000 的 key
-
抽奖过程
不带权重值:
repository.getRateRange(100001L)→ 从 Redis 取strategy_rate_range_key100001,得到 rateRange(如 10000)new SecureRandom().nextInt(rateRange)→ 生成 0 ~ rateRange-1 的随机数repository.getStrategyAwardAssemble("100001", 随机数)→ 从 Redis Mapstrategy_rate_table_key100001中取对应索引的值,即奖品 ID- 返回奖品 ID
带权重值:
- 拼接 key:
"100001_4000:102,103,104,105" repository.getRateRange(key)→ 从 Redis 取strategy_rate_range_key100001_4000:102,103,104,105- 生成随机数 → 从
strategy_rate_table_key100001_4000:102,103,104,105取奖品 ID - 返回奖品 ID
-
出的bug
1.主要是xml语句写错
strategy_rule_mapper.xml 有两个问题:
问题 1:queryStrategyRule 同时写了 resultType 和 resultMap,冲突了。big-market 只有 resultMap。
问题 2:where 条件里参数名用错了:
- 你的:
#{strategy_id}和#{ruleModel}— 下划线混驼峰 - big-market:
#{strategyId}和#{ruleModel}— 都是驼峰
问题 3:缺少 parameterType。

-
不懂的问题
1.为什么要这样装配两次 意义在哪里![image]()
答:
这两次装配针对的是不同的抽奖场景:
第一次装配 assembleLotteryStrategy("100001", strategyAwardEntities)
这是不带权重的基础概率表。所有奖品都参与,按各自的概率比例填充。当用户没有命中任何权重规则时(比如普通用户),就走这张表抽奖。
存入 Redis 的 key 是 strategy_rate_table_key100001。
第二次装配 assembleLotteryStrategy("100001_4000:...", strategyAwardEntitiesClone)
这是带权重过滤的概率表。strategyAwardEntitiesClone 是经过 removeIf 过滤后的——只保留该权重档位允许的奖品。比如权重 4000 只保留奖品 102105,权重 109。6000 多保留 106
存入 Redis 的 key 是 strategy_rate_table_key100001_4000:102,103,104,105。
为什么要两套表?
业务场景:不同积分的用户抽奖范围不同。4000 分用户只能在 102105 里抽,6000 分用户可以多抽 106109。如果只有一张基础表,所有用户抽到的奖品范围一样,权重就没有意义了。
抽奖时:
- 普通用户 →
getRandomAwardId(100001L)→ 查基础表 - 4000 分用户 →
getRandomAwardId(100001L, "4000:102,103,104,105")→ 查权重表
两张表并存,根据用户身份路由到不同的表。
2.那如果是权重的用户的话 第一次装配是不是浪费了
答:
不算浪费,因为:
1. **第一次装配是给无权重用户用的**。如果来的用户没有命中任何权重规则,就走基础表。这是兜底方案。
2. **第二次装配依赖第一次的查询结果**。`strategyAwardEntities` 是从 DB 查出来的完整奖品列表,权重装配时直接 clone 一份再过滤,不需要再查一次 DB。
3. **Redis 里两张表互不干扰**,各自服务不同的用户群体。
真正可以优化的是:如果确定某条策略**所有用户都有权重**,那基础表确实不会被用到。但实际业务中,通常会有不满足任何权重条件的用户(比如新用户积分 0),基础表就是他们的兜底。所以保留是合理的。
3.乱序在哪里有乱序呢
在 `assembleLotteryStrategy` 方法的第 5 步:
```java
// 5. 对存储的奖品进行乱序操作
Collections.shuffle(strategyAwardSearchRateTables);
```
`strategyAwardSearchRateTables` 是一个 `List<Integer>`,里面按概率比例填充了奖品 ID。比如奖品 101 概率 0.5,奖品 102 概率 0.3,在 10000 的范围里 101 占 5000 个位置,102 占 3000 个位置——此时 List 是连续的 `[101,101,101..., 102,102,102...]`。
如果不乱序,随机数落在前半段永远抽到 101,后半段永远抽到 102,抽奖结果就可预测了。`Collections.shuffle` 打乱后,101 和 102 均匀散布在整个 List 中,随机数落在任意位置的概率才真正反映配置的权重。
4.看起来这个乱序有点怪怪的?



浙公网安备 33010602011771号