在设计邀请码、一次性授权码等场景时,通常会使用较短的随机字符串。
随机字符串越短,用户输入越方便,但碰撞风险也会显著上升。因此,长度设计本质上是在“可用性”和“唯一性”之间做平衡。
假定采用一个 Base32 字符集(A-Z 2-7),分析在每天生成 10,000 个随机字符串时,不同长度下的碰撞风险。
可用空间
对于长度为 k 的随机字符串,其理论可用空间为:
$
N = 32^k
$
其中:
- k:随机段长度
- N:总组合数
例如:
- 4 位随机码:$32^4 = 1,048,576$
- 6 位随机码:$32^6 \approx 1.07 \times 10^9$
- 8 位随机码:$32^8 \approx 1.10 \times 10^{12}$
碰撞风险
新人可能会觉得只要总容量大于每天生成数量,就足够安全。
例如:每天生成 10,000 个随机码,4 位 Base32 一共有约 104 万种组合,看起来容量远大于 10,000,因此似乎可以使用。
但实际并非如此,因为碰撞不是发生在“空间耗尽”时,而是会随着样本数量增加迅速上升。
概率学上有一个 “生日悖论”:在一个随机选择的23人群体中,至少有两个人生日相同的概率超过50%。
随机码的碰撞也是同样的数学问题。
碰撞概率模型
设:
- 每日生成数量为 (n)
- 总可用空间为 (N)
则至少发生一次碰撞的概率为:
$
P \approx 1 - e^{-\frac{n(n-1)}{2N}}
$
当概率较低时,可以使用工程近似:
$
P \approx \frac{n^2}{2N}
$
本文假设:
- 每天生成随机码数量:10,000
- 即:
$
n = 10,000
$
不同长度下的碰撞风险
-
4 位随机码
$
N = 32^4 = 1.05 \times 10^6
$$
P \approx \frac{10^8}{2.1 \times 10^6} \approx 47.6
$由于概率已经远大于 1,说明碰撞几乎必然发生,甚至可以说会有大量重复。
-
5 位随机码
$
N = 32^5 = 3.36 \times 10^7
$$
P \approx \frac{10^8}{6.72 \times 10^7} \approx 1.49
$虽然空间扩大了 32 倍,但仍然处于极高碰撞区间。
-
6 位随机码
$
N = 32^6 = 1.07 \times 10^9
$$
P \approx \frac{10^8}{2.14 \times 10^9} \approx 4.7%
$每天约有 1 / 21 的概率至少发生一次碰撞。
对于低频业务,这种风险可能可以接受,勉强可以使用。 -
7 位随机码
$
N = 32^7 = 3.44 \times 10^{10}
$$
P \approx \frac{10^8}{6.88 \times 10^{10}} \approx 0.145%
$每天约有 1 / 690 的概率发生一次碰撞,基本合理。
-
8 位随机码
$
N = 32^8 \approx 1.10 \times 10^{12}
$$
P \approx \frac{10^8}{2.2 \times 10^{12}} = 4.55 \times 10^{-5} \approx 0.0045%
$每天约 1 / 22,000 的概率发生一次碰撞,这个概率已经足够低,可以满足生产环境要求。
注意
即使使用 8 位随机码,理论上仍然不能保证绝对不碰撞。
因此在生产环境中,必须增加兜底机制:
- 数据库唯一索引
- 插入失败自动重试
- 限制最大重试次数
- 记录碰撞次数用于监控