做消息队列时发现在 Redis 的 ZSet 中,Score 数字只能设置到 17 位,我想让 Score 唯一,就尝试了几种时间戳 + 随机数的组合,于是得出一些答案
代码如下:
/**
* 取毫秒级时间戳,默认返回普通秒级时间戳 time() 及 3 位长度毫秒字符串
*
* @param int $msec_length 毫秒长度,默认 3
* @param int $random_length 添加随机数长度,默认 0
* @param bool $dot 随机是否存入小数点,默认 false
* @param int $delay 是否延迟,传入延迟秒数,默认 0
* @return string
*/
function msectime($msec_length = 3, $random_length = 0, $dot = false, $delay = 0) {
list($msec, $sec) = explode(' ', microtime());
$rand = $random_length > 0 ?
number_format(
mt_rand(1, (int)str_repeat('9', $random_length))
* (float)('0.' . str_repeat('0', $random_length - 1) . '1'),
$random_length,
'.',
'') : '';
$msectime = sprintf('%.0f', (floatval($msec) + floatval($sec) + $delay) * pow(10, $msec_length));
return $dot ? $msectime . '.' . substr($rand, 2) : $msectime . substr($rand, 2);
}
假设此刻调用上面的方法 msectime(7)
得到 15283761526669518
然后把这个数字扔进 Redis ZSet 的 Score 里,如下图,是正常的:
但是我们加一位,例如,变成了 152837615266695181
哦豁,变成了科学计数?Emmm,显然不合情理,这玩意儿似乎会丢掉精度呢。
怎么办?既然只能 17 位,我们就考虑减少时间戳的位数,当然毫秒级时间戳是非常精确的,先来看几个效果:
保留 3 位毫秒,即 10 位秒级时间戳 + 3 位毫秒。
for ($i = 0; $i < 100; $i++) {
echo msectime() . '<br>';
}
结果如下:(长图慎入)
看起来有点意思,但是注意屁股 3 位数,这循环 100 次的结果,大部分数据都是一样的,当然这种情况,我们可以考虑在后面直接 mt_rand()
生成 5 4 位随机数拼接上
我考虑了一下这个,还是觉得不太保险,继续尝试加长毫秒位数,直接加两位看看
5 位毫秒
for ($i = 0; $i < 100; $i++) {
echo msectime(5) . '<br>';
}
结果如下:
结果很好了!屁股两位的重复率很低!此时的数字长度为 15 位,似乎再生成两位随机数就可以了?
当然常规的随机数生成会考虑 mt_rand(10, 99)
这种形式
我建议这样生成:
function ex_mt_rand($length) {
$result = '';
for ($i = 0; $i < $length; $i++) {
$result .= mt_rand(0, 9);
}
return $result;
}
这样能得到更广的随机结果值,相比起普通的 mt_rand()
更可靠一些。
例如我们生成验证码之类,结果图参考:
回到正题!既然也存在小部分重复值的情况,在我看来依靠随机数总是有风险的(当然没有位数限制,完全可以考虑扩大随机范围)
于是我直接把毫秒时间戳扩大到 17 位再看
7 位毫秒
for ($i = 0; $i < 100; $i++) {
echo msectime(7) . '<br>';
}
惊喜的发现!完全没有重复值了,当然 Redis 里的 Score 长度被用光了,无法再随机哪怕 1 位数字
不过这个我的需求已经满足了!
以上方法是本人根据实际需求所写的一个,通过这个思路我们可以利用毫秒级时间戳生成订单号等需求。
再写一个例子:
msectime(3, 5, true);
// 得到结果如下
1528377789213.17053
1528377789213.19110
1528377789213.80717
1528377789213.12004
1528377789214.91335
1528377789214.08298
1528377789214.92701
1528377789214.85589
1528377789214.62383
欢迎大神提供更好的思路在秒杀等情况下生成不重复的数值。