PHP 实现 Snowflake 生成分布式唯一 ID-php教程

资源魔 34 0
Twitter 的 snowflake 正在散布式天生惟一 UUID 使用仍是蛮宽泛的,基于 snowflake 的一些变种的算法网上也有很多。应用 snowflake 天生 UUID 不少都是正在散布式场景下应用,我看了下网上有此中有几篇 PHP 完成的都不思考到线程平安。

如今 PHP 有了 Swoole 的锁以及协程的加持,关于咱们开发线程平安以及高并发模仿仍是很不便的,这里用 PHP 连系 Swoole 来学习下完成最简略的 snowflake(良久没写 PHP,觉得不 IDE 真写没有了 PHP 了)。

先来看如下 snowflake 的构造:

92477c5b6e24846f407478b4737ada5.png

天生的数值是 64 位,分红 4 个局部:

● 第一个 bit 为符号位,最高位为 0 示意负数

● 第二局部 41 个 bit 用于记载天生 ID 时分的工夫戳,单元为毫秒,以是该局部示意的数值范畴为 2^41 - 1(69 年),它是绝对于某一工夫的偏偏移量

● 第三局部的 10 个 bit 示意工作节点的 ID,示意数值范畴为 2^10 - 1,相称于支持 1024 个节点

● 第四局部 12 个 bit 示意每一个工作节点没毫秒天生的轮回自增 id,最多能够天生 2^12 -1 个 id,凌驾归零期待下一毫秒从新自增

先贴下代码:

<?php
class Snowflake
{
    const EPOCH = 1543223810238;    // 肇始工夫戳,毫秒
    const SEQUENCE_BITS = 12;   //序号局部12位
    const SEQUENCE_MAX = -1 ^ (-1 << self::SEQUENCE_BITS);  // 序号最年夜值
    const WORKER_BITS = 10; // 节点局部10位
    const WORKER_MAX = -1 ^ (-1 << self::WORKER_BITS);  // 节点最年夜数值
    const TIME_SHIFT = self::WORKER_BITS + self::SEQUENCE_BITS; // 工夫戳局部左偏偏移量
    const WORKER_SHIFT = self::SEQUENCE_BITS;   // 节点局部左偏偏移量
    protected $timestamp;   // 前次ID天生工夫戳
    protected $workerId;    // 节点ID
    protected $sequence;    // 序号
    protected $lock;        // Swoole 互斥锁
    public function __construct($workerId)
    {
        if ($workerId < 0 || $workerId > self::WORKER_MAX) {
            trigger_error("Worker ID 凌驾范畴");
            exit(0);
        }
        $this->timestamp = 0;
        $this->workerId = $workerId;
        $this->sequence = 0;
        $this->lock = new swoole_lock(SWOOLE_MUTEX);
    }
    /**
     * 天生ID
     * @return int
     */
    public function getId()
    {
        $this->lock->lock();    // 这里肯定要记患上加锁
        $now = $this->now();
        if ($this->timestamp == $now) {
            $this->sequence++;
            if ($this->sequence > self::SEQUENCE_MAX) {
                // 以后毫秒内天生的序号曾经凌驾最年夜范畴,期待下一毫秒从新天生
                while ($now <= $this->timestamp) {
                    $now = $this->now();
                }
            }
        } else {
            $this->sequence = 0;
        }
        $this->timestamp = $now;    // 更新ID生工夫戳
        $id = (($now - self::EPOCH) << self::TIME_SHIFT) | ($this->workerId << self::WORKER_SHIFT) | $this->sequence;
        $this->lock->unlock();  //解锁
        return $id;
    }
    /**
     * 猎取以后毫秒
     * @return string
     */
    public function now()
    {
        return sprintf("%.0f", microtime(true) * 1000);
    }
}

其实逻辑其实不复杂,诠释一下代码中的位运算:

-1 ^ (-1 << self::SEQUENCE_BITS)

就是-1的二进制示意为1的补码,其实同等于 :

2**self::SEQUENCE_BITS - 1

最初局部左移后或运算:

(($now - self::EPOCH) << self::TIME_SHIFT) | ($this->workerId << self::WORKER_SHIFT) | $this->sequence;

这里次要是对除了了第一名符号位之外的三个局部进行左移相应的偏偏移量使其归位,并经过或运算从新整分解下面 snowflake 的构造,比方咱们用 3 局部 4 位来演示一下该归并操作:

0000 0000 0010  --左移0位--> 0000 0000 0010
0000 0000 0100  --左移4位--> 0000 0100 0000 --或操作-->1000 0100 0010
0000 0000 1000  --左移8位--> 1000 0000 0000

上面借助 Swoole 的协程以及 channel 来暴力测试一下,看看天生的 ID 能否会呈现反复的情况:

$snowflake = new Snowflake(1);
$chan = new chan(100000);
$n = 100000;
for ($i = 0; $i < $n; $i++) {
    go(function () use ($snowflake, $chan) {
        $id = $snowflake->getId();
        $chan->push($id);
    });
}
go(function () use ($chan, $n) {
    $arr = [];
    for ($i = 0; $i < $n; $i++) {
        $id = $chan->pop();  // PHP Swoole的channel肯定要写正在go(func)的协程外面!?
        if (in_array($id, $arr)) {
            exit("ID 已存正在");
        }
        array_push($arr, $id);
    }
});
$chan->close();
echo "ok";

跑了一下,的确没有会呈现反复的 ID,对了,我用 Golang 一样完成了 snowflake 并协顺序形式跑了一样的测试,PHP 的执行工夫是约莫 12 秒阁下,Golang 只要要 1 秒。文章有甚么谬误还请斧正,谢谢。

以上就是PHP 完成 Snowflake 天生散布式惟一 ID的具体内容,更多请存眷资源魔其它相干文章!

标签: php php开发教程 php开发资料 php开发自学

抱歉,评论功能暂时关闭!