做消息隊列時發現在 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)
這種形式
我建議 substr(mt_rand(1, 99) * 0.01, 2)
這樣生成:
這樣能得到更廣的隨機結果值,相比起普通的 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
歡迎大神提供更好的思路在秒殺等情況下生成不重複的數值。