基于雪花算法的 PHP ID 生成器

基于雪花算法的 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 重复。

所以要想使用雪花算法生成唯一的 ID,就需要保证同一节点同一毫秒内生成的序列号是唯一的。基于此,我们在 SDK 中集成了多种序列号提供者:

  • RandomSequenceResolver(随机生成)
  • RedisSequenceResolver (基于 redis psetex 和 incrby 生成)
  • LaravelSequenceResolver(基于 redis psetex 和 incrby 生成)
  • SwooleSequenceResolver(基于 swoole_lock 锁)

不同的提供者只需要保证同一毫秒生成的序列号不同,就能得到唯一的 ID,最后附上 项目博客 地址,欢迎大家围观。

要求

  1. PHP >= 7.0
  2. Composer

安装

$ composer require godruoyi/php-snowflake -vvv

使用

  1. 简单使用.
$snowflake = new \Godruoyi\Snowflake\Snowflake;

$snowflake->id();
// 1537200202186752
  1. 指定数据中心ID及机器ID.
$snowflake = new \Godruoyi\Snowflake\Snowflake($datacenterId, $workerId);

$snowflake->id();
  1. 指定开始时间.
$snowflake = new \Godruoyi\Snowflake\Snowflake;
$snowflake->setStartTimeStamp(strtotime('2019-09-09')*1000);

$snowflake->id();

高级

  1. 在 Laravel 中使用

因为 SDK 相对简单,我们并没有提供 Laravel 的扩展包,你可通过下面的方式快速集成到 Laravel 中。

// App\Providers\AppServiceProvider

use Godruoyi\Snowflake\Snowflake;
use Godruoyi\Snowflake\LaravelSequenceResolver;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        $this->app->singleton('snowflake', function () {
            return (new Snowflake())
                ->setStartTimeStamp(strtotime('2019-08-08')*1000)
                ->setSequenceResolver(new LaravelSequenceResolver(
                    $this->app->get('cache')->store()
                ));
        });
    }
}
  1. 自定义序列号解决器

你可以通过实现 Godruoyi\Snowflake\SequenceResolver 接口来自定义序列号解决器。

class YourSequence implements SequenceResolver
{
    /**
     *  {@inheritdoc}
     */
    public function sequence(int $currentTime)
    {
          // Just test.
        return mt_rand(0, 1);
    }
}

// usage

$snowflake->setSequenceResolver(new YourSequence);
$snowflake->id();

你也可以直接使用闭包:

$snowflake = new \Godruoyi\Snowflake\Snowflake;
$snowflake->setSequenceResolver(function ($currentTime) {
    static $lastTime;
    static $sequence;

    if ($lastTime == $currentTime) {
        ++$sequence;
    }

    $lastTime = $currentTime;

    return $sequence;
})->id();

如果您在使用过程中遇到任何问题,欢迎提交 「PR」。

本作品采用《CC 协议》,转载必须注明作者和本文链接
二愣的闲谈杂鱼
本帖由系统于 4年前 自动加精
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 41
Epona

我选择 Str::uuid() 😂

4年前 评论
勇敢的心 1年前

使用之后,运营:麻烦替换成 字母 + 年月日 + 随机五位数, :relieved:

4年前 评论
Epona

@godruoyi 那倒是,一般的订单全都是数字

4年前 评论
godruoyi

@yxhsea 漂亮吧, 哈哈哈,链接在这里 https://carbon.now.sh

4年前 评论

如何设置位数 64位太多了

1年前 评论
panda-sir

这个不错 在掘金看过 当时想着用来做分布式消息中间件存储的request_id :+1:

4年前 评论

for($i=1;$i<=10;$i++){
$snowflake = new \Godruoyi\Snowflake\Snowflake(1, 1); $snowflake->setStartTimeStamp($xxx); $snowflake->setSequenceResolver(new RandomSequenceResolver());

        return $snowflake->id();

} 生成的id都是一样的。我批量入库的时候,会同时生成几条id。几条都是一样的

3年前 评论
godruoyi (楼主) 3年前
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;
}
3年前 评论
muqi001 (作者) 3年前
godruoyi (楼主) 3年前

高级用法用了LaravelSequenceResolver接口,还需要自定义序列号解决器吗?

3年前 评论
godruoyi (楼主) 3年前

支持一下。希望这个包可以一直完善。 之前就想过用,后来用了uuid进行转换。 没太研究过雪花的这个

4年前 评论

我们是要生成虚拟的银行卡号,但是只能是10位,最后一位 luhn 校验

file

4年前 评论
godruoyi (楼主) 4年前
GeorgeKing (作者) 4年前
godruoyi
4年前 评论
liuzhen992 3年前
GeorgeKing 4年前
$snowflake = new Snowflake();
$snowflake->setStartTimeStamp(strtotime("2019-01-01") * 1000);
$snowflake->setSequenceResolver(new RandomSequenceResolver());

dd(strlen($snowflake->id()));

为什么会生成17位的ID?

4年前 评论
godruoyi (楼主) 4年前
GeorgeKing (作者) 4年前

楼主第一张图片是用什么工具生成的?

4年前 评论

在一本 go 的书上看到过,,,看完之后觉得原来 “高大上” 的分布式 ID 生成器(原理),,,就是这么简单啊,,,

4年前 评论
godruoyi

@____ :joy: :joy: :joy:

4年前 评论
godruoyi

@drinke9 :stuck_out_tongue_winking_eye: :kissing_heart:

4年前 评论
drinke9

赞赞

4年前 评论
godruoyi

@kangfq 哈哈哈哈,一起造轮子

4年前 评论
godruoyi

@lovecn 看下版本呢,新版本是没有这个问题的呢

4年前 评论

@godruoyi 不错,,这个扩展我是一直在用.哈哈哈哈

4年前 评论
jsyzchen

建议讲下原理

4年前 评论

还要int转下?

>>> $snowflake = new \Godruoyi\Snowflake\Snowflake;
=> Godruoyi\Snowflake\Snowflake {#3060}
>>> $snowflake->setStartTimeStamp(strtotime('2019-09-09')*1000);
TypeError: Argument 1 passed to Godruoyi/Snowflake/Snowflake::setStartTimeStamp() must be of the type integer, float given on line 1
>>>
>>> $snowflake->id();
=> "-318767104"
4年前 评论
cy 4年前
godruoyi

@kangfq :joy: :joy: 标题没加 link,要点图片才进得去,不过我估计你开了 adblock 了的,把我的图片屏蔽了, :sob:

file

4年前 评论

博客相当骚啊,进去了标题是无法点击的,得先点分类然后再点文章才能看文章. :joy:

4年前 评论
godruoyi

@wanghan 是呀,一起来使用并完善更多特性吧 :see_no_evil: :see_no_evil:

4年前 评论
wanghan

这个不错啊,是稳定版本吗

4年前 评论
godruoyi

@Epona 可以用这种算法来生成唯一订单 ID 呀,UUID 不适合用来做订单 ID 吧。

4年前 评论
godruoyi

@panda-sir 👍👍

4年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!