php 雪花算法问题

高并发下,雪花算法id重复生成
源码上 如下解释

Snowflake 是 Twitter 内部的一个 ID 生算法,可以通过一些简单的规则保证在大规模分布式情况下生成唯一的 ID 号码。其组成为:

  • 第一个 bit 为未使用的符号位。
  • 第二部分由 41 位的时间戳(毫秒)构成,他的取值是当前时间相对于某一时间的偏移量。
  • 第三部分和第四部分的 5 个 bit 位表示数据中心和机器ID,其能表示的最大值为 2^5 -1 = 31。
  • 最后部分由 12 个 bit 组成,其表示每个工作节点每毫秒生成的序列号 ID,同一毫秒内最多可生成 2^12 -1 即 4095 个 ID。

需要注意的是:

  • 在分布式环境中,5 个 bit 位的 datacenter 和 worker 表示最多能部署 31 个数据中心,每个数据中心最多可部署 31 台节点
  • 41 位的二进制长度最多能表示 2^41 -1 毫秒即 69 年,所以雪花算法最多能正常使用 69 年,为了能最大限度的使用该算法,你应该为其指定一个开始时间。

由上可知,雪花算法生成的 ID 并不能保证唯一,如当两个不同请求同一时刻进入相同的数据中心的相同节点时,而此时该节点生成的 sequence 又是相同时,就会导致生成的 ID 重复。

*这里说明了是sequence 的原因,然后我看了下,结果发现了一个令我疑惑的问题

public function id()
    {
        $currentTime = $this->getCurrentMicrotime();
        while (($sequence = $this->callResolver($currentTime)) > (-1 ^ (-1 << self::MAX_SEQUENCE_LENGTH))) {
            usleep(1);
            $currentTime = $this->getCurrentMicrotime();
        }

        $workerLeftMoveLength = self::MAX_SEQUENCE_LENGTH;
        $datacenterLeftMoveLength = self::MAX_WORKID_LENGTH + $workerLeftMoveLength;
        $timestampLeftMoveLength = self::MAX_DATACENTER_LENGTH + $datacenterLeftMoveLength;

        return (string) ((($currentTime - $this->getStartTimeStamp()) << $timestampLeftMoveLength)
            | ($this->datacenter << $datacenterLeftMoveLength)
            | ($this->workerid << $workerLeftMoveLength)
            | ($sequence));
    }

代码里面有个usleep 函数,我查了下,是延迟1微秒 ,但是雪花算法的sequence 不是 一毫秒生成4095个吗,
就是很困惑,本人学艺不精,对算法了解不深,望指导

源码如下:


 *
 * This source file is subject to the MIT license that is bundled.
 */

namespace Snowflake;

class Snowflake
{
    const MAX_TIMESTAMP_LENGTH = 41;

    const MAX_DATACENTER_LENGTH = 5;

    const MAX_WORKID_LENGTH = 5;

    const MAX_SEQUENCE_LENGTH = 12;

    const MAX_FIRST_LENGTH = 1;

    /**
     * The data center id.
     *
     * @var int
     */
    protected $datacenter;

    /**
     * The worker id.
     *
     * @var int
     */
    protected $workerid;

    /**
     * The Sequence Resolver instance.
     *
     * @var \Godruoyi\Snowflake\SequenceResolver|null
     */
    protected $sequence;

    /**
     * The start timestamp.
     *
     * @var int
     */
    protected $startTime = 1607702400000;

    /**
     * Default sequence resolver.
     *
     * @var \Godruoyi\Snowflake\SequenceResolver|null
     */
    protected $defaultSequenceResolver;

    /**
     * Build Snowflake Instance.
     *
     * @param int $datacenter
     * @param int $workerid
     */
    public function __construct(int $datacenter = null, int $workerid = null)
    {
        $maxDataCenter = -1 ^ (-1 << self::MAX_DATACENTER_LENGTH);
        $maxWorkId = -1 ^ (-1 << self::MAX_WORKID_LENGTH);

        // If not set datacenter or workid, we will set a default value to use.
        $this->datacenter = $datacenter > $maxDataCenter || $datacenter < 0 ? mt_rand(0, 31) : $datacenter;
        $this->workerid = $workerid > $maxWorkId || $workerid < 0 ? mt_rand(0, 31) : $workerid;
    }

    /**
     * Get snowflake id.
     *
     * @return int
     */
    public function id()
    {
        $currentTime = $this->getCurrentMicrotime();
        while (($sequence = $this->callResolver($currentTime)) > (-1 ^ (-1 << self::MAX_SEQUENCE_LENGTH))) {
            usleep(1);
            $currentTime = $this->getCurrentMicrotime();
        }

        $workerLeftMoveLength = self::MAX_SEQUENCE_LENGTH;
        $datacenterLeftMoveLength = self::MAX_WORKID_LENGTH + $workerLeftMoveLength;
        $timestampLeftMoveLength = self::MAX_DATACENTER_LENGTH + $datacenterLeftMoveLength;

        return (string) ((($currentTime - $this->getStartTimeStamp()) << $timestampLeftMoveLength)
            | ($this->datacenter << $datacenterLeftMoveLength)
            | ($this->workerid << $workerLeftMoveLength)
            | ($sequence));
    }

    /**
     * Parse snowflake id.
     *
     * @param string $id
     *
     * @return array
     */
    public function parseId(string $id, $transform = false): array
    {
        $id = decbin($id);

        $data = [
            'timestamp' => substr($id, 0, -22),
            'sequence' => substr($id, -12),
            'workerid' => substr($id, -17, 5),
            'datacenter' => substr($id, -22, 5),
        ];

        return $transform ? array_map(function ($value) {
            return bindec($value);
        }, $data) : $data;
    }

    /**
     * Get current microtime timestamp.
     *
     * @return int
     */
    public function getCurrentMicrotime()
    {
        return floor(microtime(true) * 1000) | 0;
    }

    /**
     * Set start time (millisecond).
     *
     * @param int $startTime
     */
    public function setStartTimeStamp(int $startTime)
    {
        $missTime = $this->getCurrentMicrotime() - $startTime;
        if ($missTime < 0 || $missTime > ($maxTimeDiff = ((1 << self::MAX_TIMESTAMP_LENGTH) - 1))) {
            throw new \Exception('The starttime cannot be greater than current time and the maximum time difference is '.$maxTimeDiff);
        }

        $this->startTime = $startTime;

        return $this;
    }

    /**
     * Get start timestamp (millisecond), If not set default to 2019-08-08 08:08:08.
     *
     * @return int
     */
    public function getStartTimeStamp()
    {
        if ($this->startTime > 0) {
            return $this->startTime;
        }

        // We set a default start time if you not set.
        $defaultTime = '2019-08-08 08:08:08';

        return strtotime($defaultTime) * 1000;
    }

    /**
     * Set Sequence Resolver.
     *
     * @param SequenceResolver|callable $sequence
     */
    public function setSequenceResolver($sequence)
    {
        $this->sequence = $sequence;

        return $this;
    }

    /**
     * Get Sequence Resolver.
     *
     * @return \Godruoyi\Snowflake\SequenceResolver|callable|null
     */
    public function getSequenceResolver()
    {
        return $this->sequence;
    }

    /**
     * Get Default Sequence Resolver.
     *
     * @return \Godruoyi\Snowflake\SequenceResolver
     */
    public function getDefaultSequenceResolver(): SequenceResolver
    {
        return $this->defaultSequenceResolver ?: $this->defaultSequenceResolver = new RandomSequenceResolver();
    }

    /**
     * Call resolver.
     *
     * @param callable|\Godruoyi\Snowflake\SequenceResolver $resolver
     * @param int                                           $maxSequence
     *
     * @return int
     */
    protected function callResolver($currentTime)
    {
        $resolver = $this->getSequenceResolver();

        if (is_callable($resolver)) {
            return $resolver($currentTime);
        }

        return is_null($resolver) || !($resolver instanceof SequenceResolver)
            ? $this->getDefaultSequenceResolver()->sequence($currentTime)
            : $resolver->sequence($currentTime);
    }
}

你可能感兴趣的:(算法,php)