微信紅包自 2014 年春節上線以來,以「全民搶紅包」現象級傳播重新定義了社交支付。除夕峰值 8.1 億次/分鐘的並發衝擊背後,是經過精心設計的隨機分配演算法。官方從未公開具體實現,但開發者社群通過數據採樣、逆向推導、開源實現給出了多種可行方案。本文綜合分析四大流派演算法,剖析其數學特性、工程權衡與實戰適用場景。
x₁...xₙk = 總金額 / Σxᵢyᵢ = k × xᵢ,保證總和精確等於總金額[0.01, 剩餘金額] 確保每包 ≥ 1 分優點:統計特徵最接近真實紅包體驗;缺點:需引入統計庫或自行實現 Box-Muller 變換,工程複雜度較高。
def weixin_divide_hongbao(money, n):
divide_table = [random.randint(1, 10000) for _ in range(n)]
sum_ = sum(divide_table)
return [x * money / sum_ for x in divide_table]
| 問題 | 現象 | 解決 |
|---|---|---|
| 浮點數精度 | 金額總和 ≠ 原始總額(如 19.999999 vs 20.00) | 用整數分(乘 100)計算,最後一包用餘額補齊 |
| 邊界值處理 | 可能出現 0 元紅包 | 最小權重設為 1,或採用「預留最小值」策略 |
適用場景:原型驗證、內部測試、非金融級應用。
int number = 10; // 紅包個數
float total = 100; // 總金額(分)
double min = 0.01; // 最小金額(元)
for (int i = 1; i < number; i++) {
// 安全上限:剩餘金額 - (剩餘人數 * 最小金額) / 調節係數
double max = (total - min * (number - i)) / ((number - i) / 2);
if (number - i <= 2) max = total - min * (number - i);
int money = (int)(min*100 + Math.random()*(max*100 - min*100 + 1));
money = money / 100.0f;
total -= money;
// 記錄 money...
}
// 最後一包 = total
k = (剩餘人數) / 2:前期 k 大 → 上限小 → 金額分散;後期 k 小 → 上限大 → 衝擊大獎剩餘人數 ≤ 2 時退化為均分餘額,保證最後兩包合理性double avg = total / (number - i + 1); // 當前理論均值 double max = Math.min(avg * 2, total - min * (number - i)); // 上限不超過 2 倍均值
有效壓縮波動範圍,體驗更平滑。
將紅包分配建模為有序統計量問題:
M、人數 N、最小值 m(X₁,...,Xₙ) 滿足 ΣXᵢ = M, Xᵢ ≥ mDirichlet(α₁,...,αₙ) 縮放平移至 [m, M] 區間αᵢ 可設計為遞增序列,實現「後發優勢」工程落地可用拒絕採樣或MCMC,但計算成本高,通常近似為分段均勻或 Beta 分布組合。
騰訊開源專案 redpack(C++/Go)核心邏輯:
LPOP 原子彈出,極致效能,無運算開銷
function generateRedpack(int $totalCents, int $count, int $minCents = 1): array {
if ($totalCents < $count * $minCents) throw new InvalidArgumentException('總金額不足');
$weights = [];
$remaining = $totalCents - $count * $minCents; // 扣除最小值後的可分配餘額
for ($i = 1; $i < $count; $i++) {
// 動態上限:剩餘均值 * 2,但不超過剩餘總額
$avg = $remaining / ($count - $i + 1);
$max = min((int)($avg * 2), $remaining);
$w = random_int(0, $max);
$weights[] = $w;
$remaining -= $w;
}
$weights[] = $remaining; // 最後一包全得剩餘
// 加回最小值並打亂
$packets = array_map(fn($w) => $w + $minCents, $weights);
shuffle($packets);
return $packets;
}
// 使用範例:20 元,10 個紅包
$packets = generateRedpack(2000, 10);
echo "紅包金額(分):" . implode(', ', $packets) . "\n";
echo "總和:" . array_sum($packets) . " 分\n";
| 派別 | 核心思想 | 公平性 | 波動度 | 工程複雜度 | 適用場景 |
|---|---|---|---|---|---|
| 統計採樣 | 截尾常態 + 修正因子 | 高 | 可控 | 高 | 核心業務、體驗敏感 |
| 均勻權重 | 隨機權重比例分配 | 中 | 大 | 極低 | 原型、內測、非金融 |
| 遞迴預留 | 動態上限 + 最小值保證 | 高 | 可調 | 中 | 中小型專案、教學 |
| 數學建模 | Dirichlet/有序統計量 | 最高 | 理論最優 | 極高 | 學術研究、極高併發 |
| 預計算+隊列 | 離線生成、線上 O(1) 彈出 | 高 | 可控 | 中(需 Redis) | 高併發核心交易系統 |
紅包演算法看似簡單,實則蘊含統計學、博弈論、分散式系統工程等多重考量。選型時以「業務體驗目標」為錨,在公平性、波動感、工程成本、合規風險間找到平衡點,才是架構師的核心價值。