第4节:策略权重概率装配

这节课主要学习了“StrategyArmoryDispatch”这个接口

  • 他继承了如图两个类 

imageimageimage

  • 它调用的方法如下:

image

 

  • 他本身的方法如下:

image

  • 本身的方法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 → rateRange
    • strategy_rate_table_key100001_4000:102,103,104,105 → 乱序 Map
    • 同理 5000、6000 的 key

 

 

  • 抽奖过程

不带权重值:

  1. repository.getRateRange(100001L) → 从 Redis 取 strategy_rate_range_key100001,得到 rateRange(如 10000)
  2. new SecureRandom().nextInt(rateRange) → 生成 0 ~ rateRange-1 的随机数
  3. repository.getStrategyAwardAssemble("100001", 随机数) → 从 Redis Map strategy_rate_table_key100001 中取对应索引的值,即奖品 ID
  4. 返回奖品 ID

带权重值:

  1. 拼接 key:"100001_4000:102,103,104,105"
  2. repository.getRateRange(key) → 从 Redis 取 strategy_rate_range_key100001_4000:102,103,104,105
  3. 生成随机数 → 从 strategy_rate_table_key100001_4000:102,103,104,105 取奖品 ID
  4. 返回奖品 ID
  • 出的bug

1.主要是xml语句写错

strategy_rule_mapper.xml 有两个问题:

问题 1queryStrategyRule 同时写了 resultType 和 resultMap,冲突了。big-market 只有 resultMap

问题 2:where 条件里参数名用错了:

  • 你的:#{strategy_id} 和 #{ruleModel} — 下划线混驼峰
  • big-market:#{strategyId} 和 #{ruleModel} — 都是驼峰

问题 3:缺少 parameterType

image

 

  • 不懂的问题

1.为什么要这样装配两次 意义在哪里image

答:

这两次装配针对的是不同的抽奖场景

第一次装配 assembleLotteryStrategy("100001", strategyAwardEntities)

这是不带权重的基础概率表。所有奖品都参与,按各自的概率比例填充。当用户没有命中任何权重规则时(比如普通用户),就走这张表抽奖。

存入 Redis 的 key 是 strategy_rate_table_key100001

第二次装配 assembleLotteryStrategy("100001_4000:...", strategyAwardEntitiesClone)

这是带权重过滤的概率表。strategyAwardEntitiesClone 是经过 removeIf 过滤后的——只保留该权重档位允许的奖品。比如权重 4000 只保留奖品 102105,权重 6000 多保留 106109。

存入 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.看起来这个乱序有点怪怪的?

image

posted @ 2026-07-16 20:55  超级无敌大帅比  阅读(5)  评论(0)    收藏  举报