snowflake算法 php,Snowflake —— 分布式全局唯一 id 生成算法
簡介
Snowflake 是 Twitter 提出一種的分布式唯一序列號生成算法,理論上單節點 1 毫秒可以生成 4096 個(每秒四百萬個)唯一序列,這個序列是個 long 類型的數字,在數據庫中的存儲和查詢也非常高效。其原理很簡單,卻又很精妙。
原理
Snowflake 算法生成的序列號充分地利用了每一個比特位,需要占用 8bytes ( 64bits ) 空間,這 64 個 bit 一共分成 4 部分:
0 | 00000000000000000000000000000000000000000 | 0000000000 | 000000000000
定值,1 位 | 時間戳(毫秒),41 位 | 節點序號,10 位 | 自增序列,12 位
第一部分只有一位,固定為 0 ,可以理解成是有符號長整型的正數;
第二部分占 41 位,用來存生成該序列號時的時間戳(毫秒),默認情況下可以用到 2039-09-07 23:47:35;
第三部分占 10 位,是機器節點 id,取值范圍為 0~1023(2^10-1) ,即最大支持 1024 個計算節點;
第四部分占 12 位,自增序列,每生成一個序列號之后循環自增,取值范圍是 0~4095(2^12-1)
算法就是這么簡單
實現
實現起來也非常簡單,這里給一個 PHP 的例子:
class Snowflake
{
// 41 位、10 位、 12 位的最大值,作為掩碼使用
const MAX41BITS = 2199023255551;
const MAX10BITS = 1023;
const MAX12BITS = 4095;
private $nodeId;
private $seq;
public function __construct($nodeId)
{
$this->nodeId = $nodeId;
$this->seq = 0;
}
public function next()
{
// 獲取當前時間戳(毫秒)
list($microsec, $sec) = explode(" ", microtime());
$timestamp = $sec * 1000 + (int)($microsec * 1000);
// 生成 id
// 這里用到了位運算,稍微解釋一下:
// 先將時間戳與 41 位的最大值做一次與運算,保證其長度不會超過 41 位;
// 再將節點號跟 10 位的最大值做與運算,也是保證其長度不會超過 10 位;
// 然后將時間戳左移 22 位、節點號左移 12 位,這樣就能使得時間戳在第 2 位到第 42 位、節點號在第 43 位到第 52 位;
// 最后再對時間戳、節點號、自增序列做一次或運算,這樣三個數字就拼接在一起了
$id = ($timestamp & self::MAX41BITS) << 22 | ($this->nodeId & self::MAX10BITS) << 12 | $this->seq;
// id 生成好之后將自增序列加 1 同時與 12 位的最大值做與運算,這樣就能達到循環滾動的效果
$this->seq = (++$this->seq) & self::MAX12BITS;
return $id;
}
public function parse($id)
{
$timestamp = ($id >> 22);
$nodeId = ($id >> 12) & self::MAX10BITS;
$seq = $id & self::MAX12BITS;
return [$timestamp, $nodeId, $seq];
}
}
// 測試一下
$snowflake = new Snowflake(1);
for ($i = 0; $i < 1000; $i++)
{
$id = $snowflake->next();
echo $id . " ";
list($timestamp, $nodeId, $seq) = $snowflake->parse($id);
echo $timestamp . " " . $nodeId . " " . $seq . PHP_EOL;
}
在我的機器上(MacBook Pro 2017 13inch i5/8G),一毫秒大概能生成 40 個左右 id,只達到了理論值一毫秒 4096 個的百分之一,所以不用擔心并發太大會導致生成了重復的 id(還沒有用完 4096 個自增序列,時間位就已經加一啦),而且,在更大并發的情況下,相信你也不會只用一個計算節點來抗~
改進
上面介紹的是 Snowflake 的常規版本,因為非常簡單,所以在實際項目中,我們可以根據業務來做一些改進,這里舉幾個改進點
時間戳
前面說到,時間戳位默認情況下可以用到 2039-09-07 23:47:35 ,這是因為我們是從 1970-01-01 08:00:00 (UTC+8) 作為時間原點的,而從 UNIX 時間零點到我們的項目上線這里有個四十多年的時間,因此我們可以設置一個新的時間零點,使得算法可以用更長時間。
例如我們可以新增一個 紀元常量 ,可以取新項目上線第一天的 0:00
const EPOCH = 1535731200000; // 2018-09-01 0:00:00.000
那么在 next() 里的位運算前,我們就需要將時間戳做一次減法:
$timestamp -= self::EPOCH;
同樣的,在 parse() 里需要將 self::EPOCH 加回去。只要保證同一個項目(或者同一個團隊)用同一個“時間零點”,就能夠將 id 還原出原有的信息
節點位、自增序列位
在實際項目中,我們往往不會/不需要部署 1024 個那么多的 snowflake 服務,所以我們可以將節點位由 10 位改成 5 位(最大支持 32 個節點)甚至是 4 位(最大支持 16 個節點),這樣就能多出來 5~6 位,可以拿來做什么呢?
可以存儲一些業務信息,比如說 user_id 、 order_id 等等都是用 snowflake 來生成的時候,我們可以將多出來的那幾位作為一個 業務編號位 ,通過這樣改進之后,一個 id 攜帶的信息就多了一些,拿到一個 id 之后也可以判斷出這個 id 是什么業務的,數據庫查詢的時候就能知道該查哪張表。
同樣的,自增序列位也可以按需縮減到 11 位或者是 10 位。
gRPC 服務
我們可以將 Snowflake 做成一個 RPC 服務,提供給不同的項目調用,這里我寫了一個簡單的 gRPC 的例子,用 Golang 寫的服務端(截止本文撰寫日,PHP 還不支持寫 gRPC 服務,只能寫客戶端),PHP 寫的客戶端
總結
以上是生活随笔為你收集整理的snowflake算法 php,Snowflake —— 分布式全局唯一 id 生成算法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: php如何实现省市,PHP简单实现正则匹
- 下一篇: yum源 php7.2,云服务器:Cen