Implementation of Snowflake Algorithms in php (ID Incremental)

Keywords: PHP less

A brief description of the snowflake algorithm:

The highest bit is the symbol bit, always 0, not available.
41-bit time series, accurate to milliseconds, 41-bit length can be used for 69 years. Time bits also play an important role in sorting by time.
The 10-bit machine identifier and the 10-bit length support the deployment of 1024 nodes at most.
The 12-bit counting sequence number, which is a series of self-increasing ids, can support the same node to generate multiple ID serial numbers in the same millisecond. The 12-bit counting sequence number supports 4096 ID serial numbers per millisecond for each node.
As you can see, this algorithm is very simple and concise, but it is still a good ID generation strategy. Among them, the 10-bit identifier is usually a 5-bit IDC+5-bit machine number, the only one to determine.

<?php

namespace app\helpers;

/**
 * Snowflake Algorithms
 * @package app\helpers
 */
class SnowFlake
{
    const EPOCH_OFFSET = 0;  //Offset timestamp, the time must be less than the time of the first id generation, and as large as possible (affecting the effective available time of the algorithm)

    const SIGN_BITS = 1;        //Maximum bit (symbol bit) digit, always 0, not available
    const TIMESTAMP_BITS = 41;  //Timestamp digits (algorithm default 41 digits, can be used for 69 years)
    const DATA_CENTER_BITS = 5;  //IDC (Data Center) Number Digit (algorithm defaults 5 digits, up to 32 nodes are supported for deployment)
    const MACHINE_ID_BITS = 5;  //Machine number digits (the algorithm defaults to 5 digits and supports the deployment of up to 32 nodes)
    const SEQUENCE_BITS = 12;   //Counting sequence number digits, i.e. a series of self-increasing ids, can support the same node to generate multiple ID serial numbers in the same millisecond (the default algorithm is 12 bits, supporting each node to generate 4096 ID serial numbers per millisecond).

    /**
     * @var integer Data Center Number
     */
    protected $data_center_id;

    /**
     * @var integer Machine number
     */
    protected $machine_id;

    /**
     * @var null|integer The timestamp used in the last generation of id (millisecond level)
     */
    protected $lastTimestamp = null;

    /**
     * @var int
     */
    protected $sequence = 1;    //serial number
    protected $signLeftShift = self::TIMESTAMP_BITS + self::DATA_CENTER_BITS + self::MACHINE_ID_BITS + self::SEQUENCE_BITS;  //Left displacement digits of sign bits
    protected $timestampLeftShift = self::DATA_CENTER_BITS + self::MACHINE_ID_BITS + self::SEQUENCE_BITS;    //Time stamp left displacement digit
    protected $dataCenterLeftShift = self::MACHINE_ID_BITS + self::SEQUENCE_BITS;   //IDC Left Displacement Number
    protected $machineLeftShift = self::SEQUENCE_BITS;  //Machine Number Left Displacement Number
    protected $maxSequenceId = -1 ^ (-1 << self::SEQUENCE_BITS);    //Maximum serial number
    protected $maxMachineId = -1 ^ (-1 << self::MACHINE_ID_BITS);   //Maximum Machine Number
    protected $maxDataCenterId = -1 ^ (-1 << self::DATA_CENTER_BITS);   //Maximum Data Center Number

    /**
     * @param integer $dataCenter_id The unique ID of the data center (if you use multiple data centers, you need to set this ID to differentiate)
     * @param integer $machine_id The unique ID of the machine (if you use multiple machines, you need to set this ID to distinguish)
     * @throws \Exception
     */
    public function __construct($dataCenter_id = 0, $machine_id = 0)
    {
        if ($dataCenter_id > $this->maxDataCenterId) {
            throw new \Exception('Data Center Number Range:0-' . $this->maxDataCenterId);
        }
        if ($machine_id > $this->maxMachineId) {
            throw new \Exception('Machine Number Range:0-' . $this->maxMachineId);
        }
        $this->data_center_id = $dataCenter_id;
        $this->machine_id = $machine_id;
    }

    /**
     * Generate a unique ID using the snowflake algorithm
     * @return string Generated ID
     * @throws \Exception
     */
    public function generateID()
    {
        $sign = 0; //Symbol bit, value always 0
        $timestamp = $this->getUnixTimestamp();
        if ($timestamp < $this->lastTimestamp) {
            throw new \Exception('Time has gone backwards.!');
        }

        //Equal to the last timestamp, a sequence number needs to be generated. If not, the sequence number is reset.
        if ($timestamp == $this->lastTimestamp) {
            $sequence = ++$this->sequence;
            if ($sequence == $this->maxSequenceId) { //If the serial number exceeds the limit, the time will need to be retrieved
                $timestamp = $this->getUnixTimestamp();
                while ($timestamp <= $this->lastTimestamp) {    //The same time blockages
                    $timestamp = $this->getUnixTimestamp();
                }
                $this->sequence = 0;
                $sequence = ++$this->sequence;
            }
        } else {
            $this->sequence = 0;
            $sequence = ++$this->sequence;
        }

        $this->lastTimestamp = $timestamp;
        $time = (int)($timestamp - self::EPOCH_OFFSET);
        $id = ($sign << $this->signLeftShift) | ($time << $this->timestampLeftShift) | ($this->data_center_id << $this->dataCenterLeftShift) | ($this->machine_id << $this->machineLeftShift) | $sequence;

        return (string)$id;
    }

    /**
     * Get the current timestamp
     *
     * @return integer Millisecond timestamp
     */
    private function getUnixTimestamp()
    {
        return floor(microtime(true) * 1000);
    }
}

Posted by meediake on Sat, 12 Oct 2019 08:11:07 -0700