做消息队列时发现在 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 里,如下图,是正常的:

lua os毫秒时间戳 毫秒级时间戳_Score

但是我们加一位,例如,变成了 152837615266695181

lua os毫秒时间戳 毫秒级时间戳_Redis_02

哦豁,变成了科学计数?Emmm,显然不合情理,这玩意儿似乎会丢掉精度呢。

怎么办?既然只能 17 位,我们就考虑减少时间戳的位数,当然毫秒级时间戳是非常精确的,先来看几个效果:


保留 3 位毫秒,即 10 位秒级时间戳 + 3 位毫秒。

for ($i = 0; $i < 100; $i++) {
    echo msectime() . '<br>';
}

结果如下:(长图慎入)

lua os毫秒时间戳 毫秒级时间戳_PHP_03

看起来有点意思,但是注意屁股 3 位数,这循环 100 次的结果,大部分数据都是一样的,当然这种情况,我们可以考虑在后面直接 mt_rand() 生成 5 4 位随机数拼接上

我考虑了一下这个,还是觉得不太保险,继续尝试加长毫秒位数,直接加两位看看


5 位毫秒

for ($i = 0; $i < 100; $i++) {
    echo msectime(5) . '<br>';
}

结果如下:

lua os毫秒时间戳 毫秒级时间戳_Score_04

结果很好了!屁股两位的重复率很低!此时的数字长度为 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() 更可靠一些。

例如我们生成验证码之类,结果图参考:

lua os毫秒时间戳 毫秒级时间戳_Redis_05

回到正题!既然也存在小部分重复值的情况,在我看来依靠随机数总是有风险的(当然没有位数限制,完全可以考虑扩大随机范围)

于是我直接把毫秒时间戳扩大到 17 位再看


7 位毫秒

for ($i = 0; $i < 100; $i++) {
    echo msectime(7) . '<br>';
}

lua os毫秒时间戳 毫秒级时间戳_Score_06

惊喜的发现!完全没有重复值了,当然 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

欢迎大神提供更好的思路在秒杀等情况下生成不重复的数值。